Backbone-Ansicht: Erben und erweitern Sie Ereignisse vom übergeordneten Element


115

In der Dokumentation von Backbone heißt es:

Die Ereigniseigenschaft kann auch als eine Funktion definiert werden, die einen Ereignis-Hash zurückgibt, um das programmgesteuerte Definieren Ihrer Ereignisse sowie das Erben dieser Ereignisse von übergeordneten Ansichten zu vereinfachen.

Wie erben Sie die Ansichtsereignisse eines Elternteils und erweitern sie?

Übergeordnete Ansicht

var ParentView = Backbone.View.extend({
   events: {
      'click': 'onclick'
   }
});

Untergeordnete Ansicht

var ChildView = ParentView.extend({
   events: function(){
      ????
   }
});

Antworten:


189

Ein Weg ist:

var ChildView = ParentView.extend({
   events: function(){
      return _.extend({},ParentView.prototype.events,{
          'click' : 'onclickChild'
      });
   }
});

Ein anderer wäre:

var ParentView = Backbone.View.extend({
   originalEvents: {
      'click': 'onclick'
   },
   //Override this event hash in
   //a child view
   additionalEvents: {
   },
   events : function() {
      return _.extend({},this.originalEvents,this.additionalEvents);
   }
});

var ChildView = ParentView.extend({
   additionalEvents: {
      'click' : ' onclickChild'
   }
});

Um zu überprüfen, ob Ereignisse eine Funktion oder ein Objekt sind

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});

Das ist großartig ... Vielleicht könnten Sie dies aktualisieren, um zu zeigen, wie Sie von einer ChildView erben würden (prüfen Sie, ob die Prototypereignisse eine Funktion oder ein Objekt sind) ... Oder ich überdenke dieses ganze Vererbungsmaterial.
Brent

@ Brent Sicher, gerade dritten Fall hinzugefügt
soldat.moth

14
Wenn ich mich nicht irre, sollten Sie in der Lage sein, parentEvents = _.result(ParentView.prototype, 'events');anstelle von 'manuell' zu prüfen, ob eventses sich um eine Funktion handelt.
Koen.

3
@Koen. +1 für die Erwähnung der Unterstrich-Dienstprogrammfunktion _.result, die ich vorher nicht bemerkt hatte. Für alle, die interessiert sind, hier ist eine jsfiddle mit einer Reihe von Variationen zu diesem Thema: jsfiddle
EleventyOne

1
Nur um meine zwei Cent hier reinzuwerfen, glaube ich, dass die zweite Option die beste Lösung ist. Ich sage dies aufgrund der Tatsache, dass es die einzige Methode ist, die wirklich gekapselt ist. Der einzige verwendete Kontext besteht thisdarin, die übergeordnete Klasse nicht nach dem Instanznamen aufrufen zu müssen. Vielen Dank dafür.
Jessie James Jackson Taylor

79

Die Antwort von soldat.moth ist gut. Wenn Sie es weiter vereinfachen, können Sie einfach Folgendes tun

var ChildView = ParentView.extend({
   initialize: function(){
       _.extend(this.events, ParentView.prototype.events);
   }
});

Definieren Sie dann einfach Ihre Ereignisse in beiden Klassen auf typische Weise.


8
Guter Anruf, obwohl Sie wahrscheinlich tauschen möchten this.eventsund ParentView.prototype.eventsansonsten, wenn beide Handler für dasselbe Ereignis definieren, überschreibt der Handler des Elternteils den des Kindes.
soldat.moth

1
@ Soldier.moth, okay, ich habe es so bearbeitet, dass es so ist{},ParentView.prototype.events,this.events
AJP

1
Natürlich funktioniert dies, aber wie ich weiß, delegateEventswird es im Konstruktor aufgerufen, Ereignisse zu binden. initializeWie kommt es, dass es nicht zu spät ist, wenn Sie es im verlängern ?
SelimOber

2
Es ist nicht sehr wählerisch, aber mein Problem mit dieser Lösung ist: Wenn Sie eine vielfältige und reichhaltige Hierarchie von Ansichten haben, werden Sie unweigerlich initializein einigen Fällen schreiben (dann müssen Sie sich auch mit der Verwaltung der Hierarchie dieser Funktion befassen), um einfach Führen Sie die Ereignisobjekte zusammen. Scheint mir sauberer, die eventsVerschmelzung in sich zu behalten .
Davon abgesehen

1
Diese Antwort ist nicht mehr gültig, da delegateEvents vor der Initialisierung aufgerufen wird (dies gilt für Version 1.2.3) - dies ist in der mit Anmerkungen versehenen Quelle einfach.
Roey

12

Sie können die defaultsMethode auch verwenden , um das Erstellen des leeren Objekts zu vermeiden {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});

2
Dies führt dazu, dass die übergeordneten Handler nach den untergeordneten Handlern gebunden werden. In den meisten Fällen kein Problem, aber wenn ein untergeordnetes Ereignis ein übergeordnetes Ereignis abbrechen (nicht überschreiben) sollte, ist dies nicht möglich.
Koen.

