Em meu aplicativo da web, tenho uma lista de usuários em uma tabela à esquerda e um painel de detalhes do usuário à direita. Quando o administrador clica em um usuário na tabela, seus detalhes devem ser exibidos à direita.
Eu tenho um UserListView e UserRowView à esquerda e um UserDetailView à direita. As coisas funcionam, mas tenho um comportamento estranho. Se eu clicar em alguns usuários à esquerda e, em seguida, clicar em excluir em um deles, obtenho caixas de confirmação de javascript sucessivas para todos os usuários que foram exibidos.
Parece que as associações de eventos de todas as visualizações exibidas anteriormente não foram removidas, o que parece ser normal. Não devo fazer um novo UserDetailView todas as vezes no UserRowView? Devo manter uma visualização e alterar seu modelo de referência? Devo controlar a visualização atual e removê-la antes de criar uma nova? Estou meio perdido e qualquer ideia será bem-vinda. Obrigado !
Aqui está o código da visão esquerda (exibição de linha, evento de clique, criação de visão direita)
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()
}
})
E o código para a visualização correta (botão excluir)
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;
}
})
fonte
delete view
no roteador?Sempre destruo e crio visualizações porque, à medida que meu aplicativo de página única fica cada vez maior, seria difícil manter as visualizações ao vivo não utilizadas na memória apenas para poder reutilizá-las.
Aqui está uma versão simplificada de uma técnica que uso para limpar minhas visualizações para evitar vazamentos de memória.
Primeiro crio um BaseView do qual todas as minhas visualizações herdam. A ideia básica é que minha View manterá uma referência a todos os eventos aos quais está inscrita, de modo que, quando for hora de descartar a View, todas essas ligações serão automaticamente desassociadas. Aqui está um exemplo de implementação do meu 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;
Sempre que uma View precisa se vincular a um evento em um modelo ou coleção, eu usaria o método bindTo. Por exemplo:
var SampleView = BaseView.extend({ initialize: function(){ this.bindTo(this.model, 'change', this.render); this.bindTo(this.collection, 'reset', this.doSomething); } });
Sempre que removo uma visualização, apenas chamo o método dispose, que limpará tudo automaticamente:
var sampleView = new SampleView({model: some_model, collection: some_collection}); sampleView.dispose();
Eu compartilhei essa técnica com o pessoal que está escrevendo o ebook "Backbone.js on Rails" e acredito que essa é a técnica que eles adotaram para o livro.
Atualização: 24/03/2014
A partir do Backone 0.9.9, listenTo e stopListening foram adicionados aos eventos usando as mesmas técnicas bindTo e unbindFromAll mostradas acima. Além disso, View.remove chama stopListening automaticamente, então vincular e desvincular é tão fácil quanto isso agora:
var SampleView = BaseView.extend({ initialize: function(){ this.listenTo(this.model, 'change', this.render); } }); var sampleView = new SampleView({model: some_model}); sampleView.remove();
fonte
Esta é uma condição comum. Se você criar uma nova visualização todas as vezes, todas as visualizações antigas ainda estarão vinculadas a todos os eventos. Uma coisa que você pode fazer é criar uma função em sua visualização chamada
detatch
:detatch: function() { $(this.el).unbind(); this.model.unbind();
Então, antes de criar a nova visualização, certifique-se de chamar
detatch
a visualização antiga.Claro, como você mencionou, você sempre pode criar uma visualização de "detalhe" e nunca alterá-la. Você pode vincular ao evento "alterar" no modelo (da vista) para renderizar novamente. Adicione isto ao seu inicializador:
this.model.bind('change', this.render)
Isso fará com que o painel de detalhes seja renderizado novamente CADA vez que uma alteração for feita no modelo. Você pode obter uma granularidade mais precisa observando uma única propriedade: "change: propName".
Obviamente, fazer isso requer um modelo comum ao qual o item View faz referência, bem como a visualização de lista de nível superior e a visualização de detalhes.
Espero que isto ajude!
fonte
this.model.unbind()
é errado para mim porque desassocia todos os eventos deste modelo, incluindo eventos relacionados a outras visualizações do mesmo usuário. Além disso, para chamar adetach
função, preciso manter uma referência estática para a visualização e não gosto disso. Eu suspeito que ainda há algo que eu não entendi ...Para corrigir eventos vinculados várias vezes,
$("#my_app_container").unbind() //Instantiate your views here
Usando a linha acima antes de instanciar as novas visualizações da rota, resolvi o problema que eu tinha com visualizações zumbis.
fonte
Acho que a maioria das pessoas começa com o Backbone criará a visualização como no seu código:
var view = new UserDetailView({model:this.model});
Este código cria uma visão zumbi, porque podemos criar constantemente uma nova visão sem limpar a visão existente. No entanto, não é conveniente chamar view.dispose () para todas as visualizações de backbone em seu aplicativo (especialmente se criarmos visualizações em loop for)
Acho que o melhor momento para colocar o código de limpeza é antes de criar uma nova visualização. Minha solução é criar um ajudante para fazer esta limpeza:
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 o VM para criar sua visão ajudará a limpar qualquer visão existente sem ter que chamar view.dispose (). Você pode fazer uma pequena modificação em seu código de
var view = new UserDetailView({model:this.model});
para
var view = VM.createView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
Portanto, depende de você se deseja reutilizar a visualização em vez de criá-la constantemente, desde que a visualização esteja limpa, você não precisa se preocupar. Basta alterar createView para reuseView:
var view = VM.reuseView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
O código detalhado e a atribuição estão postados em https://github.com/thomasdao/Backbone-View-Manager
fonte
Uma alternativa é vincular, em vez de criar uma série de novas visualizações e, em seguida, desvincular essas visualizações. Você faria isso fazendo 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"); } });
Você definiria o modelo de myView como myViewModel, que seria definido como um modelo de usuário. Dessa forma, se você definir myViewModel para outro usuário (ou seja, alterando seus atributos), ele poderá acionar uma função de renderização na visualização com os novos atributos.
Um problema é que isso quebra o vínculo com o modelo original. Você pode contornar isso usando um objeto de coleção ou definindo o modelo do usuário como um atributo do modelo de visão. Então, ele estaria acessível na visualização como myview.model.get ("model").
fonte
Use este método para limpar as visualizações secundárias e atuais da memória.
//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); } });
fonte