Wie lösche / entferne ich beobachtbare Bindungen in Knockout.js?


113

Ich baue Funktionen auf eine Webseite, die der Benutzer mehrmals ausführen kann. Durch die Aktion des Benutzers wird ein Objekt / Modell erstellt und mit ko.applyBindings () auf HTML angewendet.

Der datengebundene HTML-Code wird über jQuery-Vorlagen erstellt.

So weit, ist es gut.

Wenn ich diesen Schritt wiederhole, indem ich ein zweites Objekt / Modell erstelle und ko.applyBindings () aufrufe, treten zwei Probleme auf:

  1. Das Markup zeigt das vorherige Objekt / Modell sowie das neue Objekt / Modell.
  2. Ein Javascript-Fehler tritt in Bezug auf eine der Eigenschaften im Objekt / Modell auf, obwohl er immer noch im Markup gerendert wird.

Um dieses Problem zu umgehen, rufe ich nach dem ersten Durchgang jempty .empty () auf, um das HTML-Vorlagen zu entfernen, das alle Datenbindungsattribute enthält, sodass es nicht mehr im DOM enthalten ist. Wenn der Benutzer den Prozess für den zweiten Durchgang startet, wird der datengebundene HTML-Code erneut zum DOM hinzugefügt.

Aber wie gesagt, wenn der HTML-Code erneut zum DOM hinzugefügt und an das neue Objekt / Modell neu gebunden wird, enthält er immer noch Daten vom ersten Objekt / Modell, und ich erhalte immer noch den JS-Fehler, der nicht auftritt während des ersten Durchgangs.

Die Schlussfolgerung scheint zu sein, dass Knockout an diesen gebundenen Eigenschaften festhält, obwohl das Markup aus dem DOM entfernt wurde.

Ich suche also nach einem Mittel, um diese gebundenen Eigenschaften aus Knockout zu entfernen. Knockout sagen, dass es kein beobachtbares Modell mehr gibt. Gibt es eine Möglichkeit, dies zu tun?

BEARBEITEN

Der grundlegende Prozess besteht darin, dass der Benutzer eine Datei hochlädt. Der Server antwortet dann mit einem JSON-Objekt. Der datengebundene HTML-Code wird dem DOM hinzugefügt. Anschließend wird das JSON-Objektmodell mithilfe von an diesen HTML-Code gebunden

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

Sobald der Benutzer einige Auswahlen für das Modell getroffen hat, wird dasselbe Objekt auf den Server zurückgesendet, der datengebundene HTML-Code wird aus dem DOM entfernt, und ich habe dann das folgende JS

mn.AccountCreationModel = null;

Wenn der Benutzer dies noch einmal tun möchte, werden alle diese Schritte wiederholt.

Ich fürchte, der Code ist zu "involviert", um eine jsFiddle-Demo zu erstellen.


Es wird nicht empfohlen, ko.applyBindings mehrmals aufzurufen, insbesondere für dasselbe enthaltende dom-Element. Es kann einen anderen Weg geben, um das zu erreichen, was Sie wollen. Sie müssen jedoch mehr Code bereitstellen. Bitte fügen Sie nach Möglichkeit eine jsfiddle bei.
Madcapnmckay

Warum nicht eine initFunktion verfügbar machen , in der Sie die anzuwendenden Daten übergeben?
KyorCode

Antworten:


169

Haben Sie versucht, die Clean Node-Methode von Knockout für Ihr DOM-Element aufzurufen, um die im Speicher gebundenen Objekte zu entsorgen?

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

Wenn Sie dann die Knockout-Bindungen mit Ihren neuen Ansichtsmodellen erneut auf genau dieses Element anwenden, wird Ihre Ansichtsbindung aktualisiert.


33
Das funktioniert - danke. Ich kann jedoch keine Dokumentation zu dieser Methode finden.
awj

Ich habe auch nach Dokumentation zu dieser Knockout-Dienstprogrammfunktion gesucht. Soweit ich dem Quellcode deleteentnehmen kann, werden bestimmte Schlüssel für die dom-Elemente selbst aufgerufen, in denen anscheinend die gesamte Knockout-Magie gespeichert ist. Wenn jemand eine Quelle für Dokumentation hat, wäre ich sehr dankbar.
Patrick M

2
Du wirst es nicht finden. Ich habe hoch und niedrig nach Dokumenten zu ko-Dienstprogrammfunktionen gesucht, aber es gibt keine. Dieser Blog-Beitrag ist das Nächste, was Sie finden, aber er behandelt nur Mitglieder von ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels

1
Sie möchten Ereignisse auch manuell entfernen, wie in meiner Antwort unten gezeigt.
Michael Berkompas

