Нокаут afterRender, но только один раз
У меня есть простой наблюдаемый массив, который содержит много пользовательских моделей.
В разметке есть шаблон с циклом foreach, который петли пользователей и выводит их в простой таблице. Я также создаю таблицу с помощью специальной полосы прокрутки и другого javascript. Итак, теперь я должен знать, когда цикл foreach закончен, и все модели добавлены в DOM.
Проблема с обратным вызовом afterRender заключается в том, что он вызывается каждый раз, когда что-то добавляется, но мне нужен вид обратного вызова, который срабатывает только один раз.
Ответы
Ответ 1
Лучше всего использовать пользовательскую привязку. Вы можете разместить свое обязательное связывание после foreach
в списке привязок в data-bind
или вы можете выполнить свой код в setTimeout
, чтобы разрешить foreach
генерировать контент до того, как ваш код будет выполнен.
Вот пример, показывающий, что каждый раз, когда обновляется ваш наблюдаемый массив, отображается код запуска за один раз и запускаемый код: 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);
Ответ 2
Я придумал изящный обман. Сразу же после блока template/foreach добавьте этот код:
<!--ko foreach: { data: ['1'], afterRender: YourAfterRenderFunction } -->
<!--/ko-->
Ответ 3
Быстрый и простой способ - в обработчике afterRender сравнить текущий элемент с последним элементом в вашем списке. Если он соответствует, то это последний раз после запуска Render.
Ответ 4
Ответ Jaffa содержит ошибку, поэтому я решил создать новый ответ вместо комментариев. Невозможно использовать одновременно с шаблоном.
Так что просто переместите свою модель в тег 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);
Ответ 5
Сверху моей головы вы можете:
- Вместо того, чтобы подключаться к событию afterRender, просто вызовите свою функцию после того, как вы нажали/вытащили элемент в массиве.
- Или, возможно, оберните наблюдаемый массив в наблюдаемом, который сам по себе имеет дочерние элементы под своим собственным событием afterRender. Цикл foreach должен ссылаться на родительский наблюдаемый следующим образом:
Пример:
<div>
<div data-bind="with: parentItem(), template: { afterRender: myRenderFunc }" >
<div data-bind="foreach: observableArrayItems">
...
</div>
</div>
</div>
Не проверено это, так что просто гадать...
Ответ 6
Чтобы узнать, когда шаблон foreach завершил рендеринг, вы можете сделать привязку "dummy" и передать текущий обработанный элемент в свой обработчик и проверить, соответствует ли его длина массива.
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");
}
Удерживайте дополнительный флаг, если вы намерены добавить в список больше элементов.
Ответ 7
Я не уверен, будет ли принятый ответ работать в 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.
});
}
}
Ответ 8
Как использовать debouncer?
var afterRender = _.debounce(function(){
// code that should only fire 50ms after all the calls to it have stopped
}, 50, { leading: false, trailing: true})