Part 2: Backbone.js Deconstructed

1 - Article Overview

In Part 1 of Backbone.js Deconstructed an in-depth review of the periphery Backbone parts was completed. If you haven't read part 1, I'd suggest you start with part 1 before proceeding with part 2. At the very least I would make sure you are comfortable with the concepts discussed in part 1, section 6, Constructing Backbone Objects.

In part 2 of Backbone.js Deconstructed, we will be examining the meat of Backbone. The meat being Backbone, views, models, and collections. Equipped with the knowledge conveyed in part 1, you should be adequately prepared to get intimate with Backbone.View, Backbone.Model and Backbone.Collection constructors and corresponding object instances. Before we begin, I am going to lay out my strategy for discussing the meat.

While it is common that most efforts to teach Backbone start with learning about models, I actually think this prohibits learning. I believe once the peripheral parts are understood, the easiest path for grokking Backbone starts with the view. I strongly suggest a mastery of views before you attempt to populate a view with data (i.e. models or collections of models) and eventing the view to stay in sync with the data. The plan is to first examine views, then models, then collections in that order. Once each of these parts are understood, we will take this knowledge and use it to examine a small and contrived contact's application. Don't be afraid to examine the application first, In fact I recommend it, as it will give a helpful context before reading sections 2, 3, and 4.

