Jak wyczyścić / usunąć obserwowalne wiązania w Knockout.js?

113

Buduję funkcjonalność na stronie internetowej, którą użytkownik może wykonywać wielokrotnie. W wyniku działania użytkownika obiekt / model jest tworzony i stosowany do HTML za pomocą ko.applyBindings ().

Powiązany z danymi kod HTML jest tworzony za pomocą szablonów jQuery.

Na razie w porządku.

Kiedy powtarzam ten krok, tworząc drugi obiekt / model i wywołując ko.applyBindings (), napotykam dwa problemy:

  1. Znacznik pokazuje poprzedni obiekt / model, a także nowy obiekt / model.
  2. Występuje błąd javascript dotyczący jednej z właściwości w obiekcie / modelu, mimo że jest nadal renderowany w znaczniku.

Aby obejść ten problem, po pierwszym przejściu wywołuję .empty () jQuery, aby usunąć szablon HTML zawierający wszystkie atrybuty wiązania danych, tak aby nie był już w DOM. Gdy użytkownik rozpoczyna proces dla drugiego przebiegu, związany z danymi kod HTML jest ponownie dodawany do DOM.

Ale tak jak powiedziałem, kiedy kod HTML jest ponownie dodawany do DOM i ponownie powiązany z nowym obiektem / modelem, nadal zawiera dane z pierwszego obiektu / modelu i nadal otrzymuję błąd JS, który nie występuje podczas pierwszego przejazdu.

Wniosek wydaje się być taki, że Knockout zachowuje te powiązane właściwości, mimo że znaczniki są usuwane z DOM.

Więc to, czego szukam, to sposób na usunięcie tych powiązanych właściwości z Knockout; mówiąc nokaut, że nie ma już obserwowalnego modelu. Czy jest na to sposób?

EDYTOWAĆ

Podstawowy proces polega na tym, że użytkownik przesyła plik; serwer odpowiada następnie obiektem JSON, kod HTML powiązany z danymi jest dodawany do DOM, a następnie model obiektowy JSON jest powiązany z tym kodem HTML za pomocą

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

Gdy użytkownik dokona pewnych wyborów w modelu, ten sam obiekt jest wysyłany z powrotem na serwer, kod HTML powiązany z danymi jest usuwany z DOM, a następnie mam następujący kod JS

mn.AccountCreationModel = null;

Gdy użytkownik chce to zrobić jeszcze raz, wszystkie te kroki są powtarzane.

Obawiam się, że kod jest zbyt „zaangażowany”, aby wykonać demo jsFiddle.

awj
źródło
Wielokrotne wywoływanie ko.applyBindings, szczególnie na tym samym zawierającym element dom, nie jest zalecane. Może istnieć inny sposób na osiągnięcie tego, czego chcesz. Będziesz jednak musiał podać więcej kodu. Jeśli to możliwe, dołącz plik jsfiddle.
madcapnmckay
Dlaczego nie ujawnia initfunkcji, w której przekazujesz dane do zastosowania?
KyorCode

Odpowiedzi:

169

Czy próbowałeś wywołać metodę czyszczenia węzła knockout w elemencie DOM, aby pozbyć się obiektów związanych z pamięcią?

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

Następnie ponowne zastosowanie powiązań odcinania tylko na tym elemencie z nowymi modelami widoku zaktualizowałoby powiązanie widoku.

KodeKreachor
źródło
33
To działa - dzięki. Nie mogę jednak znaleźć żadnej dokumentacji dotyczącej tej metody.
awj
Ja również szukałem dokumentacji na temat tej funkcji narzędzia knockout. O ile mogę stwierdzić z kodu źródłowego, wywołuje deleteon pewne klucze na samych elementach dom, w których najwyraźniej przechowywana jest cała magia nokautu. Jeśli ktoś ma źródło w dokumentacji, byłbym bardzo zobowiązany.
Patrick M
2
Nie znajdziesz tego. Szukałem wysoko i nisko w dokumentach dotyczących funkcji narzędziowych ko, ale nic nie istnieje. Ten wpis na blogu jest najbliższą rzeczą, jaką znajdziesz, ale obejmuje tylko członków ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels
1
Będziesz także chciał ręcznie usuwać wydarzenia, jak pokazano w mojej odpowiedzi poniżej.
Michael Berkompas
1
@KodeKreachor Poniżej zamieściłem działający przykład. Chodziło mi o to, że lepiej byłoby zachować nienaruszone powiązanie, zamiast tego zwalniając dane w ViewModel. W ten sposób nigdy nie będziesz musiał radzić sobie z odpinaniem / ponownym wiązaniem. Wydaje się, że jest to czystsze niż używanie nieudokumentowanych metod do ręcznego usuwania wiązania bezpośrednio z DOM. Poza tym CleanNode jest problematyczny, ponieważ nie zwalnia żadnego z programów obsługi zdarzeń (zobacz odpowiedź tutaj, aby uzyskać więcej szczegółów: stackoverflow.com/questions/15063794/ ... )
Zac
31

