Comment effacer / supprimer les liaisons observables dans Knockout.js?

113

Je crée des fonctionnalités sur une page Web que l'utilisateur peut exécuter plusieurs fois. Grâce à l'action de l'utilisateur, un objet / modèle est créé et appliqué au HTML à l'aide de ko.applyBindings ().

Le HTML lié aux données est créé via des modèles jQuery.

Jusqu'ici tout va bien.

Lorsque je répète cette étape en créant un deuxième objet / modèle et en appelant ko.applyBindings (), je rencontre deux problèmes:

  1. Le balisage montre l'objet / modèle précédent ainsi que le nouvel objet / modèle.
  2. Une erreur javascript se produit concernant l'une des propriétés de l'objet / modèle, bien qu'elle soit toujours rendue dans le balisage.

Pour contourner ce problème, après le premier passage, j'appelle .empty () de jQuery pour supprimer le modèle HTML qui contient tous les attributs de liaison de données, afin qu'il ne soit plus dans le DOM. Lorsque l'utilisateur démarre le processus pour la deuxième passe, le code HTML lié aux données est de nouveau ajouté au DOM.

Mais comme je l'ai dit, lorsque le HTML est de nouveau ajouté au DOM et lié au nouvel objet / modèle, il inclut toujours les données du premier objet / modèle, et j'obtiens toujours l'erreur JS qui ne se produit pas lors du premier passage.

La conclusion semble être que Knockout tient à ces propriétés liées, même si le balisage est supprimé du DOM.

Donc, ce que je recherche, c'est un moyen de supprimer ces propriétés liées de Knockout; dire à KO qu'il n'y a plus de modèle observable. Y a-t-il un moyen de faire cela?

ÉDITER

Le processus de base est que l'utilisateur télécharge un fichier; le serveur répond alors avec un objet JSON, le HTML lié aux données est ajouté au DOM, puis le modèle d'objet JSON est lié à ce HTML en utilisant

mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);

Une fois que l'utilisateur a effectué certaines sélections sur le modèle, le même objet est renvoyé sur le serveur, le HTML lié aux données est supprimé du DOM, et j'ai alors le JS suivant

mn.AccountCreationModel = null;

Lorsque l'utilisateur souhaite recommencer, toutes ces étapes sont répétées.

J'ai peur que le code soit trop «impliqué» pour faire une démo jsFiddle.

awj
la source
Il n'est pas recommandé d'appeler ko.applyBindings plusieurs fois, en particulier sur le même élément dom contenant. Il existe peut-être une autre façon de réaliser ce que vous voulez. Vous devrez cependant fournir plus de code. Veuillez inclure un jsfiddle si possible.
madcapnmckay
Pourquoi ne pas exposer une initfonction dans laquelle vous passez les données à appliquer?
KyorCode

Réponses:

169

Avez-vous essayé d'appeler la méthode de noeud propre de knockout sur votre élément DOM pour éliminer les objets liés en mémoire?

var element = $('#elementId')[0]; 
ko.cleanNode(element);

Ensuite, appliquer à nouveau les liaisons knockout sur cet élément uniquement avec vos nouveaux modèles de vue mettrait à jour votre liaison de vue.

KodeKreachor
la source
33
Cela fonctionne - merci. Cependant, je ne trouve aucune documentation sur cette méthode.
awj
J'ai moi aussi cherché de la documentation sur cette fonction utilitaire knockout. Aussi proche que je puisse le dire d'après le code source, il fait appel deleteà certaines touches sur les éléments dom eux-mêmes, ce qui est apparemment l'endroit où toute la magie knockout est stockée. Si quelqu'un a une source sur la documentation, je serais très obligé.
Patrick M
2
Vous ne le trouverez pas. J'ai cherché haut et bas des documents sur les fonctions utilitaires ko, mais aucun n'existe. Ce billet de blog est la chose la plus proche que vous trouverez, mais il ne couvre que les membres de ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels
1
Vous voudrez également supprimer manuellement les événements comme indiqué dans ma réponse ci-dessous.
Michael Berkompas
1
@KodeKreachor J'avais déjà posté l'exemple de travail ci-dessous. Mon point était qu'il serait peut-être préférable de garder la liaison intacte, tout en libérant les données dans le ViewModel à la place. De cette façon, vous n'aurez jamais à gérer un / re-binding. Cela semble plus propre que d'utiliser des méthodes non documentées pour dissocier manuellement directement du DOM. Au-delà de cela, CleanNode est problématique car il ne libère aucun des gestionnaires d'événements (voir la réponse ici pour plus de détails: stackoverflow.com/questions/15063794/… )
Zac
31

Pour un projet sur ko.unapplyBindingslequel je travaille, j'ai écrit une fonction simple qui accepte un nœud jQuery et le booléen remove. Il dissocie d'abord tous les événements jQuery car la ko.cleanNodeméthode ne s'en charge pas. J'ai testé des fuites de mémoire et cela semble fonctionner très bien.

