Vista backbone: eredita ed estendi eventi dal genitore


115

La documentazione di Backbone afferma:

La proprietà events può anche essere definita come una funzione che restituisce un hash di eventi, per semplificare la definizione a livello di codice degli eventi, nonché per ereditarli dalle visualizzazioni padre.

Come si ereditano gli eventi di visualizzazione di un genitore e li si estendono?

Visualizzazione genitore

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

Vista bambino

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

Risposte:


189

Un modo è:

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

Un altro potrebbe essere:

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

Per verificare se Events è una funzione o un oggetto

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

È fantastico ... Forse potresti aggiornarlo per mostrare come erediteresti da ChildView (controlla se gli eventi del prototipo sono una funzione o un oggetto) ... O forse sto pensando troppo a tutta questa roba sull'ereditarietà.
brent

@brent Certo, ho appena aggiunto il terzo caso
soldato.moth

14
Se non sbaglio dovresti essere in grado di usare parentEvents = _.result(ParentView.prototype, 'events');invece di controllare "manualmente" se eventsè una funzione.
Koen.

3
@Koen. +1 per menzionare la funzione di utilità di sottolineatura _.result, che non avevo notato prima. Per chiunque sia interessato, ecco un jsfiddle con una serie di variazioni su questo tema: jsfiddle
EleventyOne

1
Solo per buttare i miei due centesimi qui, credo che la seconda opzione sia la soluzione migliore. Lo dico per il semplice fatto che è l'unico metodo veramente incapsulato. l'unico contesto utilizzato è quello di thisdover chiamare la classe genitore per nome dell'istanza. Grazie molte per questo.
jessie james jackson taylor

79

La risposta del soldato è buona. Semplificando ulteriormente potresti semplicemente fare quanto segue

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

Quindi definisci i tuoi eventi in entrambe le classi nel modo tipico.


8
Buona chiamata, anche se probabilmente vuoi scambiare this.eventse ParentView.prototype.eventsaltrimenti se entrambi definiscono i gestori sullo stesso evento, il gestore del genitore sovrascriverà quello del figlio.
soldato.moth

1
@ Soldier.moth, ok, l'ho modificato per essere come{},ParentView.prototype.events,this.events
AJP

1
Ovviamente funziona, ma come so, delegateEventsviene chiamato nel costruttore per associare gli eventi. Quindi, quando lo estendi nel initialize, come mai non è troppo tardi?
SelimOber

2
È pignolo, ma il mio problema con questa soluzione è: se hai una gerarchia di punti di vista diversificata e abbondante, ti ritroverai inevitabilmente a scrivere initializein alcuni casi (quindi a dover gestire anche la gerarchia di quella funzione) semplicemente per unire gli oggetti evento. Mi sembra più pulito mantenere la eventsfusione dentro di sé. Detto questo, non avrei pensato a questo approccio, ed è sempre bello essere costretto a guardare le cose in modo diverso :)
EleventyOne

1
questa risposta non è più valida perché delegateEvents viene chiamato prima dell'inizializzazione (questo è vero per la versione 1.2.3) - è facile farlo nel sorgente annotato.
Roey

12

È inoltre possibile utilizzare il defaultsmetodo per evitare di creare l'oggetto vuoto {}.

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

2
Ciò causa l'associazione dei gestori padre dopo i gestori figlio. Nella maggior parte dei casi non è un problema, ma se un evento figlio deve annullare (non sovrascrivere) un evento genitore, non è possibile.
Koen.

10

Se usi CoffeeScript e imposti una funzione su events, puoi usare super.

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

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

Funziona solo se la variabile degli eventi padre è una funzione piuttosto che un oggetto.
Michael

6

Non sarebbe più semplice creare un costruttore di base specializzato da Backbone.View che gestisce l'ereditarietà degli eventi nella gerarchia.

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
}

Questo ci consente di ridurre (unire) gli eventi hash lungo la gerarchia ogni volta che creiamo una nuova 'sottoclasse' (costruttore figlio) utilizzando la funzione di estensione ridefinita.

# 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: ....
} 

Creando una vista specializzata: BaseView che ridefinisce la funzione di estensione, possiamo avere sottoview (come AppView, SectionView) che vogliono ereditare gli eventi dichiarati della loro vista genitore, lo fanno semplicemente estendendola da BaseView o da uno dei suoi derivati.

Evitiamo la necessità di definire in modo programmatico le nostre funzioni evento nelle nostre sottoview, che nella maggior parte dei casi devono fare riferimento esplicitamente al costruttore padre.


2

Versione breve dell'ultimo suggerimento di @ soldier.moth:

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

2

Funzionerebbe anche:

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

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

L'uso diretto supernon funzionava per me, né specificavo manualmente il fileParentView classe o ereditata.

Accesso alla _supervar disponibile all'interno di qualsiasi coffeescriptClass … 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

Per Backbone versione 1.2.3, __super__funziona bene e può anche essere concatenato. Per esempio:

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

... che - in A_View.js- si tradurrà in:

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

1

Ho trovato soluzioni più interessanti in questo articolo

Utilizza il super di Backbone e hasOwnProperty di ECMAScript. Il secondo dei suoi esempi progressivi funziona come un fascino. Ecco un po 'un codice:

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);
    },
    ...
});

Puoi farlo anche per ui e attributi .

Questo esempio non si occupa delle proprietà impostate da una funzione, ma l'autore dell'articolo offre una soluzione in quel caso.


1

Per farlo interamente nella classe genitore e supportare un hash di eventi basato sulla funzione nella classe figlia in modo che i figli possano essere agnostici dell'ereditarietà (il figlio dovrà chiamare MyView.prototype.initializese sostituisce 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

Questa soluzione CoffeeScript ha funzionato per me (e tiene conto del suggerimento di @ soldier.moth):

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

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

0

Se sei sicuro che ParentViewabbia gli eventi definiti come oggetto e non hai bisogno di definire dinamicamente gli eventi al suo ChildViewinterno è possibile semplificare ulteriormente la risposta di soldier.moth eliminando la funzione e utilizzando _.extenddirettamente:

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

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

0

Uno schema per questo a cui sono affezionato è la modifica del costruttore e l'aggiunta di alcune funzionalità aggiuntive:

// 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'
        });
    }

});

Preferisco questo metodo perché non devi identificare il genitore -una variabile in meno da cambiare. Uso la stessa logica per attributese defaults.


0

Wow, molte risposte qui ma ho pensato di offrirne un'altra. Se utilizzi la libreria BackSupport, offre extend2. Se lo usi, extend2si occupa automaticamente dell'unione events(così come di defaultsproprietà simili) per te.

Ecco un rapido esempio:

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
Mi piace il concetto, ma, in linea di principio, trasmetterei qualsiasi libreria che pensi che "extended2" sia un nome di funzione corretto.
Yaniv

Sarei lieto di qualsiasi suggerimento che potete offrire su come denominare una funzione che è essenzialmente "Backbone.extend, ma con funzionalità migliorate". Extend 2.0 ( extend2) era il meglio che potessi inventare, e non penso sia poi così terribile: chiunque sia abituato a Backbone è già abituato a usarlo extend, quindi in questo modo non ha bisogno di memorizzare un nuovo comando.
machineghost

È stato aperto un problema nel repository Github al riguardo. :)
Yaniv
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.