Dla projektu, nad którym pracuję, napisałem prostą ko.unapplyBindingsfunkcję, która akceptuje węzeł jQuery i wartość logiczną remove. Najpierw rozwiązuje wszystkie zdarzenia jQuery, ponieważ ko.cleanNodemetoda o to nie robi. Testowałem pod kątem wycieków pamięci i wydaje się, że działa dobrze.

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
źródło
Tylko jedno zastrzeżenie, nie testowałem ponownego wiązania do czegoś, co właśnie zostało ko.cleanNode()wywołane, a nie zastąpiono całego kodu HTML.
Michael Berkompas
4
czy Twoje rozwiązanie nie rozwiązałoby również wszystkich innych powiązań zdarzeń? czy istnieje możliwość usunięcia tylko programów obsługi zdarzeń ko?
lordvlad
bez zmiany rdzeń, który jest ko
lordvlad
1
To prawda, ale nie jest to możliwe, o ile wiem, bez podstawowej zmiany. Zobacz ten problem, który przywołałem tutaj: github.com/SteveSanderson/knockout/issues/724
Michael Berkompas
Czy to nie pomysł KO, że bardzo rzadko powinieneś sam dotykać dom? Ta odpowiedź przechodzi przez dom i na pewno nie byłaby bezużyteczna w moim przypadku użycia.
Blowsie
12

Możesz spróbować użyć powiązania z wiązaniem, które oferuje knockout: http://knockoutjs.com/documentation/with-binding.html Chodzi o to, aby użyć zastosuj powiązania raz i za każdym razem, gdy zmienią się twoje dane, po prostu zaktualizuj model.

Załóżmy, że masz model widoku najwyższego poziomu storeViewModel, Twój koszyk reprezentowany przez cartViewModel i listę elementów w tym koszyku - powiedzmy cartItemsViewModel.

Powiążesz model najwyższego poziomu - storeViewModel z całą stroną. Następnie możesz oddzielić części strony, które są odpowiedzialne za koszyk lub elementy koszyka.

Załóżmy, że cartItemsViewModel ma następującą strukturę:

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

CartItemsViewModel może być na początku pusty.

Kroki wyglądałyby następująco:

  1. Zdefiniuj wiązania w html. Oddziel powiązanie 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. Model sklepu pochodzi z Twojego serwera (lub jest tworzony w inny sposób).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Zdefiniuj puste modele w modelu widoku najwyższego poziomu. Następnie strukturę tego modelu można zaktualizować o rzeczywiste dane.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Powiąż model widoku najwyższego poziomu.

    ko.applyBindings(storeViewModel);

  5. Gdy obiekt cartItemsViewModel jest dostępny, przypisz go do wcześniej zdefiniowanego symbolu zastępczego.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Jeśli chcesz wyczyścić pozycje z koszyka: storeViewModel.cartItemsViewModel(null);

Knockout zajmie się html - czyli pojawi się, gdy model nie jest pusty, a zawartość div (ta z "z wiązaniem") zniknie.

Sylwester Gryzio
źródło
9

Muszę wywoływać ko.applyBinding za każdym razem, gdy kliknę przycisk wyszukiwania, a przefiltrowane dane wracają z serwera, aw tym przypadku dalsza praca u mnie bez użycia ko.cleanNode.

Doświadczyłem, że jeśli zastąpimy foreach szablonem, to powinno działać dobrze w przypadku collections / observableArray.

Ten scenariusz może być przydatny.

