En mi aplicación web, tengo una lista de usuarios en una tabla a la izquierda y un panel de detalles de usuarios a la derecha. Cuando el administrador hace clic en un usuario en la tabla, sus detalles deben mostrarse a la derecha.
Tengo un UserListView y UserRowView a la izquierda, y un UserDetailView a la derecha. Las cosas funcionan, pero tengo un comportamiento extraño. Si hago clic en algunos usuarios a la izquierda, luego hago clic en eliminar en uno de ellos, obtengo cuadros de confirmación de javascript sucesivos para todos los usuarios que se han mostrado.
Parece que los enlaces de eventos de todas las vistas mostradas anteriormente no se han eliminado, lo que parece ser normal. ¿No debería hacer un nuevo UserDetailView cada vez en UserRowView? ¿Debo mantener una vista y cambiar su modelo de referencia? ¿Debo realizar un seguimiento de la vista actual y eliminarla antes de crear una nueva? Estoy un poco perdido y cualquier idea será bienvenida. Gracias !
Aquí está el código de la vista izquierda (visualización de fila, evento de clic, creación de vista derecha)
window.UserRowView = Backbone.View.extend({
tagName : "tr",
events : {
"click" : "click",
},
render : function() {
$(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
return this;
},
click : function() {
var view = new UserDetailView({model:this.model})
view.render()
}
})
Y el código para la vista derecha (botón eliminar)
window.UserDetailView = Backbone.View.extend({
el : $("#bbBoxUserDetail"),
events : {
"click .delete" : "deleteUser"
},
initialize : function() {
this.model.bind('destroy', function(){this.el.hide()}, this);
},
render : function() {
this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
this.el.show();
},
deleteUser : function() {
if (confirm("Really delete user " + this.model.get("login") + "?"))
this.model.destroy();
return false;
}
})
fuente
delete view
en el enrutador?Siempre destruyo y creo vistas porque a medida que mi aplicación de una sola página se hace cada vez más grande, mantener las vistas en vivo no utilizadas en la memoria solo para poder reutilizarlas sería difícil de mantener.
Aquí hay una versión simplificada de una técnica que utilizo para limpiar mis Vistas y evitar pérdidas de memoria.
Primero creo una BaseView de la que heredan todas mis vistas. La idea básica es que mi Vista mantendrá una referencia a todos los eventos a los que está suscrito, de modo que cuando sea el momento de deshacerse de la Vista, todos esos enlaces se desvincularán automáticamente. Aquí hay una implementación de ejemplo de mi BaseView:
var BaseView = function (options) { this.bindings = []; Backbone.View.apply(this, [options]); }; _.extend(BaseView.prototype, Backbone.View.prototype, { bindTo: function (model, ev, callback) { model.bind(ev, callback, this); this.bindings.push({ model: model, ev: ev, callback: callback }); }, unbindFromAll: function () { _.each(this.bindings, function (binding) { binding.model.unbind(binding.ev, binding.callback); }); this.bindings = []; }, dispose: function () { this.unbindFromAll(); // Will unbind all events this view has bound to this.unbind(); // This will unbind all listeners to events from // this view. This is probably not necessary // because this view will be garbage collected. this.remove(); // Uses the default Backbone.View.remove() method which // removes this.el from the DOM and removes DOM events. } }); BaseView.extend = Backbone.View.extend;
Siempre que una vista necesita vincularse a un evento en un modelo o colección, usaría el método bindTo. Por ejemplo:
var SampleView = BaseView.extend({ initialize: function(){ this.bindTo(this.model, 'change', this.render); this.bindTo(this.collection, 'reset', this.doSomething); } });
Siempre que elimino una vista, simplemente llamo al método de disposición que limpiará todo automáticamente:
var sampleView = new SampleView({model: some_model, collection: some_collection}); sampleView.dispose();
Compartí esta técnica con las personas que están escribiendo el libro electrónico "Backbone.js on Rails" y creo que esta es la técnica que han adoptado para el libro.
Actualización: 2014-03-24
A partir de Backone 0.9.9, listenTo y stopListening se agregaron a los eventos usando las mismas técnicas bindTo y unbindFromAll que se muestran arriba. Además, View.remove llama a stopListening automáticamente, por lo que vincular y desvincular es tan fácil como esto ahora:
var SampleView = BaseView.extend({ initialize: function(){ this.listenTo(this.model, 'change', this.render); } }); var sampleView = new SampleView({model: some_model}); sampleView.remove();
fuente
Ésta es una condición común. Si crea una nueva vista cada vez, todas las vistas antiguas seguirán vinculadas a todos los eventos. Una cosa que puede hacer es crear una función en su vista llamada
detatch
:detatch: function() { $(this.el).unbind(); this.model.unbind();
Luego, antes de crear la nueva vista, asegúrese de llamar
detatch
a la vista anterior.Por supuesto, como mencionaste, siempre puedes crear una vista de "detalle" y nunca cambiarla. Puede vincularse al evento "cambiar" en el modelo (desde la vista) para volver a renderizarlo usted mismo. Agregue esto a su inicializador:
this.model.bind('change', this.render)
Hacer eso hará que el panel de detalles se vuelva a renderizar CADA vez que se realice un cambio en el modelo. Puede obtener una granularidad más fina observando una sola propiedad: "change: propName".
Por supuesto, hacer esto requiere un modelo común al que tenga referencia la Vista de elementos, así como la vista de lista de nivel superior y la vista de detalles.
¡Espero que esto ayude!
fuente
this.model.unbind()
es correcto para mí porque desvincula todos los eventos de este modelo, incluidos los eventos relacionados con otras vistas del mismo usuario. Además, para llamar a ladetach
función, necesito mantener una referencia estática a la vista, y no me gusta. Sospecho que todavía hay algo que no he entendido ...Para arreglar eventos vinculantes varias veces,
$("#my_app_container").unbind() //Instantiate your views here
Usando la línea anterior antes de crear una instancia de las nuevas Vistas desde la ruta, resolvió el problema que tenía con las vistas de zombies.
fuente
Creo que la mayoría de las personas que comienzan con Backbone crearán la vista como en su código:
var view = new UserDetailView({model:this.model});
Este código crea una vista zombie, porque es posible que creemos constantemente una nueva vista sin limpiar la vista existente. Sin embargo, no es conveniente llamar a view.dispose () para todas las vistas troncales en su aplicación (especialmente si creamos vistas en bucle for)
Creo que el mejor momento para poner el código de limpieza es antes de crear una nueva vista. Mi solución es crear un ayudante para hacer esta limpieza:
window.VM = window.VM || {}; VM.views = VM.views || {}; VM.createView = function(name, callback) { if (typeof VM.views[name] !== 'undefined') { // Cleanup view // Remove all of the view's delegated events VM.views[name].undelegateEvents(); // Remove view from the DOM VM.views[name].remove(); // Removes all callbacks on view VM.views[name].off(); if (typeof VM.views[name].close === 'function') { VM.views[name].close(); } } VM.views[name] = callback(); return VM.views[name]; } VM.reuseView = function(name, callback) { if (typeof VM.views[name] !== 'undefined') { return VM.views[name]; } VM.views[name] = callback(); return VM.views[name]; }
Usar VM para crear su vista ayudará a limpiar cualquier vista existente sin tener que llamar a view.dispose (). Puede hacer una pequeña modificación a su código desde
var view = new UserDetailView({model:this.model});
a
var view = VM.createView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
Por lo tanto, depende de usted si desea reutilizar la vista en lugar de crearla constantemente, siempre que la vista esté limpia, no debe preocuparse. Simplemente cambie createView para reuseView:
var view = VM.reuseView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
El código detallado y la atribución se publican en https://github.com/thomasdao/Backbone-View-Manager
fuente
Una alternativa es vincular, en lugar de crear una serie de vistas nuevas y luego desvincular esas vistas. Lograrías esto haciendo algo como:
window.User = Backbone.Model.extend({ }); window.MyViewModel = Backbone.Model.extend({ }); window.myView = Backbone.View.extend({ initialize: function(){ this.model.on('change', this.alert, this); }, alert: function(){ alert("changed"); } });
Establecería el modelo de myView en myViewModel, que se establecería en un modelo de usuario. De esta manera, si configura myViewModel a otro usuario (es decir, si cambia sus atributos), podría activar una función de renderizado en la vista con los nuevos atributos.
Un problema es que esto rompe el vínculo con el modelo original. Puede evitar esto utilizando un objeto de colección o configurando el modelo de usuario como un atributo del modelo de vista. Entonces, esto sería accesible en la vista como myview.model.get ("modelo").
fuente
Utilice este método para borrar las vistas secundarias y las vistas actuales de la memoria.
//FIRST EXTEND THE BACKBONE VIEW.... //Extending the backbone view... Backbone.View.prototype.destroy_view = function() { //for doing something before closing..... if (this.beforeClose) { this.beforeClose(); } //For destroying the related child views... if (this.destroyChild) { this.destroyChild(); } this.undelegateEvents(); $(this.el).removeData().unbind(); //Remove view from DOM this.remove(); Backbone.View.prototype.remove.call(this); } //Function for destroying the child views... Backbone.View.prototype.destroyChild = function(){ console.info("Closing the child views..."); //Remember to push the child views of a parent view using this.childViews if(this.childViews){ var len = this.childViews.length; for(var i=0; i<len; i++){ this.childViews[i].destroy_view(); } }//End of if statement } //End of destroyChild function //Now extending the Router .. var Test_Routers = Backbone.Router.extend({ //Always call this function before calling a route call function... closePreviousViews: function() { console.log("Closing the pervious in memory views..."); if (this.currentView) this.currentView.destroy_view(); }, routes:{ "test" : "testRoute" }, testRoute: function(){ //Always call this method before calling the route.. this.closePreviousViews(); ..... } //Now calling the views... $(document).ready(function(e) { var Router = new Test_Routers(); Backbone.history.start({root: "/"}); }); //Now showing how to push child views in parent views and setting of current views... var Test_View = Backbone.View.extend({ initialize:function(){ //Now setting the current view.. Router.currentView = this; //If your views contains child views then first initialize... this.childViews = []; //Now push any child views you create in this parent view. //It will automatically get deleted //this.childViews.push(childView); } });
fuente