javascript - backbone html




Backbone View: herda e estende eventos do pai (10)

A documentação do backbone declara:

A propriedade events também pode ser definida como uma função que retorna um hash de eventos, para tornar mais fácil definir programaticamente seus eventos, além de herdá-los das visualizações pai.

Como você herda os eventos de visualização de um pai e os estende?

Vista pai

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

Visão infantil

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

A resposta do soldado é boa. Simplificando ainda mais você poderia apenas fazer o seguinte

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

Em seguida, apenas defina seus eventos em qualquer classe da maneira típica.


Esta solução CoffeeScript funcionou para mim (e leva em conta a sugestão do @ soldier.moth):

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

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

Isso também funcionaria:

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

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

Usar o straight super não estava funcionando para mim, seja manualmente especificando o ParentView ou a classe herdada.

O acesso ao _super var que está disponível em qualquer Class … extends … coffeescript Class … extends …


Não seria mais fácil criar um construtor de base especializado a partir do Backbone.View que manipula a herança de eventos na hierarquia.

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
}

Isso nos permite reduzir (mesclar) o hash de eventos na hierarquia sempre que criarmos uma nova 'subclasse' (construtor filho) usando a função de extensão 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: ....
} 

Criando uma visualização especializada: BaseView que redefine a função de extensão, podemos ter subvisualizações (como AppView, SectionView) que desejam herdar os eventos declarados de sua exibição-pai, fazendo isso estendendo-se a partir de BaseView ou de um de seus derivados.

Evitamos a necessidade de definir programaticamente nossas funções de evento em nossas subvisualizações, que na maioria dos casos precisam se referir ao construtor pai explicitamente.


Para o Backbone versão 1.2.3, __super__ funciona bem e pode até ser encadeado. Por exemplo:

// 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 - em A_View.js - resultará em:

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

Se você tem certeza que o ParentView tem os eventos definidos como objeto e você não precisa definir eventos dinamicamente em ChildView , é possível simplificar ainda mais a resposta do soldier.moth, livrando-se da função e usando o _.extend diretamente:

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

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

Uau, muitas respostas aqui, mas eu pensei em oferecer mais uma. Se você usa a biblioteca BackSupport, ela oferece extend2 . Se você usa o extend2 ele automaticamente cuida dos events de mesclagem (assim como defaults e propriedades similares) para você.

Aqui está um exemplo 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


Um padrão para isso que eu gosto é modificar o construtor e adicionar algumas funcionalidades adicionais:

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

});

Eu prefiro esse método porque você não precisa identificar o pai - uma variável menos para alterar. Eu uso a mesma lógica para attributes e defaults .


Versão curta da última sugestão do @ soldier.moth:

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

Você também pode usar o método defaults para evitar a criação do objeto vazio {} .

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




backbone-events