2012-04-19 16 views
18

Tengo un simple observableArray que contiene una gran cantidad de modelos de usuario. En el marcado, hay una plantilla con un bucle foreach que bucles a los usuarios y los genera en una tabla simple. Además, le doy estilo a la tabla con una barra de desplazamiento personalizada y algunos otros javascript. Así que ahora tengo que saber cuándo finaliza el ciclo foreach y todos los modelos se agregan al DOM.Knockout afterRender, pero solo una vez

El problema con la devolución de llamada afterRender es que se llama cada vez que se agrega algo, pero necesito una especie de devolución de llamada que se dispara solo una vez.

+0

¿Por qué necesita saber cuando se quedaban en el DOM? Si está aplicando estilos, CSS debería coincidir con la regla y aplicarse a medida que se agregan los elementos. – arb

Respuesta

21

Su mejor opción es utilizar una costumbre Unión. Puede colocar su enlace personalizado después de foreach en la lista de enlaces en su data-bind o puede ejecutar su código en setTimeout para permitir que foreach genere el contenido antes de que se ejecute su código.

Aquí es un ejemplo que muestra la ejecución de código una sola vez y la ejecución de código cada vez que sus actualizaciones 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

¡genial! esto era exactamente lo que estaba buscando;) –

+0

¿Hay alguna forma de hacer que esto funcione dentro de una llamada 'template: {foreach: ...}'? –

+1

@JulesCopeland podría ir con algo como esto: http://jsfiddle.net/rniemeyer/Ampng/37/ donde el enlace toma una dependencia de la matriz. –

0

Justo al lado de la parte superior de la cabeza que bien podría:

  • En vez de enganchar en el caso afterRender, sólo tiene que llamar a su función después de que haya empuje/hizo estallar un elemento de la matriz.
  • O, posiblemente, envuelva el observableArray en un observable que en sí mismo tiene los elementos secundarios debajo con su propio evento afterRender. El bucle foreach tendría que referirse al padre observables de este modo:

Ejemplo:

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

No se ha probado esto tan sólo una suposición ...

+1

Obteniendo un error: Mensaje: 'Múltiples enlaces (con y plantilla) están tratando de controlar los enlaces descendientes del mismo elemento. No puede usar estas uniones juntas en el mismo elemento. – Airn5475

7

Una rápida y la manera simple es, en su controlador afterRender, comparar el elemento actual con el último elemento en su lista. Si coincide, entonces esta es la última vez después de ejecutar el Render.

+0

Esto es inteligente. ¡No pensé en esto! – kamranicus

+0

Esto funcionó perfectamente para mí, ¡gracias por la sugerencia! Esto reduce la función de desplazamiento de mi función de desplazamiento una vez para cada mensaje (posiblemente mil veces) a una vez. ¡Que sea simple, estúpido, perfecto! –

0

Para saber cuándo la plantilla foreach ha completado la representación, puede hacer una vinculación "ficticia" y pasar el índice de elemento procesado actual a su controlador y verificar si coincide con la longitud de la matriz.

HTML:

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

ViewModel - Manipulador:

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

Bandera de espera adicional si tiene la intención de añadir más elementos a la lista.

8

Se me ocurrió una trampa elegent.Inmediatamente después de su bloque de plantilla/foreach, agregue este código:

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

Funcionó perfectamente y me ahorró muchas molestias, gracias! – Smegger

+2

+1 ¡Esto es realmente bueno! pero probablemente sea mejor usarlo con moderación ... – easuter

0

No estoy seguro de si la respuesta aceptada funcionará en nocaut-3.x (como dato-fijaciones ya no se ejecutan en el orden en que se declara)

Aquí hay otra opción, solo se disparará exactamente una vez.

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

¿Qué le parece usar un antirrebote?

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 respuesta de Jaffa contiene un error, así que decidí crear una nueva respuesta en lugar de comentar. No es posible usar con y plantilla al mismo tiempo. Así que basta con mover el modelo a data etiqueta de plantilla

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); 
Cuestiones relacionadas