Перегляд хребта: спадкувати і розширювати події від батьківського


115

У документації хребта зазначено:

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

Як ти успадковуєш події батьківського погляду та розширюєш їх?

Вид батьків

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 Впевнений, щойно додав третій випадок
солдат.мот

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

1
@ Soldier.moth, добре, я відредагував це так, як{},ParentView.prototype.events,this.events
AJP

1
Очевидно, що це працює, але, як я знаю, delegateEventsв конструкторі закликають зв'язувати події. Тож, коли ви продовжите це в розділі initialize, як це зробити, що це не пізно?
SelimOber

2
Це не вибагливо, але моє питання щодо цього рішення полягає в тому, що: якщо у вас різноманітна і рясна ієрархія поглядів, ви неминуче опинитесь initializeу кількох випадках (тоді також маючи справу з управлінням ієрархією цієї функції) просто для злиття об’єктів події. Мені здається чистішим, щоб зберегти eventsзлиття всередині себе. Якщо говорити, я б не думав про такий підхід, і завжди приємно бути змушеним дивитись на речі по-іншому :)
EleventyOne

1
ця відповідь більше не є дійсною, оскільки delegateEvents викликається перед ініціалізацією (це справедливо для версії 1.2.3) - це легко зробити у примітці, що коментується.
Roey

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

Коротка версія останньої пропозиції @ Vojvoder.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 клас або успадковував його.

Доступ до _superвар, який доступний у будь-якому кофескриптіClass … 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

Для хребта версії 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

У цій статті я знайшов більш цікаві рішення

Він використовує суперкореневі версії Backbone та має власну власність 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 працювало для мене (і враховує пропозицію @ солдат.мот):

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

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

0

Якщо ви впевнені, що ParentViewподії визначені як об'єкт, і вам не потрібно динамічно визначати події ChildView, можна додатково спростити відповідь солдат.мот, позбувшись функції та скориставшись _.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
Концепція мені подобається, але, в принципі, я б передав будь-яку бібліотеку, яка вважає, що "exte2" - це правильна назва функції.
Янів

Я вітаю всі запропоновані вами пропозиції щодо того, як назвати функцію, яка по суті є "Backbone.extend, але з покращеною функціональністю". Розширення 2.0 ( extend2) було найкращим, що я міг придумати, і я не думаю, що це все так страшно: кожен, хто звик до Backbone, вже звик користуватися extend, тому таким чином їм не потрібно запам'ятовувати нову команду.
machineghost

Відкрив випуск на репортажі Github про це. :)
Янів
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.