Нокаут после Рендера, но только один раз
У меня есть простой observableArray, который содержит много пользовательских моделей. В разметке есть шаблон с циклом foreach, который зацикливает пользователей и выводит их в виде простой таблицы. Я дополнительно оформляю таблицу с помощью пользовательской полосы прокрутки и некоторого другого JavaScript. Итак, теперь я должен знать, когда цикл foreach закончен и все модели добавлены в DOM.
Проблема с обратным вызовом afterRender состоит в том, что он вызывается каждый раз, когда что-то добавляется, но мне нужен своего рода обратный вызов, который срабатывает только один раз.
8 ответов
Лучше всего использовать пользовательскую привязку. Вы можете разместить свое собственное связывание после foreach
в списке привязок в вашем data-bind
или вы могли бы выполнить свой код в setTimeout
позволять foreach
генерировать контент до того, как ваш код будет выполнен.
Вот пример, показывающий выполнение кода по одному разу и выполнение кода каждый раз, когда обновляется ваш observableArray: http://jsfiddle.net/rniemeyer/Ampng/
HTML:
<table data-bind="foreach: items, updateTableOnce: true">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
</tr>
</table>
<hr/>
<table data-bind="foreach: items, updateTableEachTimeItChanges: true">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
</tr>
</table>
<button data-bind="click: addItem">Add Item</button>
JS:
var getRandomColor = function() {
return 'rgb(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ')';
};
ko.bindingHandlers.updateTableOnce = {
init: function(element) {
$(element).css("color", getRandomColor());
}
};
//this binding currently takes advantage of the fact that all bindings in a data-bind will be triggered together, so it can use the "foreach" dependencies
ko.bindingHandlers.updateTableEachTimeItChanges = {
update: function(element) {
$(element).css("color", getRandomColor());
}
};
var viewModel = {
items: ko.observableArray([
{ id: 1, name: "one" },
{ id: 1, name: "one" },
{ id: 1, name: "one" }
]),
addItem: function() {
this.items.push({ id: 0, name: "new" });
}
};
ko.applyBindings(viewModel);
Я придумал элегантный чит. Сразу после вашего шаблона / блока foreach добавьте этот код:
<!--ko foreach: { data: ['1'], afterRender: YourAfterRenderFunction } -->
<!--/ko-->
Быстрый и простой способ - в вашем обработчике afterRender сравнить текущий элемент с последним в вашем списке. Если это совпадает, то это последний раз после запуска Render.
В ответе Яффо есть ошибка, поэтому я решил создать новый ответ вместо комментариев. Невозможно использовать одновременно с шаблоном и шаблоном. Так что просто перенесите вашу модель в шаблон data
тег
Html
<div data-bind="template: {data: myModel, afterRender: onAfterRenderFunc }" >
<div data-bind="foreach: observableArrayItems">
...
</div>
</div>
Javascript
var myModel = {
observableArrayItems : ko.observableArray(),
onAfterRenderFunc: function(){
console.log('onAfterRenderFunc')
}
}
ko.applyBinding(myModel);
Я не уверен, будет ли принятый ответ работать в knockout-3.x (поскольку привязки данных больше не выполняются в том порядке, в котором вы их объявляете).
Вот еще один вариант, он будет срабатывать только один раз.
ko.bindingHandlers.mybinding {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var foreach = allBindings().foreach,
subscription = foreach.subscribe(function (newValue) {
// do something here
subscription.dispose(); // dispose it if you want this to happen exactly once and never again.
});
}
}
Как насчет использования debouncer?
var afterRender = _.debounce(function(){
// code that should only fire 50ms after all the calls to it have stopped
}, 50, { leading: false, trailing: true})
Чтобы узнать, когда шаблон foreach завершил рендеринг, вы можете сделать фиктивную привязку и передать текущий обработанный индекс элемента вашему обработчику и проверить, соответствует ли он длине массива.
HTML:
<ul data-bind="foreach: list">
<li>
\\ <!-- Your foreach template here -->
<div data-bind="if: $root.listLoaded($index())"></div>
</li>
</ul>
ViewModel - Обработчик:
this.listLoaded = function(index){
if(index === list().length - 1)
console.log("this is the last item");
}
Удерживайте дополнительный флаг, если вы хотите добавить больше элементов в список.
Прямо на макушке моей головы вы можете:
- Вместо того, чтобы подключаться к событию afterRender, просто вызовите свою функцию после того, как вы нажали / вытолкнули элемент в массиве.
- Или, возможно, оберните массив observableArray в наблюдаемый объект, который сам по себе имеет дочерние элементы с собственным событием afterRender. Цикл foreach должен ссылаться на родительский объект, наблюдаемый следующим образом:
Пример:
<div>
<div data-bind="with: parentItem(), template: { afterRender: myRenderFunc }" >
<div data-bind="foreach: observableArrayItems">
...
</div>
</div>
</div>
Не проверял это так, просто гадал...