1
@KodeKreachor Ich hatte bereits das folgende Arbeitsbeispiel gepostet. Mein Punkt war, dass es besser sein könnte, die Bindung intakt zu halten, während stattdessen die Daten im ViewModel freigegeben werden. Auf diese Weise müssen Sie sich nie mit dem Aufheben der Bindung befassen. Es scheint sauberer zu sein, als undokumentierte Methoden zu verwenden, um die Bindung direkt vom DOM manuell aufzuheben. Darüber hinaus ist CleanNode problematisch, da es keinen der Ereignishandler freigibt (siehe die Antwort hier für weitere Details: stackoverflow.com/questions/15063794/… )
Zac

31

Für ein Projekt, an dem ich arbeite, habe ich eine einfache ko.unapplyBindingsFunktion geschrieben, die einen jQuery-Knoten und den Booleschen Wert zum Entfernen akzeptiert. Zuerst werden alle jQuery-Ereignisse aufgehoben, da sich die ko.cleanNodeMethode nicht darum kümmert. Ich habe auf Speicherlecks getestet und es scheint gut zu funktionieren.

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

Nur eine Einschränkung: Ich habe die erneute Bindung an etwas, das gerade ko.cleanNode()aufgerufen wurde , nicht getestet und nicht das gesamte HTML ersetzt.
Michael Berkompas

4
Würde Ihre Lösung nicht auch alle anderen Ereignisbindungen aufheben? Gibt es eine Möglichkeit, nur die Event-Handler von ko zu entfernen?
Lordvlad

ohne den Ko-Kern zu verändern, der ist
Lordvlad

1
Das stimmt jedoch nicht, soweit ich das beurteilen kann, ohne eine grundlegende Änderung. Siehe diese Ausgabe, die ich hier
angesprochen

Ist die Idee von KO nicht, dass Sie den Dom sehr selten selbst berühren sollten? Diese Antwort durchläuft den Dom und wäre in meinem Anwendungsfall sicherlich alles andere als unbrauchbar.
Blowsie

12

Sie können versuchen, die mit Knockout angebotene Bindung zu verwenden : http://knockoutjs.com/documentation/with-binding.html Die Idee ist, einmalige Bindungsbindungen zu verwenden. Wenn sich Ihre Daten ändern, aktualisieren Sie einfach Ihr Modell.

Nehmen wir an, Sie haben ein Ansichtsmodell der obersten Ebene storeViewModel, Ihren von cartViewModel dargestellten Warenkorb und eine Liste der Artikel in diesem Warenkorb - beispielsweise cartItemsViewModel.

Sie würden das Modell der obersten Ebene - das storeViewModel - an die gesamte Seite binden. Anschließend können Sie die Teile Ihrer Seite trennen, die für den Warenkorb oder die Warenkorbartikel verantwortlich sind.

Nehmen wir an, dass cartItemsViewModel die folgende Struktur hat:

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

Das cartItemsViewModel kann am Anfang leer sein.

Die Schritte würden so aussehen:

  1. Definieren Sie Bindungen in HTML. Trennen Sie die cartItemsViewModel-Bindung.

      
        <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. Das Geschäftsmodell stammt von Ihrem Server (oder wird auf andere Weise erstellt).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Definieren Sie leere Modelle in Ihrem Ansichtsmodell der obersten Ebene. Dann kann eine Struktur dieses Modells mit tatsächlichen Daten aktualisiert werden.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Binden Sie das Ansichtsmodell der obersten Ebene.

    ko.applyBindings(storeViewModel);

  5. Wenn das cartItemsViewModel-Objekt verfügbar ist, weisen Sie es dem zuvor definierten Platzhalter zu.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Wenn Sie die Warenkorbartikel löschen möchten: storeViewModel.cartItemsViewModel(null);

Knockout kümmert sich um HTML - dh es wird angezeigt, wenn das Modell nicht leer ist und der Inhalt von div (der mit dem "mit Bindung") verschwindet.


9

Ich muss ko.applyBinding jedes Mal aufrufen, wenn auf die Schaltfläche klicken, und gefilterte Daten werden vom Server zurückgegeben. In diesem Fall kann ich nach der Arbeit für mich arbeiten, ohne ko.cleanNode zu verwenden.

Ich habe erfahren, wenn wir foreach durch template ersetzen, sollte es bei Sammlungen / ObservableArray gut funktionieren.

Dieses Szenario ist möglicherweise hilfreich.

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

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>

1
Ich habe mindestens 4 Stunden damit verbracht, ein ähnliches Problem zu lösen, und nur die Lösung von aamir funktioniert für mich.
Antonin Jelinek

@AntoninJelinek die andere Sache, die ich in meinem Szenario erlebt habe, das HTML vollständig zu entfernen und es dynamisch erneut anzuhängen, um absolut alles zu entfernen. Zum Beispiel habe ich Knockout-Code-Container div <div id = "knockoutContainerDiv"> </ div> auf $ .Ajax Erfolgsergebnis, das ich jedes Mal verfolge, wenn die Servermethode $ ("# knockoutContainerDiv") aufruft. children.remove (); / / Inhalt entfernen Rufen Sie die Methode auf, um dynamisches HTML mit dem Knockout-Code $ ("# knockoutContainerDiv") anzuhängen. append ("Kinderelemente mit Knockout-Bindungscode") und rufen Sie applyBinding erneut auf
aamir sajjad