10

Wenn Sie CoffeeScript verwenden und eine Funktion auf setzen events, können Sie verwenden super.

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'

Dies funktioniert nur, wenn die übergeordnete Ereignisvariable eher eine Funktion als ein Objekt ist.
Michael

6

Wäre es nicht einfacher, einen speziellen Basiskonstruktor aus Backbone.View zu erstellen, der die Vererbung von Ereignissen in der Hierarchie behandelt?

BaseView = Backbone.View.extend {
    # your prototype defaults
},
{
    # redefine the 'extend' function as decorated function of Backbone.View
    extend: (protoProps, staticProps) ->
      parent = this

      # we have access to the parent constructor as 'this' so we don't need
      # to mess around with the instance context when dealing with solutions
      # where the constructor has already been created - we won't need to
      # make calls with the likes of the following:   
      #    this.constructor.__super__.events
      inheritedEvents = _.extend {}, 
                        (parent.prototype.events ?= {}),
                        (protoProps.events ?= {})

      protoProps.events = inheritedEvents
      view = Backbone.View.extend.apply parent, arguments

      return view
}

Auf diese Weise können wir den Ereignis-Hash in der Hierarchie reduzieren (zusammenführen), wenn wir mithilfe der neu definierten Erweiterungsfunktion eine neue Unterklasse (untergeordneten Konstruktor) erstellen.

# AppView is a child constructor created by the redefined extend function
# found in BaseView.extend.
AppView = BaseView.extend {
    events: {
        'click #app-main': 'clickAppMain'
    }
}

# SectionView, in turn inherits from AppView, and will have a reduced/merged
# events hash. AppView.prototype.events = {'click #app-main': ...., 'click #section-main': ... }
SectionView = AppView.extend {
    events: {
        'click #section-main': 'clickSectionMain'
    }
}

# instantiated views still keep the prototype chain, nothing has changed
# sectionView instanceof SectionView => true 
# sectionView instanceof AppView => true
# sectionView instanceof BaseView => true
# sectionView instanceof Backbone.View => also true, redefining 'extend' does not break the prototype chain. 
sectionView = new SectionView { 
    el: ....
    model: ....
} 

Durch Erstellen einer speziellen Ansicht: BaseView, die die Erweiterungsfunktion neu definiert, können Unteransichten (wie AppView, SectionView) vorhanden sein, die die deklarierten Ereignisse ihrer übergeordneten Ansicht erben möchten. Dies geschieht einfach durch Erweitern von BaseView oder einer ihrer Ableitungen.

Wir vermeiden die Notwendigkeit, unsere Ereignisfunktionen programmgesteuert in unseren Unteransichten zu definieren, die in den meisten Fällen explizit auf den übergeordneten Konstruktor verweisen müssen.


2

Kurzversion des letzten Vorschlags von @ sold.moth:

var ChildView = ParentView.extend({
  events: function(){
    return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
      'click' : 'onclickChild'
    });
  }
});

2

Dies würde auch funktionieren:

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(_super::, 'events') || {},
      'bar' : 'doOtherThing')

Die Verwendung von Straight superfunktionierte bei mir nicht und spezifizierte das auch manuellParentView oder die geerbte Klasse .

Zugriff auf die _superVar, die in jedem Coffeescript verfügbar istClass … extends …


2

// ModalView.js
var ModalView = Backbone.View.extend({
	events: {
		'click .close-button': 'closeButtonClicked'
	},
	closeButtonClicked: function() { /* Whatever */ }
	// Other stuff that the modal does
});

ModalView.extend = function(child) {
	var view = Backbone.View.extend.apply(this, arguments);
	view.prototype.events = _.extend({}, this.prototype.events, child.events);
	return view;
};

// MessageModalView.js
var MessageModalView = ModalView.extend({
	events: {
		'click .share': 'shareButtonClicked'
	},
	shareButtonClicked: function() { /* Whatever */ }
});

// ChatModalView.js
var ChatModalView = ModalView.extend({
	events: {
		'click .send-button': 'sendButtonClicked'
	},
	sendButtonClicked: function() { /* Whatever */ }
});

http://danhough.com/blog/backbone-view-inheritance/


1

Für Backbone Version 1.2.3 __super__funktioniert es einwandfrei und kann sogar verkettet sein. Z.B:

// A_View.js
var a_view = B_View.extend({
    // ...
    events: function(){
        return _.extend({}, a_view.__super__.events.call(this), { // Function - call it
            "click .a_foo": "a_bar",
        });
    }
    // ...
});

// B_View.js
var b_view = C_View.extend({
    // ...
    events: function(){
        return _.extend({}, b_view.__super__.events, { // Object refence
            "click .b_foo": "b_bar",
        });
    }
    // ...
});

// C_View.js
var c_view = Backbone.View.extend({
    // ...
    events: {
        "click .c_foo": "c_bar",
    }
    // ...
});

... was - in A_View.js- führt zu:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}

1

