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


115

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

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

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

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

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

Детский вид

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

Ответы:


189

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

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

Это здорово ... Может быть, вы могли бы обновить это, чтобы показать, как вы будете наследовать от ChildView (проверьте, являются ли события прототипа функцией или объектом) ... Или, может быть, я слишком много думаю обо всем этом наследовании.
brent

@brent Конечно, только что добавили третий случай
soldier.moth

14
Если я не ошибаюсь, вы должны иметь возможность использовать parentEvents = _.result(ParentView.prototype, 'events');вместо «ручной» проверки, eventsявляется ли это функцией.
Коэн.

3
@Koen. +1 за упоминание служебной функции подчеркивания _.result, которую я раньше не замечал. Для всех, кому интересно, вот jsfiddle с множеством вариаций на эту тему: jsfiddle
EleventyOne

1
Просто чтобы бросить сюда свои два цента, я считаю, что второй вариант - лучшее решение. Я говорю это потому, что это единственный действительно инкапсулированный метод. единственный используемый контекст - это thisне необходимость вызова родительского класса по имени экземпляра. большое вам спасибо за это.
Джесси Джеймс Джексон Тейлор

79

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

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

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


8
Хороший вызов, хотя вы, вероятно, захотите поменять местами, this.eventsи в ParentView.prototype.eventsпротивном случае, если оба определяют обработчики для одного и того же события, обработчик Родителя переопределит дочерний.
soldier.moth

1
@ Soldier.moth, хорошо, я отредактировал это как{},ParentView.prototype.events,this.events
AJP

1
Очевидно, это работает, но, как я знаю, delegateEventsвызывается в конструкторе для привязки событий. Итак, когда вы расширяете его в initialize, почему еще не поздно?
SelimOber

2
Это придирчиво, но моя проблема с этим решением: если у вас разнообразная и многочисленная иерархия представлений, вы неизбежно обнаружите, что пишете initializeв нескольких случаях (а затем вам придется иметь дело с управлением иерархией этой функции) просто для того, чтобы объединить объекты событий. Мне кажется более чистым сохранить eventsслияние внутри себя. При этом, я бы не подумал об этом подходе, и всегда приятно быть вынужденным смотреть на вещи по-другому :)
EleventyOne

1
этот ответ больше недействителен, потому что delegateEvents вызывается перед инициализацией (это верно для версии 1.2.3) - это легко сделать в аннотированном источнике.
Рой

12

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

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

2
Это вызывает привязку родительских обработчиков после дочерних обработчиков. В большинстве случаев это не проблема, но если дочернее событие должно отменить (не переопределить) родительское событие, это невозможно.
Коэн.

10

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

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

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'

Это работает, только если переменная родительских событий является функцией, а не объектом.
Майкл

6

Не было бы проще создать специализированный базовый конструктор из 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 или одного из его производных.

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


2

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

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

2

Это также сработает:

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

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

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

Доступ к _supervar, который доступен в любом coffeescriptClass … extends …


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/


1

Для 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",
}

1

В этой статье я нашел более интересные решения

Он использует super Backbone и hasOwnProperty ECMAScript. Второй из его прогрессивных примеров работает как шарм. Вот небольшой код:

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

Вы также можете сделать это для пользовательского интерфейса и атрибутов .

В этом примере не рассматриваются свойства, устанавливаемые функцией, но автор статьи предлагает решение в этом случае.


1

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

0

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

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

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

0

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

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

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

0

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

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


0

Вау, здесь много ответов, но я подумал, что предлагаю еще один. Если вы используете библиотеку 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


3
Мне нравится эта концепция, но, исходя только из принципа, я бы передал любую библиотеку, которая считает «extend2» правильным именем функции.
Янив

Я был бы рад любым предложениям, которые вы можете предложить, как назвать функцию, которая по сути является «Backbone.extend, но с улучшенной функциональностью». Extend 2.0 ( extend2) был лучшим, что я мог придумать, и я не думаю, что это все так ужасно: любой, кто использовал Backbone, уже привык к этому extend, поэтому им не нужно запоминать новую команду.
machineghost

Открыл проблему в репозитории Github по этому поводу. :)
Янив
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.