Представление магистрали: наследование и расширение событий от родителя

В документации Backbone указано:

Свойство events также может быть определено как функция, возвращающая хэш событий, чтобы упростить программное определение событий, а также их наследование от родительских представлений.

Как вы наследуете события представления родителя и расширяете их?

Родительский вид

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

Детский вид

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

person brent    schedule 22.02.2012    source источник


Ответы (15)


Один из способов:

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

Другой будет:

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

Чтобы проверить, являются ли события функцией или объектом

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});
person Bobby    schedule 22.02.2012
comment
Это здорово... Может быть, вы могли бы обновить это, чтобы показать, как вы будете наследоваться от ChildView (проверьте, являются ли события прототипа функцией или объектом)... Или, может быть, я слишком много думаю обо всем этом наследовании. - person brent; 23.02.2012
comment
@brent Конечно, только что добавил третий случай - person Bobby; 23.02.2012
comment
Если я не ошибаюсь, вы должны иметь возможность использовать parentEvents = _.result(ParentView.prototype, 'events'); вместо «ручной» проверки, является ли events функцией. - person Koen.; 22.08.2013
comment
@Коэн. +1 за упоминание служебной функции подчеркивания _.result, которую я раньше не замечал. Для всех, кому интересно, вот jsfiddle с кучей вариаций на эту тему: jsfiddle - person EleventyOne; 04.01.2014
comment
Просто чтобы добавить сюда свои два цента, я считаю, что второй вариант - лучшее решение. Я говорю это из-за того факта, что это единственный действительно инкапсулированный метод. единственный используемый контекст - это this вместо того, чтобы вызывать родительский класс по имени экземпляра. большое спасибо за это. - person jessie james jackson taylor; 16.10.2014
comment
Чтобы упростить первый и последний приведенный выше пример, используйте _.defaults: return _.defaults({ 'click' : 'onclickChild' }, ParentView.prototype.events) - person TypingTurtle; 13.05.2016

Ответ солдата.мотылек хороший. Упрощая это, вы можете просто сделать следующее

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

Затем просто определите свои события в любом классе обычным способом.

person 34m0    schedule 05.04.2012
comment
Хороший вызов, хотя вы, вероятно, захотите поменять местами this.events и ParentView.prototype.events, иначе, если оба определяют обработчики одного и того же события, родительский обработчик переопределит дочерний. - person Bobby; 06.04.2012
comment
@Soldier.moth вы хотели, чтобы @34mo написал: {},ParentView.prototype.events,this.events ? Просто проверьте, как говорит underscore.extend: это в порядке, поэтому последний источник переопределит свойства того же имя в предыдущих аргументах. - person AJP; 03.06.2012
comment
@AJP да {},ParentView.prototype.events,this.events так и должно быть. Первоначально это было _.extend(this.events,ParentView.prototype.events) - person Bobby; 04.06.2012
comment
@Soldier.moth, хорошо, я отредактировал его так, чтобы он был {},ParentView.prototype.events,this.events - person AJP; 05.06.2012
comment
@ЭндрюБ. Он ждал одобрения. - person Brad Gilbert; 05.06.2012
comment
Очевидно, это работает, но, насколько я знаю, delegateEvents вызывается в конструкторе для привязки событий. Итак, когда вы продлеваете его в initialize, почему еще не слишком поздно? - person SelimOber; 31.07.2012
comment
@SelimOber Поскольку delegateEvents вызывается после инициализации, все, что вы настроили при инициализации, будет делегировано. - person Paul Alexander; 13.10.2012
comment
Это придирчиво, но моя проблема с этим решением заключается в следующем: если у вас разнообразная и многочисленная иерархия представлений, вы неизбежно обнаружите, что пишете initialize в нескольких случаях (тогда вам также приходится иметь дело с управлением иерархией этой функции) просто объединить объекты событий. Мне кажется чище держать events слитым внутри себя. При этом я бы не подумал о таком подходе, и всегда приятно быть вынужденным смотреть на вещи по-другому :) - person EleventyOne; 04.01.2014
comment
Это хорошее решение, но в основном оно создает ту же проблему с инициализацией, т.е. теперь вам нужно помнить о вызове ParentView.prototype.initialize. Принятый ответ немного более автономен - person Evan Hobbs; 26.06.2014
comment
Согласитесь с @SelimOber, это решение не должно работать, поскольку delegateEvents вызывается до вызова функции инициализации: github.com/jashkenas/backbone/blob/master/backbone.js#L1199 - person Alan; 11.11.2015
comment
этот ответ больше недействителен, потому что delegateEvents вызывается перед инициализацией (это верно для версии 1.2.3) - это легко сделать в аннотированном источнике. - person Roey; 29.11.2015
comment
Обратите внимание, что yves ansellem отменил правку, сделанную 34m0 и AJP в ответ на предложение солдата. - person Richard Möhn; 14.08.2018