Ich habe hier eine interessantere Lösung gefunden Artikel

Einzelheiten hierzu sind im Backbone des Super und ECMAScript des hasOwnProperty. Das zweite seiner progressiven Beispiele wirkt wie ein Zauber. Hier ist ein bisschen ein Code:

var ModalView = Backbone.View.extend({
    constructor: function() {
        var prototype = this.constructor.prototype;

        this.events = {};
        this.defaultOptions = {};
        this.className = "";

        while (prototype) {
            if (prototype.hasOwnProperty("events")) {
                _.defaults(this.events, prototype.events);
            }
            if (prototype.hasOwnProperty("defaultOptions")) {
                _.defaults(this.defaultOptions, prototype.defaultOptions);
            }
            if (prototype.hasOwnProperty("className")) {
                this.className += " " + prototype.className;
            }
            prototype = prototype.constructor.__super__;
        }

        Backbone.View.apply(this, arguments);
    },
    ...
});

Sie können dies auch für Benutzeroberflächen und Attribute tun .

In diesem Beispiel werden die von einer Funktion festgelegten Eigenschaften nicht berücksichtigt, aber der Autor des Artikels bietet in diesem Fall eine Lösung an.


1

Um dies vollständig in der übergeordneten Klasse zu tun und einen funktionsbasierten Ereignis-Hash in der untergeordneten Klasse zu unterstützen, damit Kinder unabhängig von der Vererbung sein können (das Kind muss aufrufen, MyView.prototype.initializewenn es überschreibt initialize):

var MyView = Backbone.View.extend({
  events: { /* ... */ },

  initialize: function(settings)
  {
    var origChildEvents = this.events;
    this.events = function() {
      var childEvents = origChildEvents;
      if(_.isFunction(childEvents))
         childEvents = childEvents.call(this);
      return _.extend({}, MyView.prototype.events, childEvents);
    };
  }
});

0

Diese CoffeeScript-Lösung hat bei mir funktioniert (und berücksichtigt den Vorschlag von @ sold.moth):

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(ParentView.prototype, 'events') || {},
      'bar' : 'doOtherThing')

0

Wenn Sie sicher sind, dass die ParentViewEreignisse als Objekt definiert sind und Sie Ereignisse nicht dynamisch definieren müssen, ChildViewkönnen Sie die Antwort von sold.moth weiter vereinfachen, indem Sie die Funktion entfernen und _.extenddirekt verwenden:

var ParentView = Backbone.View.extend({
    events: {
        'click': 'onclick'
    }
});

var ChildView = ParentView.extend({
    events: _.extend({}, ParentView.prototype.events, {
        'click' : 'onclickChild'
    })
});

0

Ein Muster dafür, das mir gefällt, ist das Ändern des Konstruktors und das Hinzufügen einiger zusätzlicher Funktionen:

// App View
var AppView = Backbone.View.extend({

    constructor: function(){
        this.events = _.result(this, 'events', {});
        Backbone.View.apply(this, arguments);
    },

    _superEvents: function(events){
        var sooper = _.result(this.constructor.__super__, 'events', {});
        return _.extend({}, sooper, events);
    }

});

// Parent View
var ParentView = AppView.extend({

    events: {
        'click': 'onclick'
    }

});

// Child View
var ChildView = ParentView.extend({

    events: function(){
        return this._superEvents({
            'click' : 'onclickChild'
        });
    }

});

Ich bevorzuge diese Methode, weil Sie die übergeordnete Variable nicht identifizieren müssen, um sie zu ändern. Ich benutze die gleiche Logik für attributesund defaults.


0

Wow, viele Antworten hier, aber ich dachte, ich würde noch eine anbieten. Wenn Sie die BackSupport-Bibliothek verwenden, bietet sie extend2. Wenn Sie es verwenden extend2, kümmert es sich automatisch um das Zusammenführen events(sowiedefaults ähnliche Eigenschaften) für Sie.

Hier ist ein kurzes Beispiel:

var Parent = BackSupport.View.extend({
    events: {
        change: '_handleChange'
    }
});
var Child = parent.extend2({
    events: {
        click: '_handleClick'
    }
});
Child.prototype.events.change // exists
Child.prototype.events.click // exists

https://github.com/machineghost/BackSupport


3
Ich mag das Konzept, aber im Prinzip allein würde ich jede Bibliothek weitergeben, die denkt, dass "extens2" ein richtiger Funktionsname ist.
Yaniv

Ich würde mich über Vorschläge freuen, die Sie zur Benennung einer Funktion machen können, die im Wesentlichen "Backbone.extend, aber mit verbesserter Funktionalität" ist. Extend 2.0 ( extend2) war das Beste, was ich mir vorstellen konnte, und ich denke nicht, dass es so schrecklich ist: Jeder, der an Backbone gewöhnt ist, ist bereits daran gewöhnt extend, so dass er sich auf diese Weise keinen neuen Befehl merken muss.
Machineghost

Es wurde eine Ausgabe im Github-Repo darüber geöffnet. :)
Yaniv
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.