Как создать функцию-обертку для привязок нокаута по умолчанию

Я показываю огромную табличную структуру с нокаутом. У пользователя есть возможность удалить строки, щелкнув флажок в строке:

data-bind="checked: row.removed"

Проблема заключается в том, что таблицу нужно перерисовать по клику, что на медленных компьютерах/браузерах занимает до одной или двух секунд - флажок меняет свое состояние после того, как таблица была отображена, поэтому пользовательский интерфейс чувствует себя невосприимчивым. Я хотел бы создать функцию-оболочку, которая делает то же самое, что и проверенная по умолчанию привязка по умолчанию, но дополнительно отображает символ загрузчика, а затем снова скрывает ее после того, как проверочная привязка выполнила свою работу. Что-то вроде:

ko.bindingHandlers.checkedWithLoader = {
    update: function(element, valueAccessor, allBindings) {
        loader.show();
        // call knockout default checked binding here
        loader.hide();
    }
};

Возможно ли подобное? Есть ли лучшая альтернатива?

Ответ 1

Как получить доступ к другим связям в пользовательской привязке

Вы можете использовать ko.applyBindingsToNode:

ko.applyBindingsToNode(element, { checked: valueAccessor() })

Источник нокаута активно раскрывает этот метод (здесь) и ссылается на него в примере на своей странице документации (здесь).

Вероятно, это не решит вашу проблему при обработке медленных рендерингов, хотя...

Обработка медленных обновлений

Вы также можете создать дополнительный слой в своей модели viewmodel для создания функции загрузки:

this.checked = ko.observable(false);

this.isLoading = ko.observable(false);
this.showLargeAndSlowTable = ko.observable(false);

this.checked.subscribe(function(isChecked) {
  this.isLoading(true);
  this.showLargeAndSlowTable(isChecked);
  this.isLoading(false);
}, this);

Вам понадобится привязка if или with, привязанная к showLargeAndSlowTable, и привяжите значение флажка к checked.

В некоторых случаях вам может потребоваться принудительно перерисовать между установкой loading наблюдаемой и инъекцией большого набора данных. В противном случае нокаут и браузер могут связывать эти обновления с одним фреймом.

Вы можете достичь этого, поместив showLargeAndSlowTable и isLoading(false) в setTimeout, или используя задержанный/дросселируемый дополнительный наблюдаемый, который запускает работу после того, как isLoading изменилось время рендеринга:

function AppViewModel() {
    var self = this;
    
    // The checkbox value that triggers the slow UI update
    this.showLargeTable = ko.observable(false);
    
    // Checkbox change triggers things
    this.showLargeTable.subscribe(updateUI)
    
    // Indicates when we're loading:
    this.working = ko.observable(false);
    this.delayedWorking = ko.pureComputed(function() {
      return self.working();
    }).extend({ throttle: 10 });
    
    // Instead of directly subscribing to `working`, we
    // subscribe to the delayed copy
    this.delayedWorking.subscribe(function(needsWork) {
      if (needsWork) {
        doWork();
        self.working(false);
      }
    });
    
    function updateUI(showTable) {
      if (showTable) {
        self.working(true); // Triggers a slightly delayed update
      } else {
        self.data([]);
      }
    }
    
    // Some data from doc. page to work with
    function doWork() {
      // (code only serves to mimic a slow render)
      for (var i = 0; i < 1499; i++) {
          self.data([]);
          self.data(data.reverse());
      }
    };
    
    var data = [
        { name: 'Alfred', position: 'Butler', location: 'London' },
        { name: 'Bruce', position: 'Chairman', location: 'New York' }
    ];
    
    // Some data to render
    this.data = ko.observableArray([]);
    
}


ko.applyBindings(new AppViewModel());
.is-loading {
  height: 100px;
  background: red;
  display: flex;
  align-items: center;
  justify-content: center;
}

.is-loading::after {
  content: "LOADING";
  color: white;
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<label>
  <input type="checkbox" data-bind="checked: showLargeTable, disable: working">Show slowly rendered table
</label>

<table data-bind="css: { 'is-loading': working }">
  <tbody data-bind="foreach: data">
    <tr>
      <td data-bind="text: name"></td>
      <td data-bind="text: position"></td>
      <td data-bind="text: location"></td>
    </tr>
  </tbody>
</table>

<em>Example based on the <a href="#" onclick="location.href='http://knockoutjs.com/documentation/deferred-updates.html'; return false;">knockout docs</a></em>