Come cancellare / rimuovere associazioni osservabili in Knockout.js?


113

Sto creando funzionalità su una pagina web che l'utente può eseguire più volte. Attraverso l'azione dell'utente, un oggetto / modello viene creato e applicato all'HTML utilizzando ko.applyBindings ().

L'HTML associato ai dati viene creato tramite modelli jQuery.

Fin qui tutto bene.

Quando ripeto questo passaggio creando un secondo oggetto / modello e chiamo ko.applyBindings () incontro due problemi:

  1. Il markup mostra l'oggetto / modello precedente così come il nuovo oggetto / modello.
  2. Si verifica un errore javascript relativo a una delle proprietà nell'oggetto / modello, sebbene sia ancora visualizzato nel markup.

Per aggirare questo problema, dopo il primo passaggio chiamo .empty () di jQuery per rimuovere l'HTML basato su modelli che contiene tutti gli attributi di data-bind, in modo che non sia più nel DOM. Quando l'utente avvia il processo per il secondo passaggio, l'HTML associato ai dati viene nuovamente aggiunto al DOM.

Ma come ho detto, quando l'HTML viene aggiunto di nuovo al DOM e ricollegato al nuovo oggetto / modello, include ancora i dati dal primo oggetto / modello e ricevo ancora l'errore JS che non si verifica durante il primo passaggio.

La conclusione sembra essere che Knockout mantiene queste proprietà associate, anche se il markup viene rimosso dal DOM.

Quindi quello che sto cercando è un mezzo per rimuovere queste proprietà associate da Knockout; dicendo a knockout che non esiste più un modello osservabile. C'è un modo per fare questo?

MODIFICARE

Il processo di base è che l'utente carica un file; il server risponde quindi con un oggetto JSON, l'HTML associato ai dati viene aggiunto al DOM, quindi il modello di oggetti JSON viene associato a questo HTML utilizzando

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

Una volta che l'utente ha effettuato alcune selezioni sul modello, lo stesso oggetto viene postato di nuovo sul server, l'HTML associato ai dati viene rimosso da allora DOM, e quindi ho il seguente JS

mn.AccountCreationModel = null;

Quando l'utente desidera farlo ancora una volta, tutti questi passaggi vengono ripetuti.

Temo che il codice sia troppo "coinvolto" per fare una demo jsFiddle.


La chiamata a ko.applyBindings più volte, specialmente sullo stesso elemento dom contenente, non è consigliata. Potrebbe esserci un altro modo per ottenere ciò che desideri. Tuttavia, dovrai fornire più codice. Si prega di includere un jsfiddle se possibile.
madcapnmckay

Perché non esporre una initfunzione in cui si passano i dati da applicare?
KyorCode

Risposte:


169

Hai provato a chiamare il metodo del nodo pulito di knockout sul tuo elemento DOM per eliminare gli oggetti associati alla memoria?

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

Quindi applicare nuovamente le associazioni knockout solo su quell'elemento con i nuovi modelli di visualizzazione aggiornerà l'associazione della vista.


33
Funziona, grazie. Tuttavia, non riesco a trovare alcuna documentazione su questo metodo.
awj

Anch'io ho cercato la documentazione su questa funzione di utilità knockout. Per quanto posso dire dal codice sorgente, sta chiamando deletealcune chiavi sugli stessi elementi dom, che a quanto pare è dove è memorizzata tutta la magia knockout. Se qualcuno ha una fonte sulla documentazione, sarei molto grato.
Patrick M

2
Non lo troverai. Ho cercato in alto e in basso per i documenti sulle funzioni di utilità ko, ma nessuno esiste. Questo post del blog è la cosa più vicina che troverai, ma copre solo i membri di ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels,

1
Dovrai anche rimuovere manualmente gli eventi come mostrato nella mia risposta di seguito.
Michael Berkompas

1
@KodeKreachor Avevo già pubblicato l'esempio funzionante di seguito. Il punto era che potrebbe essere meglio mantenere intatta l'associazione, rilasciando invece i dati all'interno del ViewModel. In questo modo non dovrai mai occuparti di un / re-binding. Sembra più pulito rispetto all'utilizzo di metodi non documentati per svincolare manualmente direttamente dal DOM. Oltre a ciò, CleanNode è problematico perché non rilascia nessuno dei gestori di eventi (vedere la risposta qui per maggiori dettagli: stackoverflow.com/questions/15063794/… )
Zac

