Backbone.js is interesting because it gives you the basic framework of an MVC application without enforcing patterns on you. However, in larger web application it's always important to ensure your structure promotes code reuse and good organization for future development. One such way is through inheritance of views, and in this post I will describe how to create an inheritance chain using Backbone Views.
Motivation
Backbone views provide a basic implementation to represent a view, but there are also areas where further functionality could be required, such as an improved handling of view disposal, or just general application logic you feel a subset of your views should have.
In our application, we want to be able to create subclasses of Backbone.View
for disposable views, drop down views, table views, row views, cell views and so on, whilst maintaining the ability to inherit further in the future. Inheriting from Backbone.View
isn't as easy as it seems, especially when you consider that you would like children to inherit events and attributes of the parent, whilst also specifying their own. To this end, I set about creating an extensible inheritance chain between my views that would suit my development needs now and in the future.
Subclassing Backbone.View
For our first example, let's assume we want to define a BaseView
from which our other views will inherit from. This view will contain the logic surrounding inheriting events and attributes. You might expect to inherit from Backbone.View
like so:
However, this solution can cause several issues. Let's delve a little deeper into this.
Prototype Properties vs. Instance Variables
When you use extend on Backbone.View
and specify properties, they aren't specific to every instance of BaseView
you create. They're properties on the prototype of Backbone.View
, not variables on the instance of your BaseView
. Consider the following:
As shown, the modification of events
in ChildTwo
has affected ChildOne
's event object, even though they should be completely separate extensions.
Extend works by extending the Backbone.View.prototype
with the object literal you specify. When we edit the events object in ChildTwo
, we're actually editing the prototype of BaseView
, rather than the instance of ChildTwo
. So, ChildOne
will also have the same modifications because it uses the prototype of BaseView
. Baddie!
There are actually two underlying problems here. Firstly, we want instance variables rather than prototype properties, so that every instance of our BaseView
or ChildView
s get their own events object. Secondly, we don't want to have to declare a new events object every time, as the parent may have events we want to keep and we don't want to duplicate code across children. So, let's solve them:
Backbone Inheritance with Instance Variables
We need to adjust the way we inherit from Backbone.View
. Firstly, we define our BaseView
as a function, which will serve as our constructor. In this constructor, we can then define the instance variables we want every instance of our BaseView
to have.
We then extend the prototype to copy over all the prototype properties from Backbone.View
, and assign the extend to our extend.
This is pretty simple, we're calling Backbone.View
in our constructor, and then extending the Backbone.View
prototype. This gives us the opportunity to define the instance variables in the constructor of BaseView
.
Inherit values from a parent's prototype property
Backbone's event
and attribute
properties are on the prototype of Backbone.View
. However, we don't want to overwrite the objects in every child class, and would rather have the events concatenated with the events of the parent. This requires some changes to our BaseView
:
Let's look at what we're doing here. Firstly, we define the instance variable inheritedEvents
, which is an array of event objects. When a child wants to add an object of arrays to be handled, it calls this.addEvents(eventObj)
to have it added to the array. Because this is an instance variable, the events are individual to each instance and we don't need to override the parent's object.
We've also defined a prototype property called baseEvents
, which is where you would put any events common across all BaseView
instances.
Finally, we've changed events
to be a function which returns the concatenation of the base events and all inherited events.
A child could add its own events like so:
and it would still have the events defined in BaseView
.
Subclassing Children - the full chain
Once we have this step handled, we can apply it throughout a chain of children. As long as each potential parent inherits from its parent in the correct way, introducing new children is seamless provided you do not override the events
function with an object literal, and call addEvents
to add your events.
Future Development
Attributes
The attributes object is handled differently. Unlike events, it cannot be a function, but it is used in Backbone's make
function to create the element. For this reason, we need to keep the attributes object an object literal and also up-to-date as children add attributes to it. One solution is to make attributes an instance variable, and have it kept up-to-date with addAttributes
like so:
I'm concerned at the processing overhead of this (though I can't see there being much of an issue, as typically you'd add attributes when you initialize a view, before the element is in the DOM) of this, however, and would like to do some more investigation and I am open to ideas as to how to improve it.
Inheritance of Functions
Things like functions which are defined on the prototype are still overriden by children, which may not be desirable. A common pattern would be something like:
In this case, you'd have your children override initializeInternal
. However, as your inheritance chain gets deeper, you may want to perform step-by-step initialization as you get further down the chain, and overriding initializeInternal
would cause a lot of duplicate code at each childless view. A solution such as creating an initialize chain may work here, and having the root view have a chain of functions to call on initialize. Something like:
This code is untested, and I'm not sure where and how you would define your initialization functions without completely changing the pattern of creating views. Currently, we keep our functional logic as low down the inheritance chain as possible to avoid a lot of overriding.
Conclusion
By inheriting from Backbone.View
you improve the readability (i.e. views are less cluttered), promote code reuse and make future developments easy. Although how to inherit from views isn't obvious at first, and Backbone's way of handling events and attributes make things a little tricky, I think being able to perform classical inheritance with Backbone is an incredibly important concept for large-scale web applications.