2012-04-19 3 views
18

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

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

+0

Почему вы должны знать, когда они оказываются в DOM? Если вы применяете стили, CSS должен просто соответствовать правилу и применяться по мере добавления элементов. – arb

ответ

21

Лучше всего использовать пользовательские связывание. Вы можете либо разместить свое обязательное связывание после 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); 
+0

отлично! это было именно то, что я искал;) –

+0

Есть ли способ заставить это работать в шаблоне: {foreach: ...} 'call? –

+1

@JulesCopeland вы можете найти что-то вроде этого: http://jsfiddle.net/rniemeyer/Ampng/37/, где привязка захватывает зависимость от массива. –

0

Просто от верхней части головы можно либо:

  • Вместо того, чтобы закреплять в случае afterRender, просто вызовите вашу функцию после того, как у вас есть толчок/совал элемент в массиве.
  • Или, возможно, оберните наблюдаемый массив в наблюдаемом, который сам по себе имеет дочерние элементы под своим собственным событием afterRender. Цикл Еогеасп должен был бы обратиться к родителю наблюдаемой следующим образом:

Пример:

<div> 
    <div data-bind="with: parentItem(), template: { afterRender: myRenderFunc }" > 
     <div data-bind="foreach: observableArrayItems"> 
      ... 
     </div> 
    </div> 
    </div> 

Не тестировалось это так просто угадать ...

+1

Получение сообщения об ошибке: Сообщение: «Несколько привязок (с помощью и шаблона) пытаются контролировать привязки потомков одного и того же элемента. Вы не можете использовать эти привязки вместе на одном элементе. ' – Airn5475

7

Быстрый и простой способ - в обработчике afterRender сравнить текущий элемент с последним элементом в вашем списке. Если он совпадает, то это последний раз, когда запускается Render.

+0

Это умно. Я не думал об этом! – kamranicus

+0

Это отлично сработало для меня, спасибо за предложение! Это сокращает мою функцию прокрутки, которая вызывается один раз для каждого сообщения (возможно, тысячу раз) до одного раза. Держите это просто глупо, отлично! –

0

Чтобы узнать, когда шаблон foreach завершил рендеринг, вы можете сделать «фиктивную» привязку и передать текущий обработанный элемент в свой обработчик и проверить, соответствует ли его длина массива.

HTML:

<ul data-bind="foreach: list"> 
    <li> 
    \\ <!-- Your foreach template here --> 
    <div data-bind="if: $root.listLoaded($index())"></div> 
    </li> 
</ul> 

ViewModel - Handler:

this.listLoaded = function(index){ 
    if(index === list().length - 1) 
     console.log("this is the last item"); 
} 

Держите дополнительный флаг, если вы собираетесь добавить элементы в список.

8

Я придумал изящный обман.Сразу же после того, как ваш шаблон/Еогеасп блока, добавьте этот код:

<!--ko foreach: { data: ['1'], afterRender: YourAfterRenderFunction } --> 
<!--/ko--> 
+0

Работал отлично и спас меня много хлопот, спасибо! – Smegger

+2

+1 Это действительно здорово! но, вероятно, лучше использовать его экономно ... – easuter

0

Я не уверен, если принятый ответ будет работать в нокауте-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. 
      }); 
     } 
} 
0

Как насчет использования дебютанта?

var afterRender = _.debounce(function(){  

    // code that should only fire 50ms after all the calls to it have stopped 

}, 50, { leading: false, trailing: true}) 
2

Ответ Яффа содержит ошибку, поэтому я решил создать новый ответ вместо комментариев. Невозможно использовать одновременно с шаблоном. Так просто переместить модель для шаблона 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); 
Смежные вопросы