¿Cómo borrar / eliminar enlaces observables en Knockout.js?

113

Estoy construyendo funcionalidad en una página web que el usuario puede realizar varias veces. A través de la acción del usuario, se crea un objeto / modelo y se aplica a HTML usando ko.applyBindings ().

El HTML enlazado a datos se crea a través de plantillas jQuery.

Hasta aquí todo bien.

Cuando repito este paso creando un segundo objeto / modelo y llamo a ko.applyBindings () encuentro dos problemas:

  1. El marcado muestra el objeto / modelo anterior, así como el nuevo objeto / modelo.
  2. Se produce un error de javascript relacionado con una de las propiedades del objeto / modelo, aunque todavía se muestra en el marcado.

Para solucionar este problema, después de la primera pasada, llamo a .empty () de jQuery para eliminar la plantilla HTML que contiene todos los atributos de enlace de datos, de modo que ya no esté en el DOM. Cuando el usuario inicia el proceso para la segunda pasada, el HTML enlazado a datos se vuelve a agregar al DOM.

Pero como dije, cuando el HTML se vuelve a agregar al DOM y se vuelve a vincular al nuevo objeto / modelo, todavía incluye datos del primer objeto / modelo, y todavía obtengo el error JS que no ocurre durante la primera pasada.

La conclusión parece ser que Knockout se aferra a estas propiedades vinculadas, aunque el marcado se elimine del DOM.

Entonces, lo que estoy buscando es un medio para eliminar estas propiedades enlazadas de Knockout; decirle al nocaut que ya no hay un modelo observable. ¿Hay alguna forma de hacer esto?

EDITAR

El proceso básico es que el usuario carga un archivo; el servidor luego responde con un objeto JSON, el HTML enlazado a datos se agrega al DOM, luego el modelo de objeto JSON se enlaza a este HTML usando

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

Una vez que el usuario ha realizado algunas selecciones en el modelo, el mismo objeto se vuelve a enviar al servidor, el HTML enlazado a datos se elimina del DOM, y luego tengo el siguiente JS

mn.AccountCreationModel = null;

Cuando el usuario desea hacer esto una vez más, se repiten todos estos pasos.

Me temo que el código está demasiado "complicado" para hacer una demostración de jsFiddle.

awj
fuente
No se recomienda llamar a ko.applyBindings varias veces, especialmente en el mismo elemento dom contenedor. Puede haber otra forma de lograr lo que desea. Sin embargo, deberá proporcionar más código. Incluya un jsfiddle si es posible.
madcapnmckay
¿Por qué no expone una initfunción en la que se le pasan los datos a aplicar?
KyorCode

Respuestas:

169

¿Ha intentado llamar al método de nodo limpio de knockout en su elemento DOM para deshacerse de los objetos enlazados en memoria?

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

Luego, la aplicación de los enlaces de eliminación de nuevo solo en ese elemento con sus nuevos modelos de vista actualizaría su enlace de vista.

KodeKreachor
fuente
33
Esto funciona, gracias. Sin embargo, no puedo encontrar ninguna documentación sobre este método.
awj
Yo también he buscado documentación sobre esta función de utilidad de eliminación. Por lo que puedo decir por el código fuente, está llamando deletea ciertas teclas en los elementos dom, que aparentemente es donde se almacena toda la magia del knockout. Si alguien tiene una fuente sobre documentación, se lo agradecería mucho.
Patrick M
2
No lo encontrarás. He buscado documentos sobre funciones de utilidad ko, pero no existe ninguno. Esta publicación de blog es lo más parecido que encontrarás, pero solo cubre a miembros de ko.utils: knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
Nick Daniels
1
También querrá eliminar eventos manualmente como se muestra en mi respuesta a continuación.
Michael Berkompas
1
@KodeKreachor Ya había publicado el ejemplo de trabajo a continuación. Mi punto fue que podría ser mejor mantener intacto el enlace, mientras se liberan los datos dentro del ViewModel. De esa manera, nunca tendrá que lidiar con la desunión / reenlace. Parece más limpio que usar métodos indocumentados para desvincular manualmente directamente del DOM. Más allá de eso, CleanNode es problemático porque no libera ninguno de los controladores de eventos (consulte la respuesta aquí para obtener más detalles: stackoverflow.com/questions/15063794/… )
Zac
31

