Правильное отслеживание зависимостей в пользовательской привязке

Я пытаюсь визуально отфильтровать строки таблицы, сгенерированные 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>

Другие вопросы по тегам