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


Документация магистрали гласит:

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

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

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

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

Вид Ребенка

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

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, который доступен в любом coffeescript Class … 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 */ }
});

Http://danhough.com/blog/backbone-view-inheritance/

Для основной версии 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

Https://github.com/machineghost/BackSupport

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