骨干网视图:从父级继承并扩展事件


115

骨干网的文档指出:

events属性也可以定义为返回事件哈希的函数,以使其更易于以编程方式定义事件以及从父视图继承它们。

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

父视图

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

儿童观

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

Answers:


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当然,只是增加第三种情况
soldier.moth

14
如果我没记错的话,您应该可以使用parentEvents = _.result(ParentView.prototype, 'events');而不是“手动”检查是否events为函数。
科恩

3
@科恩 +1是提到下划线实用程序功能_.result,我以前没有注意到。对于任何有兴趣的人,这里都是jsfiddle,该主题上有很多变种:jsfiddle
EleventyOne 2014年

1
我只想在这里扔两分钱,我相信第二种选择是最好的解决方案。我之所以这样说,是因为纯粹的事实是它是唯一真正封装的方法。唯一使用的上下文是this与必须通过实例名称调用父类。非常感谢你。
杰西·詹姆斯·杰克逊·泰勒2014年

79

士兵。蛾的答案是一个很好的答案。进一步简化它,您可以执行以下操作

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

然后只需以典型方式在任一类中定义事件。


8
打个好电话,尽管您可能想交换this.eventsParentView.prototype.events否则,如果两个都在同一事件上定义处理程序,则父级的处理程序将覆盖子级的处理程序。
军人。2012年

1
@ Soldier.moth,好吧,我将其编辑为{},ParentView.prototype.events,this.events
AJP

1
显然,这可行,但是据我所知,delegateEvents在构造函数中被称为绑定事件。因此,当您在中扩展它时initialize,为什么还不算太晚呢?
SelimOber

2
这有点挑剔,但是我对这个解决方案的问题是:如果您拥有多种多样的视图层次结构,那么您不可避免地会发现自己initialize在少数情况下进行编写(然后也不得不处理该函数的层次结构),仅仅是为了合并事件对象。对我来说似乎更干净,可以保持events合并。话虽如此,我不会想到这种方法,被迫以另一种方式看待事物总是很高兴的:)
EleventyOne 2014年

1
这个答案不再有效,因为在初始化之前调用了委托事件(对于1.2.3版而言是正确的)-在带注释的源代码中很容易做到这一点。
罗伊,2015年

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

使用Straight super不适用于我,或者是手动指定ParentView或继承的类。

访问_super任何coffeescript中可用的varClass … 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

我发现这更有趣的解决方案的文章

它使用了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);
    },
    ...
});

您也可以对uiattribute进行操作

此示例不涉及由函数设置的属性,但是本文的作者提供了这种情况下的解决方案。


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将事件定义为object并且不需要动态定义事件ChildView,则可以通过取消使用函数并_.extend直接使用来进一步简化Solder.moth的答案:

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仓库上打开了一个关于它的问题。:)
Yaniv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.