My goal is to implement a method to visually filter table rows generated by the foreach
binding. I want the filtered out rows to be hidden instead of removed from the DOM, as this can greatly improve rendering performance when filter conditions are changed by the user. Rather than binding the foreach
to a computed observable array that updates based on filter conditions, I am looking for a solution that can act as a reusable building block within my project.
From what I know about Knockout, it seems that creating a custom binding is the best approach for this task.
The envisioned use of this binding would look something like this:
<tbody data-bind="foreach: unfilteredItems, visibilityFilter: itemsFilter">
<tr>
...
</tr>
</tbody>
Where itemsFilter
is a function that returns a boolean value indicating whether the current row should be visible or not. For example:
self.itemsFilter = function (item) {
var filterFromDate = filterFromDate(), // Observable
filterDriver = self.filterDriver(); // Also an Observable
return item && item.Date >= filterFromDate && (!filterDriver || filterDriver === item.DriverKey);
};
This is the current implementation of the custom binding I have developed so far:
ko.bindingHandlers.visibilityFilter = {
/*
* Works with the 'foreach' binding and allows for rapid filtering of generated DOM nodes by hiding/showing them rather than inserting/removing DOM nodes.
*/
init: function (elem, valueAccessor) {
var predicate = ko.utils.unwrapObservable(valueAccessor());
predicate();
},
update: function (elem, valueAccessor) {
var predicate = ko.utils.unwrapObservable(valueAccessor()),
child = ko.virtualElements.firstChild(elem),
visibleUpdater = ko.bindingHandlers.visible.update,
isVisible,
childData,
trueValueAccessor = function () { return true; },
falseValueAccessor = function () { return false; };
while (child) {
if (child.nodeType === Node.ELEMENT_NODE) {
childData = ko.dataFor(child);
if (childData) {
isVisible = predicate(childData, child);
visibleUpdater(child, isVisible ? trueValueAccessor : falseValueAccessor);
}
}
child = ko.virtualElements.nextSibling(child);
}
}
};
ko.virtualElements.allowedBindings.visibilityFilter = true;
One issue with the current implementation is the perceived ugliness in the init
part where the predicate is invoked without passing an object to it. This call is necessary to ensure that the filter function is executed even if no rows are initially generated by the foreach
binding before the update
method is called. Without this initialization step, the filtering mechanism may fail to work properly when filter observables are changed due to Knockout's dependency tracking system considering the binding irrelevant.
I'm open to suggestions on how to enhance this implementation (or the overall approach to the problem) in order to avoid the awkward call to the filter function that currently awaits an undefined value as a parameter.