2012-04-19 18 views
18

Ho un semplice osservableArray che contiene molti modelli utente. Nel markup, esiste un modello con un ciclo foreach che esegue il loop degli utenti e li visualizza in una tabella semplice. Aggiungo inoltre stile al tavolo con una barra di scorrimento personalizzata e qualche altro javascript. Quindi ora devo sapere quando il ciclo foreach è finito e tutti i modelli sono aggiunti al DOM.Knockout afterRender, ma solo una volta

Il problema con il callback afterRender è che viene chiamato ogni volta che viene aggiunto qualcosa, ma ho bisogno di una sorta di richiamata che si attiva solo una volta.

+0

Perché avete bisogno di sapere quando vengono resi nel DOM? Se stai applicando gli stili, i CSS devono semplicemente corrispondere alla regola e applicare come gli elementi aggiunti. – arb

risposta

21

La cosa migliore è quella di utilizzare un costume rilegatura. Puoi inserire la tua rilegatura personalizzata dopo foreach nell'elenco di associazioni nel tuo data-bind oppure puoi eseguire il codice in un setTimeout per consentire a foreach di generare il contenuto prima che il codice venga eseguito.

Ecco un esempio che mostra l'esecuzione di codice una sola volta e l'esecuzione di codice ogni volta che gli aggiornamenti 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

great! questo era esattamente quello che stavo cercando;) –

+0

C'è un modo per farlo funzionare all'interno di una chiamata 'template: {foreach: ...}'? –

+1

@JulesCopeland si può andare con qualcosa di simile: http://jsfiddle.net/rniemeyer/Ampng/37/ dove l'associazione acquisisce una dipendenza dall'array. –

0

Appena fuori dalla parte superiore della mia testa si potrebbe o:

  • Invece di aggancio nell'evento AfterRender, è sufficiente chiamare la funzione dopo aver spinta/spuntato un elemento sulla matrice.
  • O possibilmente avvolgere l'ObservableArray in un osservabile che di per sé ha gli elementi secondari sotto con il proprio evento afterRender. Il ciclo foreach avrebbe bisogno di fare riferimento al genitore osservabile in questo modo:

Esempio:

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

Non testato questo modo solo indovinare ...

+1

Ottenere un errore: Messaggio: 'Più associazioni (con e modello) stanno cercando di controllare i collegamenti discendenti dello stesso elemento. Non è possibile utilizzare questi binding insieme sullo stesso elemento. – Airn5475

7

Un rapido e il modo semplice è, nel tuo gestore afterRender, confrontare l'elemento corrente con l'ultimo elemento nel tuo elenco. Se corrisponde, allora questa è l'ultima volta che si esegue Render.

+0

Questo è intelligente. Non ci ho pensato! – kamranicus

+0

Questo ha funzionato perfettamente per me, grazie per il suggerimento! Questo riduce la mia funzione di scroll che viene chiamata una volta per ogni messaggio (possibilmente un migliaio di volte) a una volta. Rendilo semplice, stupido, perfetto! –

0

Per sapere quando il modello foreach ha completato il rendering, è possibile eseguire un binding "fittizio" e passare l'indice di oggetti sottoposto a rendering corrente al gestore e verificare se corrisponde alla lunghezza dell'array.

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"); 
} 

Tenere bandiera in più se si ha intenzione di aggiungere altri elementi alla lista.

8

Mi sono inventato un imbroglione elegante.Subito dopo il tuo blocco template/foreach, aggiungere questo codice:

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

Ha funzionato perfettamente e mi ha salvato un sacco di problemi, grazie! – Smegger

+2

+1 Questo è veramente bello! ma probabilmente è meglio usarlo con parsimonia ... – easuter

0

Non sono sicuro se la risposta accettata lavorerà in eliminazione diretta-3.x (come data-binding non vengono eseguiti nell'ordine in cui li dichiara).

Questa è un'altra opzione, verrà attivata solo una volta.

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

Come utilizzare un debouncer?

var afterRender = _.debounce(function(){  

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

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

La risposta di Jaffa contiene un errore, quindi ho deciso di creare una nuova risposta invece di commentare. Non è possibile utilizzare con e template contemporaneamente. Quindi, basta spostare il modello per data tag del modello

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); 
Problemi correlati