Je construis 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 code HTML à l'aide de ko.applyBindings ().
Le code HTML lié aux données est créé à l'aide de 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:
Pour résoudre ce problème, après le premier passage, j'appelle .empty () de jQuery pour supprimer le code HTML basé sur un modèle contenant tous les attributs de liaison de données, afin qu'il ne soit plus dans le DOM. Lorsque l'utilisateur lance le processus pour le second passage, le code HTML lié aux données est ajouté de nouveau au DOM.
Mais comme je l'ai dit, lorsque le code HTML est rajouté 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 conserve 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’existe plus de modèle observable. Y a-t-il un moyen de faire cela?
MODIFIER
Le processus de base est que l'utilisateur télécharge un fichier; le serveur répond ensuite avec un objet JSON, le code HTML lié aux données est ajouté au DOM, puis le modèle d'objet JSON est lié à ce code HTML à l'aide de
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 code HTML lié aux données est supprimé de DOM puis, j'ai le fichier JS suivant.
mn.AccountCreationModel = null;
Lorsque l'utilisateur souhaite le faire une fois de plus, toutes ces étapes sont répétées.
Je crains que le code ne soit trop "impliqué" pour faire une démo de jsFiddle.
Avez-vous essayé d'appeler la méthode clean node de knockout sur votre élément DOM pour vous débarrasser des objets liés en mémoire?
var element = $('#elementId')[0];
ko.cleanNode(element);
Ensuite, réappliquer les liaisons défonçables uniquement sur cet élément avec vos nouveaux modèles de vue mettrait à jour votre liaison de vue.
Pour un projet sur lequel je travaille, j’ai écrit une simple fonction ko.unapplyBindings
qui accepte un nœud jQuery et le remove boolean. Tout d'abord, elle désolidarise tous les événements jQuery car la méthode ko.cleanNode
ne s'en occupe pas. J'ai testé les fuites de mémoire et cela semble bien fonctionner.
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]);
}
};
Vous pouvez essayer d’utiliser la liaison avec les offres knock-out suivantes: http://knockoutjs.com/documentation/with-binding.html L’idée est d’utiliser les liaisons d’application une fois, et chaque fois que vos données changent, il suffit de mettre à jour votre modèle.
Disons que vous avez un modèle de vue de niveau magasin storeViewModel, votre panier représenté par cartViewModel et une liste des éléments de ce panier - par exemple, cartItemsViewModel.
Vous lieriez le modèle de niveau supérieur - storeViewModel à la page entière. Ensuite, vous pouvez séparer les parties de votre page responsables du panier ou de ses éléments.
Supposons que cartItemsViewModel présente la structure suivante:
var actualCartItemsModel = { CartItems: [
{ ItemName: "FirstItem", Price: 12 },
{ ItemName: "SecondItem", Price: 10 }
] }
CartItemsViewModel peut être vide au début.
Les étapes ressembleraient à ceci:
Définir 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>
Le modèle de magasin provient de votre serveur (ou est créé de toute autre manière).
var storeViewModel = ko.mapping.fromJS(modelFromServer)
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 les données réelles.
storeViewModel.cartItemsViewModel = ko.observable();
storeViewModel.cartViewModel = ko.observable();
Lier le modèle de vue de niveau supérieur.
ko.applyBindings(storeViewModel);
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 éléments du panier: storeViewModel.cartItemsViewModel(null);
Knockout s’occupe 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 "avec liaison") disparaîtra.
Je dois appeler ko.applyBinding à chaque fois que vous cliquez sur le bouton de recherche et que les données filtrées sont renvoyées par le serveur. Dans ce cas, vous ne pouvez pas utiliser ko.cleanNode.
J'ai expérimenté, si nous remplaçons foreach par template, alors 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>
Au lieu d'utiliser les fonctions internes de KO et de s'occuper de la suppression du gestionnaire d'événements complet de JQuery, une meilleure idée est d'utiliser les liaisons with
ou template
. Lorsque vous faites cela, ko recrée cette partie du DOM et le nettoie automatiquement. Ceci est également recommandé, voir ici: https://stackoverflow.com/a/15069509/207661 .
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 lui sont associées. J'ai rencontré ce problème et découvert que le moyen le plus efficace de le faire consistait à appeler à l'aide de la méthode .resetAll()
sur le tableau dans lequel je conservais mes données.
En gros, vous pouvez commencer avec une variable globale contenant des données à restituer via le ViewModel:
var myLiveData = ko.observableArray();
Il m'a fallu un certain temps pour réaliser que je ne pouvais pas simplement faire myLiveData
un tableau normal - la partie ko.oberservableArray
était importante.
Ensuite, vous pouvez continuer et faire ce que vous voulez myLiveData
. Par exemple, appelez $.getJSON
:
$.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 cela est fait, vous pouvez appliquer des liaisons à l'aide de votre ViewModel comme d'habitude:
function MyViewModel() {
var self = this;
self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());
Ensuite, dans le code HTML, utilisez simplement myData
comme 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, insérez simplement cette ligne $.getJSON
dans une fonction et appelez setInterval
dessus. Vous n'aurez jamais besoin de supprimer la liaison aussi longtemps que vous vous souviendrez de garder la ligne myLiveData.removeAll();
à l'intérieur.
À moins que vos données ne soient vraiment volumineuses, l'utilisateur ne pourra même pas remarquer le temps qui s'écoule entre la réinitialisation du tableau et l'ajout des données les plus récentes.
J'ai récemment eu un problème de fuite de mémoire et ko.cleanNode(element);
ne le ferait pas pour moi -ko.removeNode(element);
l'a fait. Javascript + Knockout.js fuite de mémoire - Comment s'assurer que l'objet est détruit?
Avez-vous pensé à cela:
try {
ko.applyBindings(PersonListViewModel);
}
catch (err) {
console.log(err.message);
}
Je suis venu avec cela 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 soit lié, mais que l'erreur n'a pas été détectée et traitée ...
J'ai constaté que si le modèle d'affichage contient de nombreuses liaisons div, le meilleur moyen d'effacer la ko.applyBindings(new someModelView);
consiste à utiliser: ko.cleanNode($("body")[0]);
Cela vous permet d'appeler une nouvelle ko.applyBindings(new someModelView2);
de manière dynamique sans vous soucier du modèle d'affichage précédent toujours lié.