Подтвердить что ты не робот

Нокаут afterRender, но только один раз

У меня есть простой наблюдаемый массив, который содержит много пользовательских моделей. В разметке есть шаблон с циклом foreach, который петли пользователей и выводит их в простой таблице. Я также создаю таблицу с помощью специальной полосы прокрутки и другого javascript. Итак, теперь я должен знать, когда цикл foreach закончен, и все модели добавлены в DOM.

Проблема с обратным вызовом afterRender заключается в том, что он вызывается каждый раз, когда что-то добавляется, но мне нужен вид обратного вызова, который срабатывает только один раз.

4b9b3361

Ответ 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})