1
Hey ammir, ich hatte so ziemlich das gleiche Szenario wie du. Alles, was Sie erwähnt haben, hat großartig funktioniert, außer dass ich ko.cleanNode (Element) verwenden musste. vor jeder erneuten Bindung.
Radoslav Minchev

@RadoslavMinchev Glaubst du, ich kann dir weiter helfen? Wenn ja, wie dann, teile ich gerne meine Gedanken / Erfahrungen zu bestimmten Fragen mit.
Aamir Sajjad

Vielen Dank. @aamirsajjad, wollte nur erwähnen, dass es für mich funktioniert hat, die Funktion cleanNode () aufzurufen, damit es funktioniert.
Radoslav Minchev

6

Statt KO interne Funktionen zu nutzen und mit JQuery der Decke Ereignishandler Entfernung zu tun, eine viel bessere Idee verwendet withoder templateBindungen. Wenn Sie dies tun, erstellt ko diesen Teil des DOM neu und es wird automatisch bereinigt. Dies wird auch empfohlen, siehe hier: https://stackoverflow.com/a/15069509/207661 .


4

Ich denke, es ist vielleicht besser, die Bindung die ganze Zeit beizubehalten und einfach die damit verbundenen Daten zu aktualisieren. Ich bin auf dieses Problem gestoßen und habe festgestellt, dass das Aufrufen der .resetAll()Methode auf dem Array, in dem ich meine Daten gespeichert habe, der effektivste Weg ist, dies zu tun.

Grundsätzlich können Sie mit einer globalen Variable beginnen, die Daten enthält, die über das ViewModel gerendert werden sollen:

var myLiveData = ko.observableArray();

Es dauerte eine Weile, bis mir klar wurde, dass ich nicht einfach myLiveDataein normales Array erstellen konnte - der ko.oberservableArrayTeil war wichtig.

Dann können Sie weitermachen und tun, was Sie wollen myLiveData. Zum Beispiel macht einen $.getJSONAnruf:

$.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);
});

Sobald Sie dies getan haben, können Sie wie gewohnt Bindungen mit Ihrem ViewModel anwenden:

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

Dann im HTML einfach so verwenden, myDatawie Sie es normalerweise tun würden.

Auf diese Weise können Sie einfach mit myLiveData von jeder Funktion aus Mist spielen. Wenn Sie beispielsweise alle paar Sekunden aktualisieren möchten, schließen Sie diese $.getJSONZeile einfach in eine Funktion ein und rufen Sie setIntervalsie auf. Sie müssen die Bindung niemals entfernen, solange Sie daran denken, die myLiveData.removeAll();Linie beizubehalten.

Wenn Ihre Daten nicht wirklich riesig sind, können Benutzer nicht einmal die Zeit zwischen dem Zurücksetzen des Arrays und dem erneuten Hinzufügen der aktuellsten Daten bemerken.


In der Zeit seit dem Posten der Frage mache ich das jetzt. Es ist nur so, dass einige dieser Knockout-Methoden entweder nicht dokumentiert sind oder Sie sich wirklich umschauen müssen (den Funktionsnamen bereits kennen), um herauszufinden, was sie tun.
awj

Ich musste sehr genau hinschauen, um dies auch zu finden (wenn die Anzahl der Stunden, die durch Dokumente gegraben werden, die resultierende Anzahl von Codezeilen überschreitet ... wow). Ich bin froh, dass du es zum Laufen gebracht hast.
Zac


1

Haben Sie darüber nachgedacht:

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

Ich habe mir das ausgedacht, weil ich in Knockout diesen Code gefunden habe

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

Für mich ist es also nicht wirklich ein Problem, das bereits gebunden ist, sondern dass der Fehler nicht abgefangen und behoben wurde ...


0

Ich habe festgestellt, dass, wenn das Ansichtsmodell viele Div-Bindungen enthält, der beste Weg zum Löschen der folgenden ko.applyBindings(new someModelView);ist: ko.cleanNode($("body")[0]);Dies ermöglicht es Ihnen, ein neues ko.applyBindings(new someModelView2);dynamisch aufzurufen , ohne dass das vorherige Ansichtsmodell noch gebunden werden muss.


4
Es gibt ein paar Punkte, die ich hinzufügen möchte: (1) Dadurch werden ALLE Bindungen von Ihrer Webseite gelöscht, was möglicherweise zu Ihrer Anwendung passt. Ich stelle mir jedoch vor, dass es viele Anwendungen gibt, bei denen Bindungen zu mehreren Teilen der Seite getrennt hinzugefügt wurden Gründe dafür. Für viele Benutzer ist es möglicherweise nicht hilfreich, ALLE Bindungen in einem einzigen, umfassenden Befehl zu bereinigen. (2) Eine schnellere, effizientere, native JavaScript-Methode zum Abrufen $("body")[0]ist document.body.
awj
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.