webentwicklung-frage-antwort-db.com.de

Backbone.js: die Ansicht neu füllen oder neu erstellen?

In meiner Webanwendung gibt es links eine Benutzerliste in einer Tabelle und rechts einen Benutzerdetailbereich. Wenn der Administrator auf einen Benutzer in der Tabelle klickt, sollten die Details rechts angezeigt werden.

Ich habe eine UserListView und UserRowView auf der linken Seite und eine UserDetailView auf der rechten Seite. Die Dinge funktionieren, aber ich benehme mich komisch. Wenn ich links auf einige Benutzer klicke und dann auf einen von ihnen auf "Löschen" klicke, werden nacheinander Javascript-Bestätigungsfelder für alle angezeigten Benutzer angezeigt.

Es sieht so aus, als ob die Ereignisbindungen aller zuvor angezeigten Ansichten nicht entfernt wurden, was normal zu sein scheint. Ich sollte nicht jedes Mal eine neue UserDetailView in UserRowView erstellen. Soll ich eine Ansicht beibehalten und das Referenzmodell ändern? Sollte ich die aktuelle Ansicht verfolgen und entfernen, bevor ich eine neue erstelle? Ich bin irgendwie verloren und jede Idee ist willkommen. Vielen Dank !

Hier ist der Code der linken Ansicht (Zeilendarstellung, Klickereignis, Rechtsansichtserstellung)

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

Und der Code für die rechte Ansicht (Löschen-Button)

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

Ich habe kürzlich darüber gebloggt und verschiedene Dinge gezeigt, die ich in meinen Apps mache, um mit diesen Szenarien umzugehen:

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

27
Derick Bailey

Ich zerstöre und erstelle immer Ansichten, da meine App für einzelne Seiten immer größer wird und es schwierig wird, nicht verwendete Live-Ansichten im Speicher zu behalten, damit ich sie wiederverwenden kann.

Hier ist eine vereinfachte Version einer Technik, mit der ich meine Ansichten bereinige, um Speicherverluste zu vermeiden.

Ich erstelle zuerst eine BaseView, von der alle meine Ansichten erben. Die Grundidee ist, dass meine Ansicht einen Verweis auf alle Ereignisse enthält, für die sie abonniert ist, sodass alle diese Bindungen automatisch aufgehoben werden, wenn die Ansicht verfügbar ist. Hier ist eine Beispielimplementierung von 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;

Immer wenn eine Ansicht an ein Ereignis in einem Modell oder einer Sammlung gebunden werden muss, verwende ich die bindTo-Methode. Beispielsweise:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

Immer wenn ich eine Ansicht entferne, rufe ich einfach die dispose-Methode auf, die alles automatisch aufräumt:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

Ich habe diese Technik mit den Leuten geteilt, die das E-Book "Backbone.js on Rails" schreiben, und ich glaube, das ist die Technik, die sie für das Buch übernommen haben.

Aktualisierung: 24.03.2014

Ab Backone 0.9.9 wurden listenTo und stopListening Events mit denselben Techniken wie bindTo und unbindFromAll hinzugefügt, die oben gezeigt wurden. Außerdem ruft View.remove stopListening automatisch auf, sodass das Binden und Aufheben der Bindung jetzt so einfach ist:

var SampleView = BaseView.extend({

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

var sampleView = new SampleView({model: some_model});
sampleView.remove();
136
Johnny Oshika

Dies ist eine häufige Erkrankung. Wenn Sie jedes Mal eine neue Ansicht erstellen, bleiben alle alten Ansichten an alle Ereignisse gebunden. Sie können in Ihrer Ansicht eine Funktion namens detatch erstellen:

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

Bevor Sie die neue Ansicht erstellen, müssen Sie in der alten Ansicht detatch aufrufen.

Natürlich können Sie, wie Sie bereits erwähnt haben, immer eine "Detailansicht" erstellen und diese niemals ändern. Sie können an das "change" -Ereignis des Modells (aus der Ansicht) binden, um sich selbst neu zu rendern. Fügen Sie dies zu Ihrem Initialisierer hinzu:

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

Wenn Sie dies tun, wird der Detailbereich JEDES Mal, wenn Änderungen am Modell vorgenommen werden, neu gerendert. Sie können eine feinere Granularität erzielen, indem Sie auf eine einzelne Eigenschaft achten: "change: propName".

Dies setzt natürlich ein allgemeines Modell voraus, auf das sich die Elementansicht sowie die Listenansicht und die Detailansicht auf höherer Ebene beziehen.

Hoffe das hilft!

8
Brian Genisio

So beheben Sie Ereignisse, die mehrmals gebunden werden:

$("#my_app_container").unbind()
//Instantiate your views here

Die Verwendung der obigen Zeile vor dem Instanziieren der neuen Ansichten von der Route löste das Problem, das ich mit Zombie-Ansichten hatte.

6
Ashan

Ich denke, die meisten Leute, die mit Backbone anfangen, werden die Ansicht wie in Ihrem Code erstellen:

var view = new UserDetailView({model:this.model});

Dieser Code erstellt eine Zombie-Ansicht, da wir möglicherweise ständig eine neue Ansicht erstellen, ohne die vorhandene Ansicht zu bereinigen. Es ist jedoch nicht praktisch, view.dispose () für alle Backbone-Ansichten in Ihrer App aufzurufen (insbesondere, wenn wir Ansichten in der for-Schleife erstellen).

Ich denke, das beste Timing, um Bereinigungscode zu setzen, ist vor dem Erstellen einer neuen Ansicht. Meine Lösung besteht darin, einen Helfer für diese Bereinigung zu erstellen:

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

Mit VM zum Erstellen Ihrer Ansicht können Sie vorhandene Ansichten bereinigen, ohne view.dispose () aufrufen zu müssen. Mit können Sie eine kleine Änderung an Ihrem Code vornehmen

var view = new UserDetailView({model:this.model});

zu

var view = VM.createView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

Sie müssen sich also keine Sorgen machen, wenn Sie die Ansicht wiederverwenden möchten, anstatt sie ständig zu erstellen, solange sie sauber ist. Ändern Sie einfach createView in reuseView:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

Detaillierter Code und Namensnennung finden Sie unter https://github.com/thomasdao/Backbone-View-Manager

2
thomasdao

Eine Alternative besteht darin, zu binden, anstatt eine Reihe neuer Ansichten zu erstellen und diese Ansichten dann zu entbinden. Sie würden dies folgendermaßen erreichen:

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

Sie würden das Modell von myView auf myViewModel setzen, was einem Benutzermodell entsprechen würde. Auf diese Weise kann myViewModel, wenn Sie einen anderen Benutzer angeben (d. H. Seine Attribute ändern), eine Renderfunktion in der Ansicht mit den neuen Attributen auslösen.

Ein Problem ist, dass dadurch die Verknüpfung zum ursprünglichen Modell unterbrochen wird. Sie können dies umgehen, indem Sie entweder ein Auflistungsobjekt verwenden oder das Benutzermodell als Attribut des Ansichtsmodells festlegen. Dann wäre dies in der Ansicht als myview.model.get ("model") zugänglich.

0
bento

Verwenden Sie diese Methode, um die untergeordneten Ansichten und die aktuellen Ansichten aus dem Speicher zu löschen.

//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
Robins Gupta