Vista de backbone: hereda y extiende eventos de padre

115

La documentación de Backbone dice:

La propiedad de eventos también se puede definir como una función que devuelve un hash de eventos, para facilitar la definición de sus eventos mediante programación, así como para heredarlos de las vistas principales.

¿Cómo hereda los eventos de vista de un padre y los extiende?

Vista principal

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

Vista infantil

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

Respuestas:

189

Una forma es:

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

Otro sería:

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

Para comprobar si Eventos es función u objeto

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});
soldado.moth
fuente
Eso es genial ... Tal vez podría actualizar esto para mostrar cómo heredaría de un ChildView (verifique si el prototipo de eventos es una función u objeto) ... O tal vez estoy pensando demasiado en todo esto de la herencia.
brent
@brent Seguro, acabo de agregar el tercer caso
soldier.moth
14
Si no me equivoco, debería poder usar en parentEvents = _.result(ParentView.prototype, 'events');lugar de verificar 'manualmente' si eventses una función.
Koen.
3
@Koen. +1 por mencionar la función de utilidad de subrayado _.result, que no había notado antes. Para cualquiera que esté interesado, aquí hay un jsfiddle con un montón de variaciones sobre este tema: jsfiddle
EleventyOne
1
Solo para arrojar mis dos centavos aquí, creo que la segunda opción es la mejor solución. Digo esto por el simple hecho de que es el único método que está verdaderamente encapsulado. el único contexto utilizado es thisversus tener que llamar a la clase principal por el nombre de la instancia. Muchas gracias por esto.
jessie james jackson taylor
79

La respuesta de soldier.moth es buena. Simplificándolo aún más, podría hacer lo siguiente

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

Luego, simplemente defina sus eventos en cualquiera de las clases de la manera típica.

34m0
fuente
8
Buena decisión, aunque probablemente desee intercambiar this.eventsy, de lo ParentView.prototype.eventscontrario, si ambos definen controladores en el mismo evento, el controlador del padre anulará el del hijo.
soldier.moth
1
@ Soldier.moth, está bien, lo he editado para que sea como{},ParentView.prototype.events,this.events
AJP
1
Obviamente, esto funciona, pero como sé, delegateEventsse llama en el constructor para vincular eventos. Entonces, cuando lo extiendes en el initialize, ¿cómo es que no es demasiado tarde?
SelimOber
2
Es quisquilloso, pero mi problema con esta solución es: si tiene una jerarquía diversa y abundante de puntos de vista, inevitablemente se encontrará escribiendo initializeen algunos casos (luego tendrá que lidiar con la gestión de la jerarquía de esa función también) simplemente para fusionar los objetos de evento. Me parece más limpio mantener la eventsfusión dentro de sí mismo. Dicho esto, no habría pensado en este enfoque, y siempre es agradable verse obligado a ver las cosas de una manera diferente :)
EleventyOne
1
esta respuesta ya no es válida porque se llama a delegateEvents antes de inicializar (esto es cierto para la versión 1.2.3); es fácil hacerlo en la fuente anotada.
Roey
12

También puede utilizar el defaultsmétodo para evitar crear el objeto vacío {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});
jermel
fuente
2
Esto hace que los controladores principales se vinculen después de los controladores secundarios. En la mayoría de los casos no es un problema, pero si un evento secundario debe cancelar (no anular) un evento principal, no es posible.
Koen.
10

Si usa CoffeeScript y establece una función en events, puede usar super.

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

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'
Shuhei Kagawa
fuente
Esto solo funciona si la variable de eventos principal es una función en lugar de un objeto.
Michael
6

¿No sería más fácil crear un constructor base especializado desde Backbone.View que maneje la herencia de eventos en la jerarquía?

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
}

Esto nos permite reducir (fusionar) los eventos en la jerarquía cada vez que creamos una nueva 'subclase' (constructor hijo) utilizando la función de extensión redefinida.

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

Al crear una vista especializada: BaseView que redefine la función de extensión, podemos tener subvistas (como AppView, SectionView) que quieran heredar los eventos declarados de su vista principal, simplemente hágalo extendiendo desde BaseView o uno de sus derivados.

Evitamos la necesidad de definir programáticamente nuestras funciones de eventos en nuestras subvistas, que en la mayoría de los casos necesitan referirse explícitamente al constructor padre.

Shaw W
fuente
2

Versión corta de la última sugerencia de @ soldier.moth:

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

Esto también funcionaría:

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

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

Usar directamente superno me funcionaba, tampoco especificaba manualmente elParentView clase o heredada.

Acceso a la _supervar que está disponible en cualquier coffeescriptClass … extends …

yema de huevo
fuente
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/

vovan
fuente
1

Para la versión 1.2.3 de Backbone, __super__funciona bien e incluso puede estar encadenado. P.ej:

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

... que - en A_View.js- resultará en:

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

Encontré soluciones más interesantes en este artículo.

Utiliza super de Backbone y hasOwnProperty de ECMAScript. El segundo de sus ejemplos progresivos funciona a las mil maravillas. Aquí hay un código:

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

También puede hacer eso para la interfaz de usuario y los atributos .

Este ejemplo no se ocupa de las propiedades establecidas por una función, pero el autor del artículo ofrece una solución en ese caso.

firebird631
fuente
1

Para hacer esto completamente en la clase principal y admitir un hash de eventos basado en funciones en la clase secundaria para que los niños puedan ser independientes de la herencia (el niño tendrá que llamar MyView.prototype.initializesi anula 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);
    };
  }
});
Kevin Borders
fuente
0

Esta solución de CoffeeScript funcionó para mí (y tiene en cuenta la sugerencia de @ soldier.moth):

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

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

Si está seguro de que ParentViewtiene los eventos definidos como objeto y no necesita definir eventos dinámicamente ChildView, es posible simplificar aún más la respuesta de soldier.moth al deshacerse de la función y usar _.extenddirectamente:

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

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

Un patrón para esto que me gusta es modificar el constructor y agregar alguna funcionalidad adicional:

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

});

Prefiero este método porque no tienes que identificar al padre, una variable menos para cambiar. Utilizo la misma lógica para attributesy defaults.

jtrumbull
fuente
0

Vaya, muchas respuestas aquí, pero pensé en ofrecer una más. Si usa la biblioteca BackSupport, ofrece extend2. Si lo usa extend2, automáticamente se encarga de fusionar events(así comodefaults propiedades similares) por usted.

He aquí un ejemplo rápido:

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

máquina fantasma
fuente
3
Me gusta el concepto, pero, solo en principio, pasaría cualquier biblioteca que piense que "extend2" es un nombre de función adecuado.
Yaniv
Agradecería cualquier sugerencia que pueda ofrecer sobre el nombre de una función que es esencialmente "Backbone.extend, pero con una funcionalidad mejorada". Extend 2.0 ( extend2) fue lo mejor que se me ocurrió, y no creo que sea tan terrible: cualquiera que esté acostumbrado a Backbone ya está acostumbrado a usarlo extend, por lo que de esta manera no necesita memorizar un nuevo comando.
machineghost
Abrió un problema en el repositorio de Github al respecto. :)
Yaniv