ko.unapplyBindings = function ($node, remove) {
    // unbind events
    $node.find("*").each(function () {
        $(this).unbind();
    });

    // Remove KO subscriptions and references
    if (remove) {
        ko.removeNode($node[0]);
    } else {
        ko.cleanNode($node[0]);
    }
};
Michael Berkompas
la source
Juste une mise en garde, je n'ai pas testé la re-liaison à quelque chose qui vient d'être ko.cleanNode()appelé et pas le html entier remplacé.
Michael Berkompas
4
Votre solution ne dissocierait-elle pas également toutes les autres liaisons d'événements? est-il possible de supprimer uniquement les gestionnaires d'événements de ko?
lordvlad
sans modifier le noyau ko qui est
lordvlad
1
C'est vrai, mais ce n'est pas possible pour autant que je sache sans une modification de base. Voir ce problème que j'ai soulevé ici: github.com/SteveSanderson/knockout/issues/724
Michael Berkompas
L'idée de KO n'est-elle pas que vous devriez très rarement toucher le dom vous-même? Cette réponse boucle à travers le dom, et serait certainement loin d'être inutilisable dans mon cas d'utilisation.
Blowsie
12

Vous pouvez essayer d'utiliser la liaison avec que Knockout offre: http://knockoutjs.com/documentation/with-binding.html L'idée est d'utiliser des liaisons d'application une fois, et chaque fois que vos données changent, mettez simplement à jour votre modèle.

Disons que vous avez un storeViewModel de modèle de vue de niveau supérieur, votre panier représenté par cartViewModel, et une liste d'articles dans ce panier - disons cartItemsViewModel.

Vous lieriez le modèle de niveau supérieur - le storeViewModel à la page entière. Ensuite, vous pouvez séparer les parties de votre page qui sont responsables des articles du panier ou du panier.

Supposons que le cartItemsViewModel a la structure suivante:

var actualCartItemsModel = { CartItems: [
  { ItemName: "FirstItem", Price: 12 }, 
  { ItemName: "SecondItem", Price: 10 }
] }

Le cartItemsViewModel peut être vide au début.

Les étapes ressembleraient à ceci:

  1. Définissez les liaisons en html. Séparez la liaison cartItemsViewModel.

      
        <div data-bind="with: cartItemsViewModel">
          <div data-bind="foreach: CartItems">
            <span data-bind="text: ItemName"></span>
            <span data-bind="text: Price"></span>
          </div>
        </div>
      
    
  2. Le modèle de magasin provient de votre serveur (ou est créé de toute autre manière).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Définissez des modèles vides sur votre modèle de vue de niveau supérieur. Ensuite, une structure de ce modèle peut être mise à jour avec des données réelles.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Liez le modèle de vue de niveau supérieur.

    ko.applyBindings(storeViewModel);

  5. Lorsque l'objet cartItemsViewModel est disponible, affectez-le à l'espace réservé précédemment défini.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Si vous souhaitez effacer les articles du panier: storeViewModel.cartItemsViewModel(null);

Knockout s'occupera du html - c'est-à-dire qu'il apparaîtra lorsque le modèle n'est pas vide et que le contenu de div (celui avec le "avec liaison") disparaîtra.

Sylwester Gryzio
la source
9

Je dois appeler ko.applyBinding chaque fois que vous cliquez sur le bouton de recherche, et les données filtrées sont renvoyées par le serveur, et dans ce cas, je travaille pour moi sans utiliser ko.cleanNode.

J'ai expérimenté, si nous remplaçons foreach par template, cela devrait fonctionner correctement en cas de collections / observableArray.

Vous pouvez trouver ce scénario utile.

<ul data-bind="template: { name: 'template', foreach: Events }"></ul>

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>
aamir sajjad
la source
1
J'ai passé au moins 4 heures à essayer de résoudre un problème similaire et seule la solution d'Aamir fonctionne pour moi.
Antonin Jelinek
@AntoninJelinek l'autre chose que j'ai expérimentée dans mon scénario, supprimer complètement le html, et l'ajouter dynamiquement à nouveau pour supprimer absolument tout. par exemple, j'ai le conteneur de code Knockout div <div id = "knockoutContainerDiv"> </div> sur $ .Ajax Success result Je fais suivre chaque fois que la méthode serveur appelle $ ("# knockoutContainerDiv"). children.remove (); / / supprimer le contenu de celui-ci appelle la méthode pour ajouter du code HTML dynamique avec le code knockout $ ("# knockoutContainerDiv"). append ("childelements with knockout binding code") et appel à nouveau
applyBinding
1
Hé ammir, j'ai eu à peu près le même scénario que toi. Tout ce que vous avez mentionné fonctionnait très bien sauf le fait que je devais utiliser ko.cleanNode (élément); avant chaque reliure.
Radoslav Minchev
@RadoslavMinchev pensez-vous que je peux vous aider davantage, si oui alors comment, je serai heureux de partager mes pensées / expérience sur une question particulière.
aamir sajjad
THX. @aamirsajjad, je voulais juste mentionner que ce qui a fonctionné pour moi était d'appeler la fonction cleanNode () pour que cela fonctionne.
Radoslav Minchev
6

