Правильное отслеживание зависимостей в пользовательской привязке
Я пытаюсь визуально отфильтровать строки таблицы, сгенерированные foreach
связывание таким образом, что tr
элементы отфильтрованных строк будут скрыты, а не удалены из DOM.
Такой подход значительно повышает производительность рендеринга, когда пользователь меняет условия фильтра. Вот почему я не хочу foreach
быть привязанным к вычисляемому наблюдаемому массиву, который обновляется в зависимости от условия фильтра.
Я хочу, чтобы это решение было готовым к использованию строительным блоком, который я могу использовать в других местах проекта.
Насколько я знаю, Knockout, лучший способ - реализовать пользовательскую привязку.
То, как я намеревался использовать эту привязку, выглядит примерно так:
<tbody data-bind="foreach: unfilteredItems, visibilityFilter: itemsFilter">
<tr>
...
</tr>
</tbody>
где itemsFilter
функция, возвращающая boolean
в зависимости от того, должна ли быть видима текущая строка, например:
self.itemsFilter = function (item) {
var filterFromDate = filterFromDate(), // Observable
filterDriver = self.filterDriver(); // Observable too
return item && item.Date >= filterFromDate && (!filterDriver || filterDriver === item.DriverKey);
};
Вот обязательная реализация, которую я имею до сих пор:
/*
* Works in conjunction with the 'foreach' binding and allows to perform fast filtering of generated DOM nodes by
* hiding\showing them rather than inserting\removing DOM nodes.
*/
ko.bindingHandlers.visibilityFilter = {
// Ugly thing starts here
init: function (elem, valueAccessor) {
var predicate = ko.utils.unwrapObservable(valueAccessor());
predicate();
},
// Ugly thing ends
update: function (elem, valueAccessor) {
var predicate = ko.utils.unwrapObservable(valueAccessor()),
child = ko.virtualElements.firstChild(elem),
visibleUpdater = ko.bindingHandlers.visible.update,
isVisible,
childData,
trueVaueAccessor = function () { return true; },
falseVaueAccessor = function () { return false; };
while (child) {
if (child.nodeType === Node.ELEMENT_NODE) {
childData = ko.dataFor(child);
if (childData) {
isVisible = predicate(childData, child);
visibleUpdater(child, isVisible ? trueVaueAccessor : falseVaueAccessor);
}
}
child = ko.virtualElements.nextSibling(child);
}
}
};
ko.virtualElements.allowedBindings.visibilityFilter = true;
Вы видите это безобразно init
расстаться с вызовом предиката, не передавая ему объект?
Без этого, если нет строк, сгенерированных foreach
связывание в первый раз нокаут вызывает update
метод, itemsFilter
функция фильтра не будет вызвана.
Следовательно, никакие наблюдаемые не будут считываться, и механизм отслеживания зависимостей KO решает, что эта привязка не зависит от каких-либо наблюдаемых в моей модели представления.
А когда значения фильтра наблюдаемые (filterFromDate
а также filterDriver
) переодевайся update
никогда не будет вызван снова, и вся фильтрация не работает.
Как я могу улучшить эту реализацию (или весь подход к проблеме), чтобы не сделать этот уродливый вызов функции фильтра, которая по крайней мере заставляет функцию ждать undefined
значение как параметр?
1 ответ
Вы можете использовать visible
обязательный tr
и связать его с результатом вызова функции, используя $data
в качестве параметра. Немного демо ниже. Какое бы значение вы ни выбрали, оно будет отфильтровано из таблицы.
var vm = {
rows: ko.observableArray(['One', 'Two', 'Three']),
selected: ko.observable('One'),
isVisible: function(row) {
return row !== vm.selected();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options:rows, value:selected"></select>
<table border="1" data-bind="foreach:rows">
<tr data-bind="visible:$parent.isVisible($data)">
<td data-bind="text:$data"></td>
</tr>
</table>