web-dev-qa-db-fra.com

Knockout afterRender, mais juste une fois

J'ai un observableArray simple qui contient beaucoup de modèles utilisateur. Dans le balisage, il y a un modèle avec une boucle foreach qui boucle les utilisateurs et les affiche dans un tableau simple. En outre, je style la table avec une barre de défilement personnalisée et quelques autres javascript. Alors maintenant, je dois savoir quand la boucle foreach est terminée et que tous les modèles sont ajoutés au DOM. 

Le problème avec le rappel afterRender est qu'il est appelé chaque fois que quelque chose est ajouté, mais j'ai besoin d'un type de rappel qui ne se déclenche qu'une seule fois. 

18
Sidney Widmer

Votre meilleur pari est d'utiliser une liaison personnalisée. Vous pouvez placer votre liaison personnalisée après foreach dans la liste des liaisons de votre data-bind ou exécuter votre code dans une setTimeout afin de permettre à foreach de générer le contenu avant que votre code ne soit exécuté.

Voici un exemple montrant le code en cours d'exécution une seule fois et chaque fois que votre observableArray est mis à jour: 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);
22
RP Niemeyer

Je suis venu avec une astuce élégante. Immédiatement après votre bloc template/foreach, ajoutez ce code:

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

Un moyen simple et rapide consiste à comparer, dans votre gestionnaire afterRender, l'élément actuel au dernier élément de votre liste. Si cela correspond, il s'agit de la dernière fois que afterRender est exécuté.

8
Matt Burland

La réponse de Jaffa contient une erreur, j'ai donc décidé de créer une nouvelle réponse au lieu de commenter. Il n’est pas possible d’utiliser simultanément un modèle et un modèle. Il suffit donc de déplacer votre modèle vers la balise data du modèle.

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);
2
Cheburek

Je ne sais pas si la réponse acceptée fonctionnera dans knockout-3.x (car les liaisons de données ne sont plus exécutées dans l'ordre dans lequel vous les déclarez).

Voici une autre option, il ne sera déclenché qu’une seule fois.

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.
            });
        }
}
1
j-a

Pour savoir quand le modèle foreach a terminé le rendu, vous pouvez créer une liaison "fictive", transmettre l'index de l'élément actuellement rendu à votre gestionnaire et vérifier s'il correspond à la longueur du tableau.

HTML:

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

ViewModel - Gestionnaire:

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

Tenez l'indicateur supplémentaire si vous souhaitez ajouter d'autres éléments à la liste.

0
MichaelS

Que diriez-vous d'utiliser 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})
0
Marcelo Mason

Juste au dessus de ma tête, vous pouvez soit:

  • Au lieu de vous connecter à l'événement afterRender, appelez simplement votre fonction après avoir poussé/inséré un élément dans le tableau.
  • Ou éventuellement, enveloppez le observableArray dans un observable qui contient les éléments enfants dessous avec son propre événement afterRender. La boucle foreach devrait faire référence au parent observable comme ceci:

Exemple:

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

Pas testé cela alors devinez ...

0
jaffa