Вы также можете использовать метод defaults, чтобы избежать создания пустого объекта {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});
person jermel    schedule 27.10.2012
comment
Это приводит к тому, что родительские обработчики привязываются после дочерних обработчиков. В большинстве случаев это не проблема, но если дочернее событие должно отменить (не переопределить) родительское событие, это невозможно. - person Koen.; 22.08.2013

Если вы используете CoffeeScript и устанавливаете функцию на events, вы можете использовать super.

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

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'
person Shuhei Kagawa    schedule 23.08.2013
comment
Это работает, только если родительская переменная событий является функцией, а не объектом. - person Michael; 13.01.2015

Не проще ли создать специализированный базовый конструктор из Backbone.View, который обрабатывает наследование событий вверх по иерархии.

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
}

Это позволяет нам уменьшать (объединять) хэш событий вниз по иерархии всякий раз, когда мы создаем новый «подкласс» (дочерний конструктор) с помощью переопределенной функции расширения.

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

Создавая специализированное представление: BaseView, которое переопределяет функцию расширения, мы можем иметь подпредставления (например, AppView, SectionView), которые хотят наследовать объявленные события своего родительского представления, просто делая это, расширяясь от BaseView или одного из его производных.

Мы избегаем необходимости программно определять наши функции обработки событий в наших подпредставлениях, которые в большинстве случаев должны явно ссылаться на родительский конструктор.

person Shaw W    schedule 26.05.2015

Краткая версия последнего предложения @soldier.moth:

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

Это также будет работать:

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

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

Использование прямого super у меня не работало, либо вручную указывался ParentView, либо унаследованный класс.

Доступ к _super var, который доступен в любом coffeescript Class … extends …

person yolk    schedule 17.08.2014

// 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/

person vovan    schedule 21.04.2015

Для Backbone версии 1.2.3 __super__ работает нормально и даже может быть объединен в цепочку. Например.:

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

... что - в A_View.js - приведет к:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}
person Kafoso    schedule 12.01.2016


Чтобы сделать это полностью в родительском классе и поддерживать хэш событий на основе функций в дочернем классе, чтобы дочерние элементы могли не зависеть от наследования (дочерний элемент должен будет вызывать MyView.prototype.initialize, если он переопределяет 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);
    };
  }
});
person Kevin Borders    schedule 20.06.2017

Это решение CoffeeScript сработало для меня (и учитывает предложение @soldier.moth):

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

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

Если вы уверены, что ParentView имеет события, определенные как объект, и вам не нужно динамически определять события в ChildView, можно еще больше упростить ответ солдата. мотылька, избавившись от функции и используя _.extend напрямую:

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

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

Шаблон для этого, который мне нравится, - это изменение конструктора и добавление некоторых дополнительных функций:

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

});

Я предпочитаю этот метод, потому что вам не нужно идентифицировать родителя — на одну переменную меньше для изменения. Я использую ту же логику для attributes и defaults.

person jtrumbull    schedule 26.03.2015

Вау, здесь много ответов, но я решил предложить еще один. Если вы используете библиотеку BackSupport, она предлагает extend2. Если вы используете extend2, он автоматически позаботится о слиянии events (а также defaults и подобных свойств) за вас.

Вот краткий пример:

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

person machineghost    schedule 04.10.2015
comment
Мне нравится эта концепция, но только из принципа я бы отказался от любой библиотеки, которая считает, что extension2 является правильным именем функции. - person Yaniv; 05.03.2016
comment
Буду рад любым вашим предложениям, как назвать функцию, которая по сути является Backbone.extend, но с улучшенной функциональностью. Расширение 2.0 (extend2) было лучшим, что я мог придумать, и я не думаю, что это так уж ужасно: любой, кто привык к Backbone, уже привык использовать extend, так что им не нужно запоминать новую команду. - person machineghost; 07.03.2016
comment
Открыта проблема в репозитории Github по этому поводу. :) - person Yaniv; 13.03.2016