In this tutorial, we're going to look at building a fully functional contacts manager using Backbone.js, Underscore.js, and jQuery. We'll take a look at the basic components that make Backbone tick as well as some of the convenience methods exposed by Underscore.
What Exactly Are All These Libraries?
Backbone is an architectural framework that allows us to easily create non-trivial JavaScript applications using MVC-style organisation and structure. Backbone isn't considered true MVC – C is for Collection not Controller, but it still offers many of the same benefits and enables us to write powerful yet maintainable code.
Underscore is a utility library that provides enhanced functionality to JavaScript, adding additional functions for working with arrays, collections, functions and objects.
I'm sure jQuery needs no introduction here.
Getting Started
We'll need a root project folder containing css
, img
and js
subfolders, so go ahead and create these now. We'll start out with the following HTML page:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Backbone.js Web App</title> <link rel="stylesheet" href="css/screen.css" /> </head> <body> <div id="contacts"></div> <script src="js/jquery-1.7.1.min.js"></script> <script src="js/Underscore-min.js"></script> <script src="js/Backbone-min.js"></script> <script src="js/app.js"></script> </body> </html>
Save this as index.html
in the root project folder. Backbone's only mandatory requirement is Underscore.js, but we'll also want to make use of jQuery so we link to these two libraries before Backbone. Our application's code will go into app.js
and any styles will go into screen.css
. On the page, we've got an empty container that will form the basis of our application.
Next we can create the skeletal JavaScript file that we'll gradually fill out over the course of this series. In a new file add the following code:
(function ($) { var contacts = [ { name: "Contact 1", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" }, { name: "Contact 2", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" }, { name: "Contact 3", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "friend" }, { name: "Contact 4", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "colleague" }, { name: "Contact 5", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" }, { name: "Contact 6", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "colleague" }, { name: "Contact 7", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "friend" }, { name: "Contact 8", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" } ]; } (jQuery));
Save this in the js
folder as app.js
. We'll put all of our code in an anonymous function which we invoke immediately, aliasing jQuery as the $
character. Also defined at this stage is an array of objects where each object represents a contact.
We'll use this local data store for this part of the tutorial as it allows us to get some script up and running without worrying to much about syncing with a server. We'll save that for another day!
Models
A model represents the data of an application; in our application this will be an individual contact, which will have attributes such as a name, a contact number, etc. You could say that an individual model represents the atomic part of the application – the smallest possible unit of functionality. Add the following code directly after the data array:
var Contact = Backbone.Model.extend({ defaults: { photo: "/img/placeholder.png" } });
To create a model in Backbone we just extend the Backbone.Model
class using the extend()
method. We can pass an object into the method which allows us to customize the model with our own functionality. One of the properties we can set within this object is called defaults
. This property allows us to configure default values for any attribute that we would like our models to have.
In this case, we set a placeholder image as the default value of the photo
attribute for model instances. Any models that don't have this attribute when defined will be given it.
Models have other properties that we can use to add functionality; we could define an initialize()
method, and this method would be invoked automatically by Backbone for us when each model is initialized. We won't make use of this at present, but don't worry, we'll come back to models a little later on.
Collections
A collection is a class for managing groups of models. We'll use a simple one in this example to store all of our contacts. Add the following code directly after the Contact
model:
var Directory = Backbone.Collection.extend({ model: Contact });
Like a model, a collection is a Backbone class that we extend to add custom functionality specific to our application. Collections also have an extend()
method, and it accepts an object that allows us to set properties of the class and add behaviour. We use the model
property to tell the collection what class each item in the collection should be built from, which in this case is an instance of our Contact
model. Don't worry that the classes we've defined so far seem extremely simple, we'll be coming back and adding additional functionality in later parts of the tutorial.
Views
Views are responsible for displaying the data of the application in an HTML page. One of the benefits of separating out the parts of the application that process the data and the parts that display the data is that we can very easily make a change to one, without requiring extensive changes to the other. We'll use a couple of views in our application, the first of which should be added directly after the Directory
class:
var ContactView = Backbone.View.extend({ tagName: "article", className: "contact-container", template: $("#contactTemplate").html(), render: function () { var tmpl = _.template(this.template); this.$el.html(tmpl(this.model.toJSON())); return this; } });
This view handles displaying an individual contact. Just like models and collections, views have an extend()
method used to extend the Backbone.View
class. We set several instance properties in our view; the tagName
property is used to specify the container for the view and the className
properties specifies a class name that is added to this container. We'll use a simple template in our HTML file to render each contact, so the template
property stores a cached reference to the template, which we select from the page using jQuery.
Next we define a render()
function; this function isn’t invoked automatically by Backbone and while we could call it from the automatically invoked initialize()
method to make the view self-rendering, we don’t need to in this case.
Within the render()
method we store a reference to Underscore's template()
method and pass to it the stored template. When passed a single argument containing a template Underscore doesn't invoke it immediately but will return a method that can be called in order to actually render the template.
We then set the HTML content of the <article>
element created by the view to the interpolated template using jQuery's html()
method for convenience. This is done by calling the templating function that Underscore returned previously and passing it the data to interpolate. The data is obtained from the model using Backbone's toJSON()
method on the model. Interpolating just means that the tokens within the template are replaced with actual data. Notice also that we use $el
to set the HTML content; this is a cached jQuery object representing the current element so that we don’t have to keep creating new jQuery objects.
At the end of the render()
method, we return the this
object, which points to the view instance that the render()
method is called on. This is so that we can chain other Backbone methods to the view instance after calling its render()
method.
Micro Templating With Underscore
Now would probably be an appropriate time to look at Underscore's built-in micro-templating facitlities. Underscore provides the template()
method as we saw to consume and interpolate templates. To the HTML page we should add the template that we will use; add the following code directly after the contacts container <div>
:
<script id="contactTemplate" type="text/template"> <img src="<%= photo %>" alt="<%= name %>" /> <h1><%= name %><span><%= type %></span></h1> <div><%= address %></div> <dl> <dt>Tel:</dt><dd><%= tel %></dd> <dt>Email:</dt><dd><a href="mailto:<%= email %>"><%= email %></a></dd> </dl> </script>
We use a <script>
element with an id
attribute so that we can easily select it, and a custom type
attribute so that the browser doesn’t try to execute it. Within the template we specify the HTML structure we would like to use, and use tokens to specify where the model data should be inserted. There are a couple of other features we can make use of with Underscore including interpolating HTML-escaped values, or executing arbitrary JavaScript, but we don’t need to make use of these for the purpose of this tutorial.
A Master View
To finish off this part of the tutorial we going to create one more view. Our current view represents each individual contact so is mapped to a model on a 1:1 basis. But this view isn’t self-rendering and we haven’t invoked it yet. What we need is a view that maps 1:1 to our collection, a master view that will render the right number of contact views to display each of our contacts. Directly after the ContactView
, add the following class:
var DirectoryView = Backbone.View.extend({ el: $("#contacts"), initialize: function () { this.collection = new Directory(contacts); this.render(); }, render: function () { var that = this; _.each(this.collection.models, function (item) { that.renderContact(item); }, this); }, renderContact: function (item) { var contactView = new ContactView({ model: item }); this.$el.append(contactView.render().el); } });
This view will be attached to an element that already exists on the page, the empty container that is hard-coded into the <body>
, so we select the element with jQuery and set it as the el
property. When then define a simple initialize()
function which creates an instance of our collection class and then calls its own render()
method, making this view self-rendering.
We then define the render()
method for our master view. Within the function, we store a reference to the view so that we can access it within a callback function, and then use Underscore's each()
method to iterate over each model in our collection.
This method accepts two arguments (in this form, although it can also be used with just one argument); the first is the collection of items to iterate over, the second is an anonymous function to be executed for each item. This callback function accepts the current item as an argument. All we do within this callback function is call the renderContact()
method and pass to it the current item.
Lastly we define the renderContact()
method. In this method we create a new instance of our ContactView
class (remember, the ContactView
class represents an individual contact) and set its model
property to the item passed into the method. We then append the element created by calling the view's render()
method to the $el
property of the DirectoryView
master view (the empty container we selected from the page). The $el
property is a cached jQuery object that Backbone creates for us automatically.
The master view is responsible for generating each individual model within our collection. All we need to do is initialise our master view, and because it is self-rendering, it will display all of the contacts specified in the array of dummy data:
var directory = new DirectoryView();
When we run this page in a browser now, we should see a visual representation of our data:
Backbone creates an instance of a model for each item in our original array, which are stored in our collection and rendered as an instance of a view.
This tutorial isn't about CSS, so I haven't covered the example CSS at all. It’s just basic CSS, if you’re curious take a look at the style sheet included in the demo archive.
Summary
In this part of the tutorial we've been introduced to some of the core components of Backbone.js; models, collections and views. Models are classes that we can create in order to store data about a particular thing and define behaviour for it. Collections are used to manage groups of models and views allow us to render our models using interpolated templates that display the data from our models.
In the next part of the tutorial we'll take a look at how we can filter our collection to display only a subset of our models. We'll also take a look at another major component of Backbone – Routers.
Comments