Представление магистрали: наследование и расширение событий от родительского объекта
Документация магистрали гласит:
Свойство events также может быть определено как функция, возвращающая хэш событий, чтобы упростить программное определение событий, а также наследование их от родительских представлений.
Как вы наследуете события родительского представления и расширяете их?
Родительский Вид
var ParentView = Backbone.View.extend({
events: {
'click': 'onclick'
}
});
Вид Ребенка
var ChildView = ParentView.extend({
events: function(){
????
}
});
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' }); } });
Солдат.ответ мотылька-хороший ответ. Упрощая его дальше, вы могли бы просто сделать следующее
var ChildView = ParentView.extend({ initialize: function(){ _.extend(this.events, ParentView.prototype.events); } });
Затем просто определите свои события в любом классе типичным способом.
Можно также использовать метод
defaults
, чтобы избежать создания пустого объекта{}
.var ChildView = ParentView.extend({ events: function(){ return _.defaults({ 'click' : 'onclickChild' }, ParentView.prototype.events); } });
Если вы используете CoffeeScript и задаете функцию
events
, Вы можете использоватьsuper
.class ParentView extends Backbone.View events: -> 'foo' : 'doSomething' class ChildView extends ParentView events: -> _.extend {}, super, 'bar' : 'doOtherThing'
Не проще ли было бы создать специализированный базовый конструктор из магистрали.Представление, которое обрабатывает наследование событий вверх по иерархии.
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 }
Это позволяет нам уменьшить (объединить) хэш событий вниз по иерархии всякий раз, когда мы создаем новый подкласс(дочерний конструктор) с помощью переопределенной функции extend.
# 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, которое переопределяет функцию extend, мы можем иметь подвиды (такие как AppView, SectionView), которые хотят наследовать их объявленные события родительского представления просто делают это, расширяясь от BaseView или одного из его производных.
Мы избегаем необходимости программно определять наши функции событий в наших подвидах, которые в большинстве случаев должны явно ссылаться на родительский конструктор.
Краткая версия @soldier.последнее предложение мотылька:
var ChildView = ParentView.extend({ events: function(){ return _.extend({}, _.result(ParentView.prototype, 'events') || {}, { 'click' : 'onclickChild' }); } });
Это также будет работать:
class ParentView extends Backbone.View events: -> 'foo' : 'doSomething' class ChildView extends ParentView events: -> _.extend({}, _.result(_super::, 'events') || {}, 'bar' : 'doOtherThing')
Использование straight
super
не работало для меня, либо вручную указываяParentView
или наследуемый класс.Доступ к
_super
var, который доступен в любом coffeescriptClass … extends …
// 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 */ } });
Для основной версии 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", }
Я нашел более интересные решения в этой статье
Это использование магистрального super и ECMAScript hasOwnProperty. Второй из его прогрессивных примеров работает как очарование. Вот немного кода:
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); }, ... });
Вы также можете сделать это для ui и атрибутов.
Этот пример не заботится о свойствах, заданных функцией, но автор статьи предлагает решение в этом случае.
Это решение CoffeeScript сработало для меня (и учитывает @soldier.предложение мотылька):
class ParentView extends Backbone.View events: -> 'foo' : 'doSomething' class ChildView extends ParentView events: -> _.extend({}, _.result(ParentView.prototype, 'events') || {}, 'bar' : 'doOtherThing')
Если вы уверены, что
ParentView
имеет события, определенные как объект, и вам не нужно динамически определять события вChildView
, можно упростить солдата.ответ мотылька далее, избавившись от функции и используя_.extend
напрямую:var ParentView = Backbone.View.extend({ events: { 'click': 'onclick' } }); var ChildView = ParentView.extend({ events: _.extend({}, ParentView.prototype.events, { 'click' : 'onclickChild' }) });
Шаблон для этого, который мне нравится, - это изменение конструктора и добавление некоторых дополнительных функций:
// 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
.
Вау, здесь много ответов, но я подумал, что предложу еще один. Если вы используете библиотеку 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
Чтобы сделать это полностью в родительском классе и поддерживать хэш событий на основе функций в дочернем классе, чтобы потомки могли быть агностиками наследования (потомку придется вызвать
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); }; } });