Kręgosłup.js: ponownie zaludnić czy odtworzyć widok?

W mojej aplikacji internetowej mam listę użytkowników w tabeli po lewej stronie, a panel szczegółów użytkownika po prawej. Gdy administrator kliknie użytkownika w tabeli, jego dane powinny być wyświetlane po prawej stronie.

Mam UserListView i UserRowView po lewej, a UserDetailView po prawej. Wszystko działa, ale mam dziwne zachowanie. Jeśli kliknę niektórych użytkowników po lewej stronie, a następnie kliknij przycisk Usuń na jednym z nich, otrzymuję kolejne pola potwierdzające javascript dla wszystkich użytkowników, którzy zostali wyświetleni.

Wygląda na to, że powiązania zdarzeń wszystkich wcześniej wyświetlanych widoków nie zostały usunięte, co wydaje się być normalne. Nie powinienem robić nowego UserDetailView za każdym razem na UserRowView? Czy powinienem zachować widok I zmienić jego model odniesienia? 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ę !

Oto kod widoku po lewej stronie (wyświetlanie wiersza, kliknięcie zdarzenia, Widok po prawej creation)

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 prawego widoku (przycisk delete)

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;
    }
})
Author: tereško, 2011-09-27

7 answers

Niedawno pisałem o tym na blogu i pokazałem kilka rzeczy, które robię w moich aplikacjach, aby poradzić sobie z tymi scenariuszami:

Http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

 27
Author: Derick Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-27 13:49:54

Zawsze niszczę i tworzę widoki, ponieważ w miarę jak moja pojedyncza aplikacja staje się coraz większa, utrzymywanie nieużywanych widoków na żywo w pamięci, aby móc je ponownie wykorzystać, staje się trudne do utrzymania.

Oto uproszczona wersja techniki, której używam do czyszczenia moich widoków, aby uniknąć wycieków pamięci.

Najpierw tworzę przegląd bazowy, z którego wszystkie moje widoki dziedziczą. Podstawową ideą jest to, że mój pogląd będzie zachowywał odniesienie do wszystkich wydarzeń, do których jest zapisany, tak, że gdy nadszedł czas, aby pozbyć się widoku, wszystkie te wiązania zostaną automatycznie uwolnione. Oto przykładowa implementacja My 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;

Gdy Widok musi wiązać się ze zdarzeniem w modelu lub kolekcji, używam 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ą " kręgosłup.js on Rails " ebook i ja wierzę, że to jest technika, którą przyjęli do książki.

Aktualizacja: 2014-03-24

Od wersji Backone 0.9.9, listenTo i stopListening zostały dodane do zdarzeń przy użyciu tych samych technik bindTo i unbindFromAll pokazanych powyżej. Zobacz Też:Usuń połączenia zatrzymujące się automatycznie, więc wiązanie i rozłączanie jest tak proste jak teraz:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();
 135
Author: Johnny Oshika,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-24 22:45:24

To częsta choroba. Jeśli za każdym razem tworzysz nowy widok, wszystkie stare widoki będą nadal powiązane ze wszystkimi zdarzeniami. Jedną z rzeczy, które możesz zrobić, to utworzyć funkcję w widoku o nazwie detatch:

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

Następnie, przed utworzeniem nowego widoku, upewnij się, że wywołujesz detatch W starym widoku.

Oczywiście, jak wspomniałeś, zawsze możesz utworzyć jeden widok "szczegółów" i nigdy go nie zmieniać. Możesz powiązać Zdarzenie "zmień" w modelu (z widoku), aby ponownie renderować siebie. Dodaj to do Twój inicjalizator:

this.model.bind('change', this.render)

Spowoduje to ponowne renderowanie panelu szczegółów za każdym razem, gdy zostanie wprowadzona zmiana w modelu. Możesz uzyskać drobniejszą szczegółowość obserwując pojedynczą właściwość: "change: propName".

Oczywiście wymaga to wspólnego modelu, do którego odnosi się widok pozycji, a także widoku listy wyższego poziomu i widoku szczegółów.

Mam nadzieję, że to pomoże!
 8
Author: Brian Genisio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-27 10:32:42

Aby naprawić wielokrotne powiązanie zdarzeń,

$("#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.

 6
Author: Ashan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-09 18:01:24

Myślę, że większość ludzi zaczyna od szkieletu stworzy widok tak 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 widoku nie jest wygodne.dispose () dla wszystkich widoków szkieletowych w aplikacji (zwłaszcza jeśli tworzymy widoki w pętli for)

Myślę, że najlepszym momentem, aby umieścić kod oczyszczający jest przed utworzeniem nowego widoku. Moim rozwiązaniem jest stworzenie helpera do tego czyszczenie:

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życie maszyny Wirtualnej do utworzenia widoku pomoże oczyścić istniejący widok bez konieczności wywoływania widoku.dispose (). Możesz zrobić małą modyfikację 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żyć widoku zamiast stale go tworzyć, tak długo, jak 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 przypis zamieszczono na https://github.com/thomasdao/Backbone-View-Manager

 2
Author: thomasdao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-25 10:15:59

Jedną z alternatyw jest bindowanie, w przeciwieństwie do tworzenia serii nowych widoków, a następnie ich odwiązywania. Można to osiągnąć robiąc coś w stylu:

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

Możesz ustawić model myView na myViewModel, który będzie ustawiony na model użytkownika. W ten sposób, Jeśli ustawisz myViewModel innemu użytkownikowi (np. zmieniając jego atrybuty), może on wywołać funkcję renderowania w widoku z nowymi atrybutami.

Jeden problem polega na tym, że powoduje to zerwanie połączenia z oryginalnym modelem. Ty można to obejść, używając obiektu collection lub ustawiając model użytkownika jako atrybut viewmodel. Wtedy będzie to dostępne w widoku jako myview.model.get("model").

 0
Author: bento,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-31 22:43:20

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);
       }
  });
 0
Author: Robins Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-05 16:29:10