Para un proyecto en el que estoy trabajando, escribí una ko.unapplyBindingsfunción simple que acepta un nodo jQuery y el booleano remove. Primero desvincula todos los eventos de jQuery ya que el ko.cleanNodemétodo no se ocupa de eso. He probado las pérdidas de memoria y parece funcionar 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
fuente
Solo una advertencia, no he probado la re-vinculación a algo que acaba de ko.cleanNode()llamar y no se reemplazó el HTML completo.
Michael Berkompas
4
¿Su solución no desvincularía también todas las demás vinculaciones de eventos? ¿Existe la posibilidad de eliminar solo los controladores de eventos de ko?
lordvlad
sin alterar el núcleo de ko que es
lordvlad
1
Es cierto, sin embargo, hasta donde yo sé, eso no es posible sin una alteración del núcleo. Vea este problema que mencioné
Michael Berkompas
¿No es la idea de KO que rara vez deberías estar tocando el dom? Esta respuesta recorre el dom, y ciertamente estaría lejos de ser inutilizable en mi caso de uso.
Blowsie
12

Puede intentar usar el enlace con que ofrece knockout: http://knockoutjs.com/documentation/with-binding.html La idea es usar aplicar enlaces una vez, y siempre que sus datos cambien, simplemente actualice su modelo.

Supongamos que tiene un modelo de vista de nivel superior storeViewModel, su carrito representado por cartViewModel y una lista de artículos en ese carrito, digamos cartItemsViewModel.

Vincularía el modelo de nivel superior, el storeViewModel a toda la página. Luego, puede separar las partes de su página que son responsables de los artículos del carrito o del carrito.

Supongamos que cartItemsViewModel tiene la siguiente estructura:

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

CartItemsViewModel puede estar vacío al principio.

Los pasos se verían así:

  1. Defina enlaces en html. Separe el enlace 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. El modelo de tienda proviene de su servidor (o se crea de cualquier otra manera).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. Defina modelos vacíos en su modelo de vista de nivel superior. Luego, una estructura de ese modelo se puede actualizar con datos reales.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. Vincular el modelo de vista de nivel superior.

    ko.applyBindings(storeViewModel);

  5. Cuando el objeto cartItemsViewModel esté disponible, asígnelo al marcador de posición definido previamente.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

Si desea limpiar los artículos del carrito: storeViewModel.cartItemsViewModel(null);

Knockout se encargará de html, es decir, aparecerá cuando el modelo no esté vacío y el contenido de div (el que tiene "con enlace") desaparecerá.

Sylwester Gryzio
fuente
9

Tengo que llamar a ko.applyBinding cada vez que hago clic en el botón de búsqueda, y los datos filtrados se devuelven desde el servidor, y en este caso, el siguiente trabajo para mí sin usar ko.cleanNode.

Experimenté, si reemplazamos foreach con template, entonces debería funcionar bien en el caso de colecciones / observableArray.

Puede encontrar útil este escenario.

<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
fuente
1
Pasé al menos 4 horas tratando de resolver un problema similar y solo la solución de aamir funciona para mí.
Antonin Jelinek
@AntoninJelinek, la otra cosa que experimenté en mi escenario, eliminar completamente el html y agregarlo dinámicamente de nuevo para eliminar absolutamente todo. por ejemplo, tengo el contenedor de código Knockout div <div id = "knockoutContainerDiv"> </div> en $ .Ajax Resultado de éxito que sigo cada vez que el método del servidor llama a $ ("# knockoutContainerDiv"). children.remove (); / / eliminar el contenido de la llamada al método para agregar html dinámico con el código de eliminación $ ("# knockoutContainerDiv"). append ("elementos childelements con código de vinculación de eliminación") y llamando a applyBinding nuevamente
aamir sajjad
1
Oye Ammir, tuve prácticamente el mismo escenario que tú. Todo lo que mencionaste funcionó muy bien, excepto el hecho de que tuve que usar ko.cleanNode (elemento); antes de cada reencuadernación.
Radoslav Minchev
@RadoslavMinchev, ¿cree que puedo ayudarlo más? Si es así, ¿cómo ?, estaré feliz de compartir mis pensamientos / experiencia sobre una pregunta en particular.
aamir sajjad
Gracias. @aamirsajjad, solo quería mencionar que lo que funcionó para mí fue llamar a la función cleanNode () para que funcione.
Radoslav Minchev
6

