Knockout Filtering on Observable Array
Solution 1
You cannot have a function with parameters inside a ko.computed
.
What you need is store the current filter in a new property and use that in your computed
function ProductModel() {
var self = this;
self.products = ko.observableArray([]);
self.currentFilter = ko.observable(); // property to store the filter
//...
self.filterProducts = ko.computed(function() {
if(!self.currentFilter()) {
return self.products();
} else {
return ko.utils.arrayFilter(self.products(), function(prod) {
return prod.genre == self.currentFilter();
});
}
});
}
And in your click
handler just set the current filter:
<button data-bind="click: function() { filter('1') }"> Filter </button>
self.filter = function(genre) {
self.currentFilter(genre);
}
Demo JSFiddle
Note the function() { }
in needed if you want to pass additional arguments a in click
binding (see also in the documentation), otherwise Knockout would execute your function when it parses the binding and not when you click on the button.
Solution 2
You might want to have a look at Knockout Projections plugin from the author of original knockout. It has performance advantages in scenarios with large collections. See blogpost for more details.
self.filterProducts = self.products.filter(function(prod) {
return !self.currentFilter() || prod.genre == self.currentFilter();
});
Solution 3
First you mis-understand/use for computed Observables
. From KnockoutJS documentation:
these are functions that are dependent on one or more other observables, and will automatically update whenever any of these dependencies change.
Your computed observable filterProducts
depend on observable array products
which you does not change, you just read it's value. So, there is nothing to notify filterProducts
to be re-evaluated.
So, what would be quick simple fix?
- Define a new observable object
filteredGenre
on which yourfilterProducts
will depend on. - Change
filterProducts
so that it check for the value offilteredGenre
and based on, it return filtered products. - Change
filter
function so that when it get newgenre
it changesfilteredGenre
which would result in re-evaluating of computedfilterProducts
I hope you got the idea.
Related videos on Youtube
SkelDave
Updated on August 04, 2020Comments
-
SkelDave almost 4 years
I've started learning Knockout and I'm having some trouble filtering an observable array on a button click and displaying the results.
This is my model:
function Product(data) { this.id = data.id; this.name = data.name; this.price = data.price; this.description = data.desc; this.image = data.image; this.genre = data.genre; this.show = data.show; this.offer_desc = data.offer_desc; this.offer_id = data.offer_id; } function ProductModel() { var self = this; self.products = ko.observableArray([]); $.getJSON('../PHP/Utilities.php?json=true', function(json) { var mappedProducts = $.map(json, function(item) { return new Product(item) }); self.products(mappedProducts); }); self.filterProducts = ko.computed(function(genre) { if(typeof genre === 'undefined') { return self.products(); //initial load when no genre filter is specified } else { return ko.utils.arrayFilter(self.products(), function(prod) { return prod.genre = genre; }); } }); } ko.applyBindings(new ProductModel());
This is the html:
<div data-bind="foreach: filterProducts"> <div class="row"> <div class="col-md-2"> <img data-bind="attr:{src: '../images/' + image, alt: name}" /> </div> <div class="col-md-2" data-bind="text: name"></div> <div class="col-md-1" data-bind="text: price"></div> <div class="col-md-3" data-bind="text: description"></div> <div class="col-md-1" data-bind='text: offer_id'> <div class="col-md-2" data-bind="text: genre"></div> <div class="col-md-1" data-bind="text: show"></div> </div> </div>
I'm also not sure how to bind a click function to filter the products on genre. I thought something like this...but it doesn't work
<button data-bind="click: filter('1')"> Filter </button> self.filter = function(genre) { self.filterProducts(genre); }
-
SkelDave over 10 yearsThanks for the reply but I still can't get the filter working. I changed the 'return prod.genre = currentFilter() to return ... self.currentFilter() because it threw an error. Otherwise, no errors are thrown, the product list just doesn't update. Also, what is the reason for adding the function(){} around the click binding? Thanks.
-
nemesv over 10 years@SkelDave yes the
self.currentFilter()
was a typo. But you don't just need to return you need to writereturn prod.genre == self.currentFilter();
a single=
is the assigment and the double==
is the equality check. I've added a working example and also some explanation of thefunction(){}
-
SkelDave over 10 yearsThank you, that has sorted out the problem!
-
Arun over 4 yearsThanks for clarifying the points and I had also misunderstood computed Observables. I followed your instructions in my implementation, works as expected.