31

Per un progetto su cui sto lavorando, ho scritto una semplice ko.unapplyBindingsfunzione che accetta un nodo jQuery e il booleano di rimozione. Innanzitutto svincola tutti gli eventi jQuery poiché il ko.cleanNodemetodo non se ne occupa. Ho testato per perdite di memoria e sembra funzionare bene.

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

Solo un avvertimento, non ho testato il re-binding a qualcosa che era appena stato ko.cleanNode()chiamato e non l'intero html sostituito.
Michael Berkompas

4
la tua soluzione non svincolerebbe anche tutte le altre associazioni di eventi? c'è la possibilità di rimuovere solo i gestori di eventi di ko?
lordvlad

senza alterare il ko core che è
lordvlad

1
È vero, tuttavia, per quanto ne so, non è possibile senza un'alterazione fondamentale. Vedi questo problema che ho sollevato qui: github.com/SteveSanderson/knockout/issues/724
Michael Berkompas

Non è l'idea del KO che dovresti toccare molto raramente il dom da solo? Questa risposta passa attraverso il dom e sarebbe certamente tutt'altro che inutilizzabile nel mio caso d'uso.
Blowsie

12

Potresti provare a utilizzare l'associazione che offre knockout: http://knockoutjs.com/documentation/with-binding.html L'idea è di utilizzare le associazioni una volta e ogni volta che i tuoi dati cambiano, aggiorna il tuo modello.

Supponiamo che tu abbia un modello di visualizzazione di primo livello storeViewModel, il tuo carrello rappresentato da cartViewModel e un elenco di articoli in quel carrello, ad esempio cartItemsViewModel.

Assoceresti il ​​modello di primo livello, storeViewModel, all'intera pagina. Quindi, puoi separare le parti della tua pagina che sono responsabili del carrello o degli articoli del carrello.

Supponiamo che cartItemsViewModel abbia la seguente struttura:

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

Il cartItemsViewModel può essere vuoto all'inizio.

I passaggi sarebbero simili a questo:

  1. Definisci le associazioni in html. Separare l'associazione 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. Il modello di negozio proviene dal tuo server (o viene creato in qualsiasi altro modo).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Definisci modelli vuoti sul tuo modello di visualizzazione di primo livello. Quindi una struttura di quel modello può essere aggiornata con i dati effettivi.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Associa il modello di visualizzazione di primo livello.

    ko.applyBindings(storeViewModel);

  5. Quando l'oggetto cartItemsViewModel è disponibile, assegnalo al segnaposto definito in precedenza.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Se desideri cancellare gli articoli del carrello: storeViewModel.cartItemsViewModel(null);

Knockout si occuperà di html - cioè apparirà quando il modello non è vuoto e il contenuto di div (quello con il "with binding") scomparirà.


9

Devo chiamare ko.applyBinding ogni volta che si fa clic sul pulsante di ricerca ei dati filtrati vengono restituiti dal server, e in questo caso seguendo il lavoro per me senza utilizzare ko.cleanNode.

Ho sperimentato, se sostituiamo foreach con un modello, dovrebbe funzionare bene in caso di collezioni / osservableArray.

Potresti trovare utile questo scenario.

<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
Ho trascorso almeno 4 ore cercando di risolvere un problema simile e solo la soluzione di aamir funziona per me.
Antonin Jelinek

@AntoninJelinek l'altra cosa che ho sperimentato nel mio scenario, rimuovere completamente l'html e aggiungerlo di nuovo dinamicamente per rimuovere completamente tutto. per esempio ho il contenitore del codice Knockout div <div id = "knockoutContainerDiv"> </div> su $ .Ajax Risultato di successo che seguo ogni volta che il metodo del server chiama $ ("# knockoutContainerDiv"). children.remove (); / / rimuovi il contenuto chiama il metodo per aggiungere html dinamico con codice knockout $ ("# knockoutContainerDiv"). append ("childelements with knockout binding code") e chiama di nuovo applyBinding
aamir sajjad