<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
źródło
1
Spędziłem co najmniej 4 godziny próbując rozwiązać podobny problem i działa tylko rozwiązanie Aamira.
Antonin Jelinek
@AntoninJelinek kolejna rzecz, której doświadczyłem w moim scenariuszu, to całkowite usunięcie kodu HTML i dynamiczne dodanie go ponownie, aby całkowicie usunąć wszystko. na przykład mam kontener kodu Knockout div <div id = "knockoutContainerDiv"> </div> na $ .Ajax Wynik sukcesu, który wykonuję za każdym razem, gdy metoda serwera wywołuje $ ("# knockoutContainerDiv"). children.remove (); / / remove the content of it call the method to add the dynamic html with knockout code $ ("# knockoutContainerDiv"). append ("childelements with knockout binding code") i ponownie wywołać applyBinding
aamir sajjad
1
Hej ammir, miałem prawie taki sam scenariusz jak ty. Wszystko, o czym wspomniałeś, działało świetnie, poza faktem, że musiałem użyć ko.cleanNode (element); przed każdym ponownym wiązaniem.
Radoslav Minchev
@RadoslavMinchev czy myślisz, że mogę ci pomóc, jeśli tak, to w jaki sposób, z przyjemnością podzielę się swoimi przemyśleniami / doświadczeniami na temat konkretnego pytania.
aamir sajjad
Dzięki. @aamirsajjad, chciałem tylko wspomnieć, że to, co zadziałało, to wywołanie funkcji cleanNode (), aby działała.
Radoslav Minchev
6

Zamiast używać funkcji wewnętrznych KO i zajmować się usuwaniem modułu obsługi zdarzeń JQuery, znacznie lepszym pomysłem jest użycie withlub templatepowiązania. Gdy to zrobisz, ko odtwarza tę część DOM, dzięki czemu jest ona automatycznie czyszczona. Jest to również zalecany sposób, patrz tutaj: https://stackoverflow.com/a/15069509/207661 .

Shital Shah
źródło
4

Myślę, że lepiej byłoby zachować powiązanie przez cały czas i po prostu zaktualizować powiązane z nim dane. Napotkałem ten problem i stwierdziłem, że po prostu dzwonię przy użyciu.resetAll() metody z tablicy, w której przechowywałem dane, było najefektywniejszym sposobem na zrobienie tego.

Zasadniczo możesz zacząć od globalnej zmiennej, która zawiera dane do renderowania za pośrednictwem ViewModel:

var myLiveData = ko.observableArray();

Zajęło mi trochę czasu, zanim zdałem sobie sprawę, że nie mogę po prostu zrobić myLiveDatazwykłej tablicy - ta ko.oberservableArrayczęść była ważna.

Wtedy możesz iść dalej i robić, co chcesz myLiveData. Na przykład $.getJSONzadzwoń:

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

Gdy to zrobisz, możesz normalnie zastosować wiązania za pomocą swojego ViewModel:

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

Następnie w kodzie HTML użyj myDatatak, jak zwykle.

W ten sposób możesz po prostu pomieszać z myLiveData z dowolnej funkcji. Na przykład, jeśli chcesz aktualizować co kilka sekund, po prostu zawiń tę $.getJSONlinię w funkcję i wywołaj setIntervalją. Nigdy nie będziesz musiał zdejmować wiązania, o ile będziesz pamiętać o utrzymaniu myLiveData.removeAll();żyłki.

O ile twoje dane nie są naprawdę ogromne, użytkownik nie będzie nawet w stanie zauważyć czasu między zresetowaniem macierzy a ponownym dodaniem najbardziej aktualnych danych.

Zac
źródło
Teraz robię to od czasu, gdy zadałem pytanie. Po prostu niektóre z tych metod Knockout są albo nieudokumentowane, albo naprawdę musisz się rozejrzeć (znając już nazwę funkcji), aby znaleźć, co robi.
awj
Musiałem bardzo ciężko szukać, aby to również znaleźć (kiedy liczba godzin przekopywania się przez dokumenty przekracza wynikową liczbę linii kodu ... wow). Cieszę się, że to działa.
Zac
1

Czy myślałeś o tym:

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

Wymyśliłem to, ponieważ w Knockout znalazłem ten kod

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

Więc dla mnie to nie jest tak naprawdę problem, który jest już związany, to że błąd nie został złapany i rozwiązany ...

ozzy432836
źródło
0

Zauważyłem, że jeśli model widoku zawiera wiele powiązań div, najlepszym sposobem na wyczyszczenie ko.applyBindings(new someModelView);jest użycie: ko.cleanNode($("body")[0]);Pozwala to na ko.applyBindings(new someModelView2);dynamiczne wywoływanie nowego bez obawy, że poprzedni model widoku nadal jest powiązany.

Derrick Hampton
źródło
4
Jest kilka punktów, które chciałbym dodać: (1) to usunie WSZYSTKIE powiązania z twojej strony internetowej, co może pasować do twojej aplikacji, ale wyobrażam sobie, że jest wiele aplikacji, w których wiązania zostały dodane do wielu części strony w celu oddzielenia powodów. Dla wielu użytkowników może nie być pomocne wyczyszczenie WSZYSTKICH powiązań jednym, obszernym poleceniem. (2) szybsze, bardziej skuteczny, sposób naturalnej JavaScript wciągania $("body")[0]jest document.body.
awj,