I have decided to write a Part 3 in this article series focusing on the syncing of model and collection data using a restful JSON api (i.e. fetch, save, destroy, sync). I did this to keep the complexity surrounding syncing (i.e. servers and api's) out of this article with the hope that this will shine a spotlight on the nature and function of views, models, and collections.

Let's get to it!

2 - Backbone.View

2.1 - Conceptual Overview Of A Backbone.View

It is common to consider a view as an isolated region of a web application. In other words, any logical chunk of the UI, when cutting up the UI into smaller parts for development, can be considered a view. Thus, think of a Backbone.View as the container (i.e. object) for the JavaScript logic that renders and updates this isolated region. Note that a Backbone.View does not store data or groups of data; it is commonly the hub that references model(s) or collection(s) of data and does something with that data.

If it helps, think of the view as the location in your code that brings together and controls the following parts:

The views' job, then, is to take these parts (i.e. data, template, DOM, events) and render the UI region.

Here are some points to drill into your mind about Backbone views.

2.2 - Subclassing and Creating a Backbone.View

To create a generic view, we only need to instantiate an instance from Backbone.View.

var myView = new Backbone.View();

However, it is more likely that you will be extending/subclassing the base Backbone.View before you instantiate an instance so that you can add your own domain specific properties.

Below I extend the Backbone.View constructor creating a sub-constructor (i.e. subclass) and define some contrived domain specific properties for instances to inherit.

Notes:

2.3 - Configuring A Backbone.View With Options

When you are extending or instantiating a view, you can pass the following special properties, which are hijacked and purposed by Backbone:

//Extending a view

var MyView = new Backbone.View.extend({

model: {}, 
events: {} || function(){return {}}
collection: {}, 
el: '' || function(){return ''}, 
id: '', 
className: '' || function(){return ''}, 
tagName: '' || function(){return ''}, 
attributes: {'attribute':'value','attribute':'value'}

});

//Instantiating a view

var myView = new Backbone.View({

model: {}, 
events: {} || function(){return {}}
collection: {}, 
el: '' || function(){return ''}, 
id: '', 
className: '' || function(){return ''}, 
tagName: '' || function(){return ''}, 
attributes: {'attribute':'value','attribute':'value'}

});

When the above special view options are passed as options during instantiation, they become properties of the view instance as well as properties of the options property. In the code below I demonstrate access to both.

You should be aware that non-special (i.e. foo) options only show up in the options property.

2.4 - Backbone.View Methods, Properties, And Events

Backbone view instances have the following methods and properties:

We will be examining all of the above methods and properties in this section.

Additionally, views can make use of the following built-in event:

event type arguments passed to callback description
'all' event name this special event fires for any (i.e. not just built in events) triggered event, passing the event name as the first argument

2.5 - Running initialize() Function When Backbone Views Are Instantiated

All view instances when instantiated will invoke an internal initialize function that can be defined when extending Backbone.View. Below we include an initialize function that runs when a view instance is created.

The initialize function is an ideal place to house logic that needs to run when view instances are created.

Notes:

2.6 - Rendering A View

View objects come with a default render() method that is intended to be overwritten. The render function, by design, is intended to house the logic required to construct the view. In the code below I make use of the initialize function to call the instances render function when it is instantiated.

Not much rendering is occurring because we have yet to connect the view to an element node, but clearly the render function should contain the logic that constructs a view.

Notes:

2.7 - Connecting A View To An Element Node

Backbone views are intended to be tied to an element node which is literally either in the html producing the UI or in memory. Typically, when instantiating a view, either the el or tagname property is set connecting the view instance to an element node. Below I demonstrate both of these scenarios.

Connecting a view to an element node in the HTML page using a CSS selector:

Connecting a view to an element node in memory:

You should note that connecting a view to an element node in memory will still define an el property value. The idea is that el is always a node. You can set it to reference a node in the html page using a css selector or you can create an element node in memory by using the tagname property. If you don't provide an el value or tagname value when extending Backbone.View or instantiating a view the el value will default to an empty <div> (i.e. tagname:'div').

Notes:

2.8 - Setting Element Attributes On An Element Node In Memory

The view options id, className, and attributes provide properties which add attribute values to the element node connect to a view when the tagname property is used. Below I use all three of these special properties to set attribute values on the node in memory that is connected to myViewInstance.

Notes:

2.9 - Using The jQuery-Wrapped el Shortcut (aka this.$el)

Regardless if the element connect to a view (i.e. el) is in memory or in an the html page Backbone will setup a jQuery wrapper around the element so that you don't have to. It is basically a shortcut for creating your own wrapper around the element (i.e. jQuery(this.el);) that the view is connected too. In the code example below I verify that this.$el contains my el wrapped with jQuery methods.

2.10 - Using the jQuery-Wrapped el Scoped Shortcut (aka this.$())

Backbone provides a short for performing jQuery functions scoped to the element the view is connected too. This sounds complicated but using this.$() is simply a shortcut for not having to write $(this.el).find() or this.$el.find() every time you want to perform jQuery tasks on element nodes contained in the view. In the code below I make use of this.$() selecting the <span> contained inside of myViewInstance.

Notice that by using this.$() we are confining our jQuery selecting to the children element nodes of el. This is why the only <span> that is given the bold CSS styling is contained inside of the el (i.e. <div id="myView"></div>).

2.11 - Setting Delegated Events For A Backbone View Using The events Property

Backbone will wire up events for a view, which are delegated from the el node, from an events object that can be set when extending the Backbone.View or instantiating a view. In the code below I provide an events object when extending Backbone.View that adds a click and mouseover event to the <button> element in the view.

The format for defining the event and the node inside of the view (i.e. inside of el) the event is attached, is as follows:

// event: {'event selector' : 'callback function'}

//Example based on previous JSFiddle below:

'click button': 'sayHi'

//notice the space divides the event from the selector

The event and the selector are the same values that jQuery normally takes as parameters when using the jQuery().on() method to do event delegation.

Notes:

2.12 - Setting The Element Node Associated With A View Using setElement()

Using the setElement() method available to view instances we can change the node element that is associated with the view. In the code below myViewInstance is initially associated to the <div> in the page with an id of "myView". Using the setElement() method the myViewInstance view is set so that the node element that is associated with the view is now the <div> with an id value of "anotherMyView".

When using the setElement() method on a view, you should keep in mind that Backbone will remove any events on the previous element node and set them up on the new node you are moving the view too (i.e. delegateEvents() is called). In our code example above this would explain why the second "Say Hi!" button is functional while no longer responds to click events.

2.13 - Removing A View From The DOM Using remove()

The Backbone.View remove() method invokes the jQuery remove() method on the element node that is associated with the view. The reason that you'd pick this method over simply calling the jQuery method (i.e. this.$el.remove();) is that it will also call the stopListening() event on the view instance, removing all listeners set on the view.

In the code example below I set up the rendered view to remove itself if it is clicked. Clicking on the "invoke remove()" button will cause the button itself to be removed.

Notes:

2.14 - Attaching Delegated Events Using delegateEvents()

By default, Backbone will internally call delegetEvents() when a view is instantiated, referencing the events option object for any events that need to be setup. If, for whatever reason, the events attached to a view are removed, consider the delegateEvents() method the tool for refreshing/reattaching the events to the view.

To demonstrate the delegateEvents() method, in the code below, I render a view without setting up events during extending or instantiate. After the view is created, I update the views events property so that the sayHi callback is invoked when the <button> is clicked. Then I use the delegateEvents() method to attach any events in the events object to my view.

Notes:

2.15 - Using Templates With Views

Backbone does not force you into using a specific templating engine or pattern for rendering a view. However, a templating solution for rendering complicated bits of HTML from JSON data is provided by underscore.js. I am showing a small example of how templates in underscore.js (really lodash.underscore.js) can be used.

Don't take the lack of in depth coverage on the topic of templates here to indicate the commonality of templates. Templates, in fact, are common if not almost always used when building Backbone applications. Really any web application! When rendering a view a templating engine should always be considered so that views do not become stuffed with DOM manipulation logic.

3 - Backbone.Model

3.1 - Conceptual Overview Of A Backbone.Model

A Backbone Model is likened to a table structure with column headers and rows of data. A Backbone.Model object defines the column labels and wraps the data (i.e. attributes) in each row with pre-defined and custom methods for data conversions, validations, and access control. Creating model instances from Backbone.Model or an extended Backbone.Model, provides the object to which the actual data is stored. For example, a model for a contact in a contacts application might look something like the following:

first name last name phone
John Doe 111-111-1111

You can think of a Backbone.Model constructor as the column headers and the methods and properties common to each row of data. An instance created from the constructor is likened to populating the above table with actual data.

//i.e.
new Backbone.Model(
   {firstName:'John',lastName:'Doe',phone:'1-111-1111'}
);

Keep in mind that out of the box Backbone.Model provides properties and methods (e.g. get('phone')) for operating on each row and facilitates the ability to define your own methods and properties (e.g. getFullName())

What's been summarized thus far is only part of the nature of a Backbone model. In Backbone, the other part involves the logic for syncing data over HTTP through a restful JSON api using AJAX. In this article we are going to save the details pertaining to syncing for another time. This article will focus on the life cycle of a model (and collection of models) without clouding this objective with syncing.

3.2 - Subclassing and Creating a Backbone.Model

To create a generic model with data we only need instantiate an instance from Backbone.Model and pass in the values (or what Backbone calls attributes) the model will store. For example, below I create two contact models, one for John Doe and the other for Jane Doe.

var contact1Model = new Backbone.Model(
    {firstName:'John',lastName:'Doe',phone:'111-111-1111'}
);

var contact2Model = new Backbone.Model(
    {firstName:'Jane',lastName:'Doe',phone:'222-222-2222'}
);

However, it's more likely that you will be extending/subclassing the base Backbone.Model before you instantiate an instance so that you can add your own domain specific properties.

Below I extend the Backbone.Model constructor, creating a sub-constructor, (i.e. ContactModel) and define the getFullName() method so all models created from ContactModel inherit the getFullName() method.

Notes:

3.3 - Backbone.Model Methods, Properties, And Events

Backbone model instances have the following methods and properties:

The methods and properties in bold will not be discussed in this article. In the next article we will deal with the methods and properties in bold that pertain to syncing.

Additionally, models can make use of the following built-in events:

event type arguments passed to callback description
'all' event name this special event fires for any (i.e. not just built in events) triggered event, passing the event name as the first argument
'change' model, options when a model's attributes have changed.
'change:[attribute]' model, value, options when a specific attribute has been updated.
'destroy' model, collection, options when a model is destroyed.
'error' model, xhr, options when a model's save call fails on the server.
'invalid' model, error, options when a model's validation fails on the client.
'request' model, xhr, options when a model or collection has started a request to the server.
'sync' model, resp, options when a model (or collection) has been successfully synced with the 

3.4 - Setting Default Model Values/Attributes

When extending a model, you can provide a set of default values that are shared among all instances. In the code below I setup a default value for firstName and lastName.

If you would prefer that instances not reference the same default object, you can provide a function value for defaults that will create unique default values (i.e. not referencing that same default object).

In the code example above, each instance will now make use of a unique defaults object, instead of each instance referencing the same defaults object.

3.5 - Setting, Verifying, Getting, UnSetting, and Clearing Model Data

Backbone provides a generic set of methods (set(), has(), get(), unset(), clear()) for working with model data. Below I demonstrate each of these methods.

Notes:

3.6 - Accessing A Models Data/Attributes

Backbone provides the attributes property which gives direct access to the internal object containing a models data.

While Backbone provides direct access to this object, it is not common for this object to be manipulated directly. In fact, don't do it. If you want to edit model data use set(), get(), unset(), clear() or make a copy (e.g. model.toJSON();) of the attributes object, edit that, then update the model using set().

Notes:

3.7 - Getting A Copy Of A Models Data/Attributes Using toJSON()

The toJSON() model method returns a copy of the attributes internal data object.

This can be handy when you need a clean copy of the object for a template.

Notes:

3.8 - Filtering Model Data

Backbone proxies 6 methods (keys(), values(), pairs(), invert(), pick(), omit()) from the underscore.js library to be used directly on model data. These functions provide aid when filtered states of model data are needed. Below I demonstrate the four most used methods.

3.9 - Listening To Model change Events

Anytime a change occurs with a model's data a change event is broadcasted so that typically a view can listen for such an event.

In the following code example, a model is created and then a view is created to listen for changes on the model using the change event. Any changes to the model data will trigger a change event and then ideally a re-render.

It is possible to listen only for a specific attribute change in a model by indicating the name of the attribute along with the change event when the event listener is setup (i.e. 'change:[ATTRIBUTE]'). For example, in the code below, I have changed the previous code example to only listen for changes on the firstName attribute in the contact model.

3.10 - Verify if a Model Has Changed And What Changed Using hasChanged() and changedAttributes()

The hasChanged() and changedAttributes() model methods provide the ability to determine if something has changes since that last change event and specifically what changed.

In the following code, I set up a contact model and then immediately changed the phone number attribute.

By changing the phone number in the above code example, the internal change event is called for the first time and the hasChanged() method will return a boolean indicating that the model has changed. To get an object containing the attributes that have change invoke the changedAttibutes() method on the model.

3.11 - Getting Previous Attribute Values Using previous() and previousAttributes()

The previous() and previousAttributes() method can be used to get the value of an attribute prior to the last change event. In the following code example, I set up a contact model, change the phone and firstName attributes and then use the previous() and previousAttributes() method to retrieve the prior value for the phone attribute and the state of all mode attributes before the last change event.

3.12 - Validating Model Data Before Its Set or Saved

When setting or saving model data (we will talk about saving in the next article) a validation function can be invoked on the data validating its quality before anything is set or saved.

By default, the validate property is left undefined. To define a validate function on a model pass validate:function(attributes,options){} to extend() or directly update the validate property on a model instance. To trip a validation error simply return any value from the function except false.

In the code below, I set up a validation function for phone numbers and make sure any invalid phones numbers can not be set().

The last value returned from the validation function that is not false, can be referenced from the models validationError property. In the code above we return an invalid message from the validation function and then accessed the validationError property to log this message.

Notes:

3.13 - Listening To A Model's invalid Event

The built in invalid event will be broadcasted when a model's validate function fails. In the code below I demonstrate listening for this event and then log the parameters passed to the invalid callback function.

3.14 - Manually Run validate On Model

The validate function can be manually invoked by calling the isValid() method. In the code example below the validate function is set up when extending the Backbone.model constructor so that all model instances can make use of the validate function. To make sure that data is always validated, I am running the validate function when the initialize function is called to catch any invalid data that is passed during the creation of the model instance.

Notice that the isValid() method returns a helpful boolean indicating if the model is valid or not.

Notes:

4 - Backbone.Collection

4.1 - Conceptual Overview Of A Backbone.Collection

A Backbone collection represents a logical grouping of models and provides methods and properties for working with (grouping, sorting, filtering) these groups of models. To finalize the contact illustration I have been using in this article (i.e. a model = row of labeled data for a contact in a table), you can think of a collection as the entire table that contains rows of contact data. Below I have updated the table containing contact details discussed at the start of the model section so that the entire table can be considered a contacts table.

Contacts:
first name last name phone
John Doe 111-111-1111

Of course, a Backbone.Collection is much more than a label like the previous table might express. Collections can capture events (change, destroy, request, sync, error, invalid) that are triggered on its models and collections also have their own set of useful events (e.g. add, remove, reset, sort, request, sync).

Notes:

4.2 - Creating a Backbone.Collection From Model Instances

To create a generic collection we only need instantiate an instance from Backbone.Collection and pass in during instantiation an array of model instances. In the code example I take the contact models we have been working with in this article and pass them as the first option when creating a collection.

Notes:

4.3 - Creating a Backbone.Collection From A Model Constructor

A common pattern for creating collections is to point the collection model property at a model constructor and then instead of passing an array of references to model instances, the collection itself can instantiate models from raw data and store these instances in the collection. In the code below an array of raw data and a model constructor (i.e. ContactModel) is passed to the Backbone.Collection constructor when instantiating a collection.

The results are exactly the same as passing in a reference(s) to models already created, but by using the model property the collections can do some of the setup for us.

Notes:

4.4 - Backbone.Collection Methods, Properties, And Events

Backbone collection instances have the following methods and properties:

The methods and properties in bold will not be discussed in this article. In the next article we will deal with the methods and properties in bold that pertain to syncing.

Additionally, collections can make use of the following built-in events:

event type arguments passed to callback description
'all' event name this special event fires for any (i.e. not just built in events) triggered event, passing the event name as the first argument
'add' model, collection, options when a model is added to a collection
'remove' model, collection, options when a model is removed from a collection
'reset' collection, options when the collection's entire contents have been replaced.
'sort' collection, options when the collection has been re-sorted
'request' model, xhr, options when a model or collection has started a request to the server.
'sync' model, resp, options when a model (or collection) has been successfully synced with the 

Notes:

4.5 - Getting All Model Data/Attributes Out Of A Collection Using toJSON()

The collection toJSON() method will return an array containing a copy of the internal attributes for each model in a collection. In the code below I log an array that contains all of the attributes for each model in the collection using toJSON().

4.6 - Getting Models From A Collection Using models, get(), at(), where(), and findWhere()

Direct access to the array that contains all the model references for a collection is provided by themodels property. We have used this property already in this section, but to demonstrate its value, examine the code example below which extracts the array of model instances (i.e. model objects) from a collection using the models property.

The where() method is provided so that the models array can be filtered by attributes. In the code example below I use where() to get an array of only the models in the collection that have an attribute of {firstName:'Doe'};

Using the get(), at(), and findWhere() collection methods we can get a single model reference instead of an array of model references. These three methods are demonstrated in the code below.

Notes:

4.7 - Sorting A Collection

By default a collection does not sort itself. Models are simply in the order that you add them. To have a collection keep a sort order the comparator property should be defined as a sortBy function taking a single argument, as a sort function taking two arguments, or as a string indicating the attribute to sort by. In the code example below we pass the comparator value during collection creation so that the collection will keep models sorted by firstName.

Notes:

4.8 - Getting an Array of Model Attributes And Values From Each Model in a Collection Using Pluck()

The pluck() method is extremely handy for creating an array of all of the values for a specific attribute in a collection of models. For example, in the code below I set up a collection with three models, that all contain a firstName attribute. To extract the firstName value for each model I call the pluck('firstName') method on the collection.

4.9 - Adding Models To A Collection Using add(), push(), unshift()

The add() method for adding models to a collection can add a single instance of a model or an array of model instances. If the collection has a value for its model property, you can pass raw data/attribute objects or an array of raw data attribute objects. In the code example below I demonstrate all of these methods for adding a model to a collection.

The push() and unshift() methods function identically to the add() method, and taking the same parameters, except that these methods will either add the model(s) to the end (i.e. push()) of the collection or to the beginning (i.e. unshift()) of the collection.

Notes:

4.10 - Removing Models From A Collection Using remove(), pop(), shift()

The remove() method can be used to remove a model(s) from a collection by passing references to the models that need to be removed. Below I demonstrate the removal of a single model as well as a group of models from a collection.

The pop() and shift() are shortcut methods for removing models. Calling pop() will remove the last model and shift() will remove the first model.

Notes:

4.11 - Add, Merge, and Remove Models At The Same Time Using set()

The set() method will update the state of the models in a collection by attempting to match the changes indicated by the array of model instances passed to the set() method. In other words, using set() will attempt to synchronize the array of models passed in with the internal state of the models in the collection by intelligently deciphering the differences and adding, removing, and merging according to the differences.

In the code example below I set up a collection and then using set() I add to the collection, remove a model from the collection, and update a model in the collection all using set().

Notes:

4.12 - Replacing All Models In (i.e. bulk replace/reset) A Collection Using reset()

The reset() method can be used to replace the models in a collection with a new set of model(s). It is essentially like removing all model(s) in a collection and adding a new model(s).

Notes:

4.13 - Using underscore.js (or lodash.js) Methods On Models (i.e. models) In A Collection

A collection instance has access to many useful methods for operating on the array of models the collection instance contains. But you should be aware that Backbone mixes into to the prototype chain of a collection the following underscore.js (or lodash.js) methods which can be useful as well. (e.g. myCollectionInstance.first() same as myCollectionInstance.at(0)).

In the code example below I make use of the each() and first().

5 - Building A Simple Contacts Application

5.1 - Overview

Imagine for a moment that you are in your favorite spreadsheet tool. You create a spreadsheet called "contacts" (i.e. a collection). Next you define column headers in the spreadsheet, so you add the headers "first name", "last name" and "phone" (i.e. a model). Basically what you have is a spreadsheet housing contact data (i.e. {firstName:'Jane, lastName:'Doe', phone:'111-111-1111'}). Now, imagine you want to use this information from the spreadsheet to create a UI (i.e. a view) to view/add/delete contacts. Backbone can do that! Let's do it.

5.2 - Extend Backbone.Model creating Contact Constructor/Class

var Contact = Backbone.Model.extend({
    defaults: {
        firstName: null,
        lastName: null,
        phone: null
    },
    getFullName: function(){
        return this.get('firstName') +' '+ this.get('lastName');
    }
});

5.3 - Instantiate contacts Collection, Passing A Model Constructor/Class and A Contact

var contacts = new Backbone.Collection({ //seed it with some data
    firstName: 'Jane',
    lastName: 'Doe',
    phone: '111-111-1111'
}, {
    model: Contact
});

5.4 - Extend Backbone.View creating AddContactsView & Instantiate An Instance

var ContactListView = Backbone.View.extend({
    el: '#contacts',
    events: {
        'click li button': 'removeContact'
    },
    initialize: function () {
        this.render(); //render list
        this.listenTo(contacts, 'add remove', this.render); //the magic
    },
    removeContact: function (e) {
        $(e.target).parent('li').remove();
        contacts.findWhere({
            firstName: $(e.target).parent('li').find('span').text().split(' ')[0].trim(),
            lastName: $(e.target).parent('li').find('span').text().split(' ')[1].trim()
        }).destroy(); //this will invoke the internal 'remove' event
    },
    render: function () {
        if (contacts.length > 0) {
            this.$el.empty();
            contacts.each(function (contact) {
                this.$el.append('<li><span>' + contact.getFullName() + '</span>'+' / '+ contact.get('phone') + '<button type="button" class="btn-xs btn-danger removeContactBtn">X</button></li>');
            }, this);
        }
    }
});
var contactListViewInstance = new ContactListView();

5.5 - Extend Backbone.View creating ContactListView & Instantiate An Instance

var AddContactsView = Backbone.View.extend({
    el: 'fieldset',
    events: {
        'click button': 'addContact'
    },
    addContact: function () {
        var firstName = this.$('#firstName').val();
        var lastName = this.$('#lastName').val();
        var phone = this.$('#phone').val();
        if (firstName && lastName && phone) {
            contacts.add({ //this will invoke the Backbone internal 'add' event
                firstName: firstName,
                lastName: lastName,
                phone: phone
            });
            this.$('input').val('');
        }
    }
});
var addContactsViewInstance = new AddContactsView();

5.6 - Working Demo

Below is a working demo of the contacts application discussed in this section. Examine the application code until a complete understanding of the role and function each part (i.e. model, collection, views) plays in the creation of the application. Refer back to section 2, 3, or 4 of this article as needed.

6 - Conclusion

It was the intention of this article to give the reader a thorough understanding of a Backbone view, model, and collection. In the next part of this article we'll examine syncing and the methods associated with syncing models and collections to a backend.