1
Ehi ammir, avevo più o meno la tua stessa situazione. Tutto ciò che hai menzionato ha funzionato alla grande tranne il fatto che ho dovuto usare ko.cleanNode (element); prima di ogni nuova rilegatura.
Radoslav Minchev

@RadoslavMinchev pensi di poterti aiutare ulteriormente, se sì, allora come, sarò felice di condividere i miei pensieri / esperienze su una particolare domanda.
aamir sajjad

Grazie. @aamirsajjad, volevo solo accennare al fatto che ciò che ha funzionato per me è stato chiamare la funzione cleanNode () per farlo funzionare.
Radoslav Minchev

6

Invece di utilizzare le funzioni interne di KO e occuparsi della rimozione del gestore di eventi blanket di JQuery, un'idea molto migliore è usare witho le templateassociazioni. Quando si esegue questa operazione, ko ricrea quella parte di DOM e quindi viene automaticamente pulita. Questo è anche un modo consigliato, vedere qui: https://stackoverflow.com/a/15069509/207661 .


4

Penso che potrebbe essere meglio mantenere l'associazione per tutto il tempo e aggiornare semplicemente i dati ad essa associati. Mi sono imbattuto in questo problema e ho scoperto che solo chiamando utilizzando il.resetAll() metodo sull'array in cui stavo mantenendo i miei dati era il modo più efficace per farlo.

Fondamentalmente puoi iniziare con alcune variabili globali che contengono dati da visualizzare tramite ViewModel:

var myLiveData = ko.observableArray();

Mi ci è voluto un po 'per capire che non potevo semplicemente creare myLiveDataun array normale: la ko.oberservableArrayparte era importante.

Quindi puoi andare avanti e fare quello che vuoi myLiveData. Ad esempio, effettua una $.getJSONchiamata:

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

Dopo averlo fatto, puoi andare avanti e applicare le associazioni usando il tuo ViewModel come al solito:

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

Quindi nell'HTML usalo myDatacome faresti normalmente.

In questo modo, puoi semplicemente fare il muck con myLiveData da qualsiasi funzione. Ad esempio, se vuoi aggiornare ogni pochi secondi, racchiudi quella $.getJSONriga in una funzione e chiamala setInterval. Non avrai mai bisogno di rimuovere l'associazione fintanto che ti ricordi di mantenere il filemyLiveData.removeAll(); linea.

A meno che i tuoi dati non siano davvero enormi, l'utente non sarà nemmeno in grado di notare il tempo che intercorre tra il ripristino dell'array e quindi l'aggiunta dei dati più recenti.


Da quando ho postato la domanda, questo è quello che faccio ora. È solo che alcuni di questi metodi Knockout non sono documentati o devi davvero guardarti intorno (conoscendo già il nome della funzione) per trovare quello che fa.
awj

Ho dovuto cercare molto duramente per trovare anche questo (quando il numero di ore a scavare nei documenti supera il numero di righe di codice risultante ... wow). Sono contento che funzioni.
Zac


1

Hai pensato a questo:

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

Ho pensato a questo perché in Knockout ho trovato questo codice

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

Quindi per me non è davvero un problema che sia già vincolato, è che l'errore non è stato individuato e risolto ...


0

Ho scoperto che se il modello di visualizzazione contiene molti collegamenti div, il modo migliore per cancellarli ko.applyBindings(new someModelView);è quello di usarlo: ko.cleanNode($("body")[0]);questo ti consente di chiamare un nuovo ko.applyBindings(new someModelView2);dinamicamente senza la preoccupazione che il modello di visualizzazione precedente sia ancora associato.


4
Ci sono un paio di punti che vorrei aggiungere: (1) questo cancellerà TUTTE le associazioni dalla tua pagina web, il che potrebbe adattarsi alla tua applicazione ma immagino che ci siano molte applicazioni in cui le associazioni sono state aggiunte a più parti della pagina per separare motivi. Potrebbe non essere utile per molti utenti pulire TUTTE le associazioni in un unico comando. (2) una più veloce, più efficiente, il metodo JavaScript nativo di recupero $("body")[0]è document.body.
awj
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.