Фиксация нокаута на наблюдаемом массиве
Я начал изучать нокаут, и у меня возникли проблемы с фильтрацией наблюдаемого массива нажатием кнопки и отображением результатов.
Это моя модель:
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());
Это 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>
Я также не уверен, как связать функцию щелчка, чтобы фильтровать продукты по жанру. Я думал что-то вроде этого... но он не работает.
<button data-bind="click: filter('1')"> Filter </button>
self.filter = function(genre) {
self.filterProducts(genre);
}
Ответы
Ответ 1
У вас не может быть функция с параметрами внутри ko.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();
});
}
});
}
И в вашем обработчике click
просто установите текущий фильтр:
<button data-bind="click: function() { filter('1') }"> Filter </button>
self.filter = function(genre) {
self.currentFilter(genre);
}
Демо JSFiddle
Обратите внимание на function() { }
в случае необходимости, если вы хотите передать дополнительные аргументы a в привязку click
(см. также в documentation), иначе Knockout будет выполняйте свою функцию, когда она анализирует привязку, а не когда вы нажимаете кнопку.
Ответ 2
Сначала вы неправильно понимаете/используете для computed Observables
. Из Документация KnockoutJS:
это функции, зависящие от одного или нескольких других наблюдаемых, и будет автоматически обновляться всякий раз, когда любой из этих изменения зависимостей.
Вычисленный наблюдаемый filterProducts
зависит от наблюдаемого массива products
, который вы не меняете, вы просто прочитали его значение. Таким образом, нет ничего, чтобы уведомить filterProducts
для переоценки.
Итак, что было бы быстрым простым исправлением?
- Определите новый наблюдаемый объект
filteredGenre
, от которого зависит ваш filterProducts
.
- Измените
filterProducts
так, чтобы он проверял значение filteredGenre
и на основании этого возвращал отфильтрованные продукты.
- Измените
filter
функцию, чтобы при ее получении genre
она меняла filteredGenre
, что привело бы к переоценке вычисленного filterProducts
Надеюсь, у вас есть идея.
Ответ 3
Возможно, вы захотите посмотреть плагин для нокаутов от автора оригинального нокаута. Он имеет преимущества производительности в сценариях с большими коллекциями. Подробнее см. blogpost.
self.filterProducts = self.products.filter(function(prod) {
return !self.currentFilter() || prod.genre == self.currentFilter();
});