W mojej aplikacji internetowej mam listę użytkowników w tabeli po lewej stronie i okienko szczegółów użytkownika po prawej stronie. Kiedy administrator kliknie użytkownika w tabeli, jego szczegóły powinny zostać wyświetlone po prawej stronie.
Mam UserListView i UserRowView po lewej stronie i UserDetailView po prawej stronie. Niby wszystko działa, ale mam dziwne zachowanie. Jeśli kliknę niektórych użytkowników po lewej stronie, a następnie kliknę usuń na jednym z nich, otrzymam kolejne pola potwierdzenia javascript dla wszystkich wyświetlonych użytkowników.
Wygląda na to, że powiązania zdarzeń wszystkich wcześniej wyświetlanych widoków nie zostały usunięte, co wydaje się normalne. Nie powinienem za każdym razem tworzyć nowego UserDetailView na UserRowView? Czy powinienem zachować widok i zmienić jego model referencyjny? Czy powinienem śledzić bieżący widok i usunąć go przed utworzeniem nowego? Jestem trochę zagubiony i każdy pomysł będzie mile widziany. Dziękuję Ci !
Oto kod lewego widoku (wyświetlanie wierszy, kliknięcie, tworzenie prawego widoku)
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()
}
})
I kod do prawego widoku (przycisk usuwania)
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;
}
})
źródło
delete view
w routerze?Zawsze niszczę i tworzę widoki, ponieważ w miarę jak moja pojedyncza aplikacja strony staje się coraz większa, utrzymanie w pamięci nieużywanych widoków na żywo tylko po to, aby móc ich ponownie użyć, byłoby trudne do utrzymania.
Oto uproszczona wersja techniki, której używam do czyszczenia widoków, aby uniknąć wycieków pamięci.
Najpierw tworzę BaseView, z którego dziedziczą wszystkie moje widoki. Podstawową ideą jest to, że mój widok zachowa odniesienie do wszystkich zdarzeń, do których jest zasubskrybowany, więc kiedy nadejdzie czas na usunięcie widoku, wszystkie te powiązania zostaną automatycznie niezwiązane. Oto przykładowa implementacja mojego 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;
Ilekroć widok musi powiązać się ze zdarzeniem w modelu lub kolekcji, użyłbym metody bindTo. Na przykład:
var SampleView = BaseView.extend({ initialize: function(){ this.bindTo(this.model, 'change', this.render); this.bindTo(this.collection, 'reset', this.doSomething); } });
Ilekroć usuwam widok, po prostu wywołuję metodę dispose, która wyczyści wszystko automatycznie:
var sampleView = new SampleView({model: some_model, collection: some_collection}); sampleView.dispose();
Podzieliłem się tą techniką z ludźmi, którzy piszą ebook „Backbone.js on Rails” i uważam, że jest to technika, którą przyjęli w tej książce.
Aktualizacja: 2014-03-24
Począwszy od Backone 0.9.9, ListenTo i stopListening zostały dodane do Events przy użyciu tych samych technik bindTo i unbindFromAll pokazanych powyżej. Ponadto View.remove wywołuje stopListening automatycznie, więc wiązanie i odłączanie jest teraz tak proste:
var SampleView = BaseView.extend({ initialize: function(){ this.listenTo(this.model, 'change', this.render); } }); var sampleView = new SampleView({model: some_model}); sampleView.remove();
źródło
To powszechny stan. Jeśli za każdym razem utworzysz nowy widok, wszystkie stare widoki będą nadal powiązane ze wszystkimi wydarzeniami. Jedną z rzeczy, które możesz zrobić, jest utworzenie w swoim widoku funkcji o nazwie
detatch
:detatch: function() { $(this.el).unbind(); this.model.unbind();
Następnie, zanim utworzysz nowy widok, wywołaj
detatch
stary widok.Oczywiście, jak wspomniałeś, zawsze możesz utworzyć jeden widok „szczegółów” i nigdy go nie zmieniać. Możesz powiązać się ze zdarzeniem „zmiana” w modelu (z widoku), aby ponownie renderować siebie. Dodaj to do swojego inicjatora:
this.model.bind('change', this.render)
Spowoduje to ponowne renderowanie okienka szczegółów za KAŻDĄ zmianą w modelu. Możesz uzyskać większą szczegółowość, obserwując pojedynczą właściwość: „change: propName”.
Oczywiście wykonanie tego wymaga wspólnego modelu, do którego odwołuje się widok elementu, a także widoku listy wyższego poziomu i widoku szczegółów.
Mam nadzieję że to pomoże!
źródło
this.model.unbind()
jest źle dla mnie, ponieważ rozwiązuje wszystkie zdarzenia z tego modelu, w tym zdarzenia dotyczące innych widoków tego samego użytkownika. Ponadto, aby wywołaćdetach
funkcję, muszę zachować statyczne odniesienie do widoku, a całkiem mi się to nie podoba. Podejrzewam, że wciąż jest coś, czego nieAby naprawić zdarzenia związane wielokrotnie,
$("#my_app_container").unbind() //Instantiate your views here
Użycie powyższej linii przed utworzeniem nowych widoków z trasy rozwiązało problem, który miałem z widokami zombie.
źródło
Myślę, że większość ludzi zaczyna od Backbone stworzy widok jak w Twoim kodzie:
var view = new UserDetailView({model:this.model});
Ten kod tworzy widok zombie, ponieważ możemy stale tworzyć nowy widok bez czyszczenia istniejącego widoku. Jednak wywołanie metody view.dispose () dla wszystkich widoków szkieletu w Twojej aplikacji nie jest wygodne (zwłaszcza jeśli tworzymy widoki w pętli for)
Myślę, że najlepszym momentem na umieszczenie kodu porządkującego jest przed utworzeniem nowego widoku. Moim rozwiązaniem jest utworzenie pomocnika do wykonania tego czyszczenia:
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]; }
Używanie maszyny wirtualnej do tworzenia widoku pomoże wyczyścić istniejący widok bez konieczności wywoływania metody view.dispose (). Możesz dokonać niewielkiej modyfikacji swojego kodu z
var view = new UserDetailView({model:this.model});
do
var view = VM.createView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
Więc to od Ciebie zależy, czy chcesz ponownie używać widoku zamiast go ciągle tworzyć, o ile widok jest czysty, nie musisz się martwić. Po prostu zmień createView na reuseView:
var view = VM.reuseView("unique_view_name", function() { return new UserDetailView({model:this.model}); });
Szczegółowy kod i atrybuty są zamieszczone na https://github.com/thomasdao/Backbone-View-Manager
źródło
Jedną z alternatyw jest wiązanie, a nie tworzenie serii nowych widoków, a następnie usuwanie tych widoków. Osiągnąłbyś to, robiąc coś takiego:
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"); } });
Należy ustawić model myView na myViewModel, który zostałby ustawiony na model użytkownika. W ten sposób, jeśli ustawisz myViewModel na innego użytkownika (tj. Zmieniając jego atrybuty), może to wyzwolić funkcję renderującą w widoku z nowymi atrybutami.
Jednym z problemów jest to, że powoduje to zerwanie łącza do oryginalnego modelu. Można to obejść, używając obiektu kolekcji lub ustawiając model użytkownika jako atrybut modelu widoku. Wtedy byłoby to dostępne w widoku jako myview.model.get („model”).
źródło
Użyj tej metody, aby wyczyścić widoki potomne i bieżące widoki z pamięci.
//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); } });
źródło