Backbone View: Inherit and extend events from parent
Javascriptbackbone.jsBackbone EventsJavascript Problem Overview
Backbone's documentation states: > The events property may also be defined as a function that returns an events hash, to make it easier to programmatically define your events, as well as inherit them from parent views.
How do you inherit a parent's view events and extend them?
Parent View
var ParentView = Backbone.View.extend({
events: {
'click': 'onclick'
}
});
Child View
var ChildView = ParentView.extend({
events: function(){
????
}
});
Javascript Solutions
Solution 1 - Javascript
One way is:
var ChildView = ParentView.extend({
events: function(){
return _.extend({},ParentView.prototype.events,{
'click' : 'onclickChild'
});
}
});
Another would be:
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'
}
});
To check whether Events is function or object
var ChildView = ParentView.extend({
events: function(){
var parentEvents = ParentView.prototype.events;
if(_.isFunction(parentEvents)){
parentEvents = parentEvents();
}
return _.extend({},parentEvents,{
'click' : 'onclickChild'
});
}
});
Solution 2 - Javascript
The soldier.moth answer is a good one. Simplifying it further you could just do the following
var ChildView = ParentView.extend({
initialize: function(){
_.extend(this.events, ParentView.prototype.events);
}
});
Then just define your events in either class in the typical way.
Solution 3 - Javascript
You could also use the defaults
method to avoid creating the empty object {}
.
var ChildView = ParentView.extend({
events: function(){
return _.defaults({
'click' : 'onclickChild'
}, ParentView.prototype.events);
}
});
Solution 4 - Javascript
If you use CoffeeScript and set a function to events
, you can use super
.
class ParentView extends Backbone.View
events: ->
'foo' : 'doSomething'
class ChildView extends ParentView
events: ->
_.extend {}, super,
'bar' : 'doOtherThing'
Solution 5 - Javascript
Wouldn't it be easier to create specialized base constructor from Backbone.View that handles the inheritance of events up the hierarchy.
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
}
This allows us to reduce(merge) the events hash down the hierarchy whenever we create a new 'subclass'(child constructor) by using the redefined extend function.
# 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: ....
}
By creating a specialized view: BaseView that redefines the extend function, we can have subviews(like AppView, SectionView) that want to inherit their parent view's declared events simply do so by extending from BaseView or one of its derivatives.
We avoid the need to programmatically define our event functions in our subviews, which in most cases need to refer to the parent constructor explicitly.
Solution 6 - Javascript
Short version of @soldier.moth's last suggestion:
var ChildView = ParentView.extend({
events: function(){
return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
'click' : 'onclickChild'
});
}
});
Solution 7 - Javascript
This would also work:
class ParentView extends Backbone.View
events: ->
'foo' : 'doSomething'
class ChildView extends ParentView
events: ->
_.extend({}, _.result(_super::, 'events') || {},
'bar' : 'doOtherThing')
Using straight super
wasn't working for me, either was manually specifying the ParentView
or inherited class.
Access to the _super
var which is available within any coffeescript Class … extends …
Solution 8 - Javascript
// 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 */ }
});
Solution 9 - Javascript
For Backbone version 1.2.3, __super__
works fine, and may even be chained. E.g.:
// 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",
}
// ...
});
... which - in A_View.js
- will result in:
events: {
"click .a_foo": "a_bar",
"click .b_foo": "b_bar",
"click .c_foo": "c_bar",
}
Solution 10 - Javascript
I've found a more interesting solutions in this article
It use of the Backbone’s super and ECMAScript’s hasOwnProperty. The second of its progressives examples works like a charm. Here's a bit a code :
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);
},
...
});
You can also do that for ui and attributes.
This example does not take care of the properties set by a function, but the author of the article offers a solution in that case.
Solution 11 - Javascript
To do this entirely in the parent class and support a function-based events hash in the child class so that children can be agnostic of inheritance (the child will have to call MyView.prototype.initialize
if it overrides 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);
};
}
});
Solution 12 - Javascript
This CoffeeScript solution worked for me (and takes into account @soldier.moth's suggestion):
class ParentView extends Backbone.View
events: ->
'foo' : 'doSomething'
class ChildView extends ParentView
events: ->
_.extend({}, _.result(ParentView.prototype, 'events') || {},
'bar' : 'doOtherThing')
Solution 13 - Javascript
If you are sure that the ParentView
has the events defined as object and you don't need to define events dynamically in ChildView
it is possible to simplify soldier.moth's answer further by getting rid of the function and using _.extend
directly:
var ParentView = Backbone.View.extend({
events: {
'click': 'onclick'
}
});
var ChildView = ParentView.extend({
events: _.extend({}, ParentView.prototype.events, {
'click' : 'onclickChild'
})
});
Solution 14 - Javascript
A pattern for this that I am fond of is modifying the constructor and adding some additional functionality:
// 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'
});
}
});
I prefer this method because you do not have to identify the parent -one less variable to change. I use the same logic for attributes
and defaults
.
Solution 15 - Javascript
Wow, lots of answers here but I thought I'd offer one more. If you use the BackSupport library, it offers extend2
. If you use extend2
it automatically takes care of merging events
(as well as defaults
and similar properties) for you.
Here's a quick example:
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