Visualização do backbone: Herdar e estender eventos do pai

115

A documentação do Backbone afirma:

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, bem como herdá-los das visualizações pai.

Como você herda os eventos de visão de um pai e os amplia?

Visão parental

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

Visão infantil

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

Respostas:

189

Uma maneira é:

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

Outro seria:

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 verificar se os eventos são função ou objeto

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});
soldado.moth
fonte
Isso é ótimo ... Talvez você possa atualizar isso para mostrar como você herdaria de um ChildView (verifique se os eventos de protótipo são uma função ou objeto) ... Ou talvez eu esteja pensando demais em toda essa coisa de herança.
brent
@brent Claro, acabou de adicionar o terceiro caso
soldier.moth
14
Se não me engano, você deve ser capaz de usar em parentEvents = _.result(ParentView.prototype, 'events');vez de 'manualmente' verificar se eventsé uma função.
Koen.
3
@Koen. +1 por mencionar a função de utilidade de sublinhado _.result, que eu não tinha notado antes. Para quem estiver interessado, aqui está um jsfiddle com um monte de variações sobre este tema: jsfiddle
EleventyOne
1
Só para jogar meus dois centavos aqui, acredito que a segunda opção é a melhor solução. Digo isso pelo simples fato de ser o único método verdadeiramente encapsulado. o único contexto usado é thisversus ter que chamar a classe pai pelo nome da instância. Muito obrigado por isso.
jessie james jackson taylor
79

A resposta do soldado.moth é 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.

34m0
fonte
8
Boa chamada, embora você provavelmente queira trocar this.events& ParentView.prototype.eventscaso contrário, se ambos definirem manipuladores no mesmo evento, o manipulador do Pai substituirá o do filho.
soldier.moth
1
@ Soldier.moth, ok, eu editei para ser como{},ParentView.prototype.events,this.events
AJP
1
Obviamente, isso funciona, mas como eu sei, delegateEventsé chamado no construtor para vincular eventos. Então, quando você estende isso no initialize, por que não é tarde demais?
SelimOber
2
É minucioso, mas meu problema com esta solução é: se você tem uma hierarquia de visualizações diversa e abundante, inevitavelmente se encontrará escrevendo initializeem alguns casos (então tendo que lidar com o gerenciamento da hierarquia dessa função também) simplesmente para mesclar os objetos de evento. Parece mais limpo para mim manter a eventsfusão dentro de si. Dito isso, eu não teria pensado nessa abordagem e é sempre bom ser forçado a ver as coisas de uma maneira diferente :)
EleventyOne
1
esta resposta não é mais válida porque delegateEvents é chamado antes da inicialização (isso é verdade para a versão 1.2.3) - é fácil fazer isso na fonte anotada.
Roey
12

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

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});
jermel
fonte
2
Isso faz com que os manipuladores pais sejam vinculados após os manipuladores filhos. Na maioria dos casos, não é um problema, mas se um evento filho cancelar (não substituir) um evento pai, isso não será possível.
Koen.
10

Se você usar CoffeeScript e definir uma função para events, poderá usar super.

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

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'
Shuhei Kagawa
fonte
Isso só funciona se a variável de eventos pai for uma função em vez de um objeto.
Michael
6

Não seria mais fácil criar um construtor de base especializado em Backbone.View que lida com 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) os eventos hash abaixo na hierarquia sempre que criamos 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: ....
} 

Ao criar uma visão especializada: BaseView que redefine a função extender, podemos ter subvisualizações (como AppView, SectionView) que desejam herdar os eventos declarados de sua visão pai simplesmente estendendo de BaseView ou 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.

Shaw W
fonte
2

Versão resumida da última sugestão de @ soldier.moth:

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

Isso também funcionaria:

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

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

Usar straight supernão estava funcionando para mim, era especificar manualmente a ParentViewclasse herdada ou.

Acesso ao _supervar que está disponível em qualquer coffeescriptClass … extends …

gema
fonte
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
fonte
1

Para 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",
}
Kafoso
fonte
1

Eu encontrei soluções mais interessantes neste artigo

Ele usa o super do Backbone e o hasOwnProperty do ECMAScript. O segundo de seus exemplos progressivos funciona perfeitamente. Aqui está um pequeno 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);
    },
    ...
});

Você também pode fazer isso para interface do usuário e atributos .

Este exemplo não cuida das propriedades definidas por uma função, mas o autor do artigo oferece uma solução nesse caso.

firebird631
fonte
1

Para fazer isso inteiramente na classe pai e oferecer suporte a um hash de eventos baseado em função na classe filha para que os filhos possam ser agnósticos quanto à herança (o filho terá que chamar MyView.prototype.initializese isso substituir 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
fonte
0

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

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

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

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

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

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

Um padrão para isso de que 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'
        });
    }

});

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

trumbull
fonte
0

Uau, muitas respostas aqui, mas pensei em oferecer mais uma. Se você usar a biblioteca BackSupport, ela oferece extend2. Se você usá- extend2lo, automaticamente cuidará da mesclagem events(bem como de defaultspropriedades semelhantes) 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

machineghost
fonte
3
Gosto do conceito, mas, apenas por princípio, passaria adiante qualquer biblioteca que pense "extend2" um nome de função apropriado.
Yaniv
Eu agradeceria qualquer sugestão que você possa oferecer sobre como nomear uma função que é essencialmente "Backbone.extend, mas com funcionalidade aprimorada". Extend 2.0 ( extend2) foi o melhor que eu consegui inventar, e eu não acho que seja tão terrível assim: qualquer pessoa acostumada com o Backbone já está acostumada a usar extend, então assim não precisa memorizar um novo comando.
machineghost
Abriu um problema no repositório Github sobre isso. :)
Yaniv