En lugar de usar las funciones internas de KO y lidiar con la eliminación del controlador de eventos general de JQuery, una idea mucho mejor es usar enlaces witho template. Cuando haces esto, ko vuelve a crear esa parte de DOM y así se limpia automáticamente. Esta también es la forma recomendada, consulte aquí: https://stackoverflow.com/a/15069509/207661 .

Shital Shah
fuente
4

Creo que sería mejor mantener el enlace todo el tiempo y simplemente actualizar los datos asociados con él. Me encontré con este problema y descubrí que simplemente llamar usando el .resetAll()método en la matriz en la que estaba guardando mis datos era la forma más efectiva de hacerlo.

Básicamente, puede comenzar con una var global que contiene datos que se procesarán a través de ViewModel:

var myLiveData = ko.observableArray();

Me tomó un tiempo darme cuenta de que no podía simplemente hacer myLiveDatauna matriz normal, la ko.oberservableArrayparte era importante.

Entonces puedes seguir adelante y hacer lo que quieras myLiveData. Por ejemplo, haz una $.getJSONllamada:

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

Una vez que haya hecho esto, puede continuar y aplicar enlaces usando su ViewModel como de costumbre:

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

Luego, en el HTML, use myDatacomo lo haría normalmente.

De esta manera, puede simplemente jugar con myLiveData desde cualquier función. Por ejemplo, si desea actualizar cada pocos segundos, simplemente envuelva esa $.getJSONlínea en una función y llámela setInterval. Nunca necesitará quitar la encuadernación siempre que recuerde mantener la myLiveData.removeAll();línea dentro.

A menos que sus datos sean realmente enormes, el usuario ni siquiera podrá notar el tiempo entre el reinicio de la matriz y luego volver a agregar los datos más recientes.

Zac
fuente
En el tiempo transcurrido desde que publiqué la pregunta, esto es lo que hago ahora. Es solo que algunos de estos métodos Knockout no están documentados o realmente necesitas mirar a tu alrededor (ya sabiendo el nombre de la función) para encontrar lo que hace.
awj
Tuve que buscar mucho para encontrar esto también (cuando la cantidad de horas de búsqueda de documentos excede la cantidad resultante de líneas de código ... wow). Me alegro de que lo hayas hecho funcionar.
Zac
1

Has pensado sobre esto:

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

Se me ocurrió esto porque en Knockout encontré este código

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

Entonces, para mí, no es realmente un problema que ya esté vinculado, es que el error no fue detectado y tratado ...

ozzy432836
fuente
0

Descubrí que si el modelo de vista contiene muchos enlaces div, la mejor manera de borrar el ko.applyBindings(new someModelView);es usar: ko.cleanNode($("body")[0]);Esto le permite llamar a un nuevo ko.applyBindings(new someModelView2);modelo dinámicamente sin la preocupación de que el modelo de vista anterior aún esté enlazado.

Derrick Hampton
fuente
4
Hay un par de puntos que me gustaría agregar: (1) esto borrará TODOS los enlaces de su página web, lo que puede adaptarse a su aplicación, pero imagino que hay muchas aplicaciones en las que se han agregado enlaces a varias partes de la página para separar razones. Puede que a muchos usuarios no les resulte útil limpiar TODOS los enlaces con un solo comando de barrido. (2) un método de recuperación de JavaScript nativo más rápido y eficiente $("body")[0]es document.body.
awj