Au lieu d'utiliser les fonctions internes de KO et de gérer la suppression du gestionnaire d'événements de couverture de JQuery, une bien meilleure idée consiste à utiliser withou les templateliaisons. Lorsque vous faites cela, ko recrée cette partie du DOM et elle est donc automatiquement nettoyée. Ceci est également recommandé, voir ici: https://stackoverflow.com/a/15069509/207661 .

Shital Shah
la source
4

Je pense qu'il serait peut-être préférable de conserver la liaison tout le temps et de simplement mettre à jour les données qui y sont associées. J'ai rencontré ce problème et j'ai constaté que le simple fait d'appeler en utilisant la .resetAll()méthode du tableau dans lequel je conservais mes données était le moyen le plus efficace de le faire.

En gros, vous pouvez commencer avec une variable globale qui contient des données à rendre via le ViewModel:

var myLiveData = ko.observableArray();

Il m'a fallu un certain temps pour réaliser que je ne pouvais pas simplement créer myLiveDataun tableau normal - la ko.oberservableArraypartie était importante.

Ensuite, vous pouvez continuer et faire ce que vous voulez myLiveData. Par exemple, passez un $.getJSONappel:

$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
    myLiveData.removeAll();
    /* parse the JSON data however you want, get it into myLiveData, as below */
    myLiveData.push(data[0].foo);
    myLiveData.push(data[4].bar);
});

Une fois que vous avez fait cela, vous pouvez continuer et appliquer des liaisons en utilisant votre ViewModel comme d'habitude:

function MyViewModel() {
    var self = this;
    self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());

Ensuite, dans le HTML, utilisez simplement myDatacomme vous le feriez normalement.

De cette façon, vous pouvez simplement utiliser myLiveData à partir de n'importe quelle fonction. Par exemple, si vous souhaitez mettre à jour toutes les quelques secondes, enveloppez simplement cette $.getJSONligne dans une fonction et appelez setInterval-la. Vous n'aurez jamais besoin de supprimer la liaison tant que vous vous souviendrez de conserver la myLiveData.removeAll();ligne.

À moins que vos données ne soient vraiment énormes, l'utilisateur ne pourra même pas remarquer le temps entre la réinitialisation du tableau et l'ajout des données les plus récentes.

Zac
la source
Depuis la publication de la question, c'est ce que je fais maintenant. C'est juste que certaines de ces méthodes Knockout sont soit non documentées, soit vous devez vraiment regarder autour de vous (connaissant déjà le nom de la fonction) pour trouver ce qu'elle fait.
awj
J'ai dû chercher très fort pour trouver cela aussi (lorsque le nombre d'heures de fouille dans les documents dépasse le nombre de lignes de code qui en résulte ... wow). Heureux que tu es parvenu à le faire fonctionner.
Zac
1

Avez-vous pensé à cela:

try {
    ko.applyBindings(PersonListViewModel);
}
catch (err) {
    console.log(err.message);
}

J'ai trouvé ça parce que dans Knockout, j'ai trouvé ce code

    var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
    if (!sourceBindings) {
        if (alreadyBound) {
            throw Error("You cannot apply bindings multiple times to the same element.");
        }
        ko.utils.domData.set(node, boundElementDomDataKey, true);
    }

Donc, pour moi, ce n'est pas vraiment un problème qui est déjà lié, c'est que l'erreur n'a pas été détectée et traitée ...

ozzy432836
la source
0

J'ai trouvé que si le modèle de vue contient de nombreuses liaisons div, la meilleure façon de l'effacer ko.applyBindings(new someModelView);est d'utiliser: ko.cleanNode($("body")[0]);Cela vous permet d'appeler un nouveau ko.applyBindings(new someModelView2);dynamiquement sans vous soucier du modèle de vue précédent toujours lié.

Derrick Hampton
la source
4
Il y a quelques points que je voudrais ajouter: (1) cela effacera TOUTES les liaisons de votre page Web, ce qui peut convenir à votre application, mais j'imagine qu'il existe de nombreuses applications où des liaisons ont été ajoutées à plusieurs parties de la page pour des les raisons. Il peut ne pas être utile pour de nombreux utilisateurs de nettoyer TOUTES les liaisons en une seule commande de balayage. (2) Une méthode de récupération JavaScript native plus rapide et plus efficace $("body")[0]est document.body.
awj