Backbone视图:继承并扩展父级事件

117

Backbone文档指出:

事件属性也可以定义为返回事件哈希的函数,以便更轻松地编程定义您的事件,并从父视图继承它们。

如何继承父视图事件并扩展它们?

父视图

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

子视图

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

192

一种方法是:

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

检查 Events 是否为函数或对象

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

太好了...也许你可以更新一下,展示一下如何从ChildView继承(检查原型事件是否为函数或对象)...或者也许我在过度思考整个继承的东西。 - brent
@brent 好的,我刚刚添加了第三种情况。 - Bobby
15
如果我没记错的话,你应该能够使用parentEvents = _.result(ParentView.prototype, 'events');来代替手动检查events是否为函数。 - Koen.
3
感谢提到下划线实用函数_.result的Koen,我之前没有注意到。对于任何有兴趣的人,这里有一个包含许多变化的jsfiddle主题:jsfiddle - EleventyOne
1
只是想在这里加入我的两分意见,我相信第二个选项是最好的解决方案。我这样说是因为它是唯一真正封装的方法。唯一使用的上下文是“this”,而不是通过实例名称调用父类。非常感谢你的帮助。 - jessie james jackson taylor
为了简化上述第一个和最后一个示例,使用_.defaults: return _.defaults({ 'click' : 'onclickChild' }, ParentView.prototype.events) - TypingTurtle

79

soldier.moth 的回答很好。进一步简化,你可以按照以下步骤操作

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

然后按照通常的方式在任一类中定义您的事件。


8
不错的建议,不过你可能想交换 this.eventsParentView.prototype.events,否则如果两者都在同一个事件上定义处理程序,则父级的处理程序将覆盖子级的处理程序。 - Bobby
1
@Soldier.moth,好的,我已经将其编辑为{},ParentView.prototype.events,this.events - AJP
1
显然这个方法是可行的,但是我知道delegateEvents在构造函数中被调用以绑定事件。那么当你在initialize中扩展它时,为什么不会太晚呢? - SelimOber
2
这可能有点挑剔,但我对这个解决方案的问题是:如果你有一个多样化且丰富的视图层级,你将不可避免地发现自己需要在一些情况下编写initialize(然后还要处理该函数的层次结构),仅仅为了合并事件对象。在我看来,保持events在其内部合并更清晰。话虽如此,我本人没有想到这种方法,被迫以不同的方式看待事物总是很好的 :) - EleventyOne
1
这个答案不再有效,因为在初始化之前调用了delegateEvents(这对于版本1.2.3是正确的) - 在注释源代码中很容易看到这一点。 - Roey
显示剩余7条评论

12
你还可以使用 defaults 方法来避免创建空对象 {}
var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});

2
这会导致父处理程序在子处理程序之后绑定。在大多数情况下不是问题,但如果子事件应该取消(而不是覆盖)父事件,则无法实现。 - Koen.

10
如果您使用CoffeeScript并将函数设置为events,则可以使用super
class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

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

只有在父事件变量是函数而不是对象时,这才有效。 - Michael

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
}

这使我们能够通过重新定义的 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,重新定义扩展函数,我们可以拥有子视图(如AppView,SectionView),它们希望继承其父视图声明的事件,只需从BaseView或其派生类之一进行扩展即可。
我们避免了在子视图中以编程方式定义事件函数的需要,在大多数情况下,这些函数需要明确引用父构造函数。

2
这也可以工作:
class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

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

我尝试使用直接的super,但无法正常工作,手动指定ParentView或继承类也不行。

可以在任何Coffeescript Class … extends …中使用_super变量。


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/


2

@soldier.moth的最后一个建议的简短版本:

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

1
我在这篇文章中发现了更有趣的解决方案。
它使用了Backbone的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);
    },
    ...
});

你也可以对 uiattributes 进行此操作。
此示例未考虑由函数设置的属性,但文章作者提供了解决方案。

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",
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接