At the Forge - Backbone.js

by Reuven M. Lerner

JavaScript is changing. Actually, I'm not sure how much of that is true; the underlying language hasn't changed too much over the years. But, even if the language itself isn't changing, everything else about it is. There's a growing interest in server-side JavaScript, with high-speed applications based on Node.JS (as described in last month's column). Browser-based JavaScript is not only pretty standard, but also executes very efficiently. And, of course, a number of high-quality, open-source JavaScript libraries exist, such as jQuery, MooTools and Prototype, that make it easy to work with JavaScript within the browser.

So, have our JavaScript demons been exorcised forever? Not at all. As applications migrate to the browser, there is a growing interest in making them even more desktop-like. Sure, JavaScript UI libraries make it relatively easy to implement desktop-like functionality, such as drag and drop. But if you want to create a truly sophisticated, desktop-like application, you're going to find yourself lost in a forest of event callbacks, not to mention widgets that might or might not be appropriate for such an application.

Thus, it shouldn't come as a surprise to find that in the last year or two, a new type of Web application has emerged—one written almost purely in JavaScript, which executes inside the browser, and which only occasionally contacts a server. This turns the usual model of Web development—in which the majority of the processing takes place on the server, emitting HTML and JavaScript that handles things until the next call to the server—on its head, making the server little more than a storage facility that stores and retrieves information determined by the browser application.

You could argue that Google Maps, Gmail and Google Docs—to choose three famous examples, but by no means the only ones—have been demonstrating such capabilities for several years. But until recently, it was relatively difficult for average developers to create applications that were heavily based on JavaScript.

Fortunately, things have changed, and in a big way. If you want to create a rich JavaScript application, you have a variety of toolkits from which to choose. The question no longer is whether you can create such an application, but rather, which tools you will use to create it and how it'll talk to the server. Just off the top of my head, I can recall Backbone.js, Knockout, JavaScript MVC, SproutCore, Closure and Cappuccino, and you can be sure that I'm mentioning only a small fraction of the toolkits that exist. It might go without saying nowadays, but I should add that the leading toolkits are all released under open-source licenses, making it possible to download, try and explore each of these libraries without having to worry about licensing restrictions when downloading or deploying them.

This month, I'm starting a series of columns about several of these in-browser application development frameworks, and how you can use them to create richer, more interesting Web applications. In each case, I'll explore how easy it is to get started with the framework, its relative advantages and disadvantages, and discuss how you might have it interact with data on a server.

During the past decade, we have seen a clear trend toward MVC frameworks on the server side that provide RESTful APIs. Ruby on Rails isn't the only framework that has promoted such a development style, but it certainly has pushed developers hard in those directions, making non-REST and non-MVC development frustratingly difficult. It turns out that many of the new, modern JavaScript frameworks also have adopted the MVC model, each in its own way and always with differences between the server-side model that Rails developers might expect.

Using MVC on the browser and on the server (which I like to call MVC-squared, but maybe that's just me) turns a Web application into two separate software systems: one on the server that's basically exposing a RESTful, JSON API to the world, and another in the browser that's consuming a RESTful, JSON API from a server. Decomposing the program into these two parts makes it easier to split the development work across two individuals or groups, as well as to organize the code in a smarter way. I'll have more to say about this in the coming months, as I connect JavaScript applications to back-end storage systems.

This month, I take an initial look at Backbone.js, a very small JavaScript library that has been getting a fair amount of press recently. And, I explain how to build a simple application using Backbone.js, creating functionality that exists completely within the browser.

The Basics

Backbone.js, as I indicated above, follows the model-view-controller (MVC) paradigm that has been used by software developers for several decades, and that has become ubiquitous in server-side Web development during the past few years. An MVC application has three distinct parts: the model (which provides an interface to the data itself), the view (which presents information to the user) and the controller (which directs the user's requests to the right models, and then renders the results in the view). By dividing the program logic along these lines, it becomes fairly obvious where each function should go, making the code more maintainable.

In the MVC world of Backbone.js, the split works in a similar way. You retrieve and store data in a model object, individual methods (and URL routes) are defined in a controller object, and the view shows things in the user's browser.

But, if you're coming from the server-side world, there are some subtle (and not-so-subtle) differences between server-side and client-side MVC. For starters, it's pretty clear in a server-side program that the model retrieves data from a database, either relational or non-relational. By contrast, the model in a JavaScript application can come from...well, it can come from a variety of sources, one of which would be a server-side Web application. I'll look into this more next month; for my examples this month, let's assume that the data is already in JavaScript and doesn't need to be loaded from anywhere.

In a server-side application, the view is actually a combination of HTML, CSS and JavaScript, rather than being a single file or format. Actually, the view doesn't have to be HTML; it also can be XML, JSON or a variety of other formats, from CSV to PDF to images. By contrast, the view in a Backbone.js application typically is going to rewrite a single part of the current page, rather than load an entirely new one.

So with this in mind, let's create a basic Backbone.js application. I've decided to jump onto the social bandwagon and develop a tiny application that lets people look at a list of recipe titles, click on a title that sounds interesting, and then read the contents of the recipe in question. The same principle could apply to an address book, a diary or even an unusually formatted blog.

So, let's start with the data model. Creating a data model in Ruby on Rails (using ActiveRecord) is easy. You define a subclass of ActiveRecord, thus inheriting all of its capabilities. Of course, JavaScript doesn't have a traditional object model with classes and inheritance, so Backbone.js needs to use a different paradigm. Instead, what you do in Backbone.js is invoke the “extend” function on Backbone.Model. Attributes passed to Backbone.Model.extend either are treated as data fields or as methods, depending on whether they're data or functions. For example, if you want to model a single appointment, you could do it as follows:

Appointment = Backbone.Model.extend({
    person: null,
    meeting_at: null,
    note: null
});

Note that you also could define an “initialize” attribute, which would take the place of the default constructor method. In this particular case, I'm not planning to do anything fancy, which means I can use the default. To create a new appointment, you can say:

var new_appointment =
new Appointment({person: 'Barak Obama',
         meeting_at: '2011-jul-14',
         note: 'Meet with the president'});

You also can replace individual attributes within an appointment:

new_appointment.set({person: 'Joe Biden'});

Or, you can retrieve an attribute from an appointment:

new_appointment.get('person');
Collections and Controllers

Of course, most people have to schedule more than one appointment, which means that this example program needs to keep track of more than one at a time. Now, you normally might assume that you simply could store more than one appointment in a JavaScript array. But, in the world of Backbone.js, you actually use a special kind of object, known as a collection, to store appointments.

Why a collection and not simply an array? Mostly because it works together with other items in Backbone.js. For example, you can set things such that whenever you add or remove an element to your collection, it automatically will invoke another method. For another, collection objects incorporate the Underscore library for JavaScript, which defines a number of methods from functional programming, such as map and pluck, so retrieving information from your collection is quite straightforward.

Just as you defined a model by extending Backbone.Model, you define a collection by extending Backbone.Collection:

Appointments = Backbone.Collection.extend({
  });

Any attributes that you define on the collection are then available, as data or functions, on collection objects of this type. In this particular case, I defined two different attributes, the initialize constructor and the update_appointment_counter method:

Appointments = Backbone.Collection.extend({

  update_appointment_counter: function() {
      $("#number-of-appointments").html(this.length);
  },

    initialize: function(models, options) {
      $("#number-of-appointments").html(this.length);

      this.bind("add", options.view.add_appointment_row);
      this.bind("add", this.update_appointment_counter);
  }

});

In this case, the constructor uses jQuery to initialize the appointment length counter (to zero, given that the collection is only now being initialized) and then adds two handlers to the “add” event. Each time you add a new appointment to this collection, two different functions will fire. One of them (options.view.add_appointment_row) will add a new row to the HTML table containing a list of appointments, and the other (this.update_appointment_counter) updates the counter. As you can see, the functions can be defined in a variety of places; it probably would have made more sense to put both of these methods on the view.

Experienced JavaScript programmers know what “this” is; thus, this.update_appointment_counter makes sense. But, what is options.view? Well, it might help to see how you create your collection, inside the view constructor:

initialize: function() {
  this.appointments = new Appointments(null, {view:this});
},

Basically, you're saying that the appointments attribute for the view is an Appointments collection, starting with no data. Passing a second parameter allows you to set one or more options in a JavaScript object, which is then available as “options”. Because the view passes itself (!) as the “view” option when creating the collection, you then can access the view from within the collection as options.view.

The upshot is that your view, thus, has access to your collection (as this.appointments), and your collection has access to our view (as options.view). This sort of simple, two-way communication is typical for Backbone.js, which tries to make things as simple and short as possible.

The code doesn't include a controller. That's because controllers are necessary only if you want to provide a number of different URLs—well, fragments at the end of a URL—that invoke different methods. For now, you can do without it, but a larger application certainly will require it.

Views

As always in the MVC paradigm, the view is where things are displayed to (and interact with) the end user. In the Rails world, a view is almost always rendered by the controller; your application doesn't need to create it explicitly. In the Backbone.js world, a view is just another object that can be created, often by a model, and which has many controller-like functions. You create it, as you might expect, with:

AppView = Backbone.View.extend({
});

So, you can think of Backbone.js views as fragments of HTML that are added to the current page, plus some of the functionality that you might associate with a controller. Each view is associated with a DOM element. By default, it's a regular “div” element, but you either can set it in one place (using the “el” attribute), or you can set it using a combination of the “tagName”, “className” and “id” attributes as well.

As with models and collections, you can use the “initialize” constructor to set up one or more objects. In the case of this example application, you'll initialize your Appointments collection without any element members, as you saw above when I discussed that collection.

You also will define an event handler, such that clicking on the “add-appointment” button will do so:

events: {
  "click #add-appointment": "add_appointment"
},

When you click on the button, the following code is executed:

add_appointment: function() {
var person = $("#new-appointment td input[name=person]").val();
var meeting_at = $("#new-appointment td 
 ↪input[name=meeting_at]").val();
var note = $("#new-appointment td input[name=note]").val();

this.appointments.add({person: person, meeting_at: meeting_at, 
 ↪note: note});
},

In other words, when you click on the “add-appointment” button, the “click” event handler executes the add_appointment function. This function grabs the values from the little form and uses those values to instantiate a new appointment, adding it to the collection of appointments.

But, you also have event handlers running on the collection! The first handler updates the appointment counter, and the second adds a new row to the table of appointments. It adds the row by cheating a little bit. Although it would have been more elegant to have a second view with an element of “tr” that would add a new row, I decided to mimic some of the on-line tutorials I've seen, adding a new row in a slightly simpler way—namely, an ugly text string.

If I weren't interested in creating an entirely new view, I could have used the “template” function that Backbone.js inherits from underscore.js, giving me ERb-like templates that can be filled in more nicely. Something else that I could have done is break this application into smaller pieces. Although it's nice to have everything in a single file when working on something small, a larger Backbone.js application could well be put into multiple files, with each file defining a different object. Developers experienced with any modern server-side MVC framework, such as Rails or Django, will understand the advantages of putting things into separate files.

Listing 1. appointments.html

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/
↪1.4.4/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/
↪1.1.4/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/
↪0.3.3/backbone-min.js"></script>

<title>Appointments</title>
</head>
<body>
<h1>Appointments</h1>

<table>
<tr>
<th>Person</th>
<th>Date/time</th>
<th>Note</th>
</tr>
<tr id="new-appointment">
<td><input type="text" name="person" /></td>
<td><input type="text" name="meeting_at" /></td>
<td><input type="text" name="note" /></td>
</tr>
<tr align="center">
<td colspan="3"><input type="button" id="add-appointment" 
 ↪value="Add Appointment"/ ></td>
</tr>
</table>

<hr />

<p>Number of appointments: <span id="number-of-appointments">
 ↪</span></p>

<table id="appointments">
<tr>
<th>Person</th>
<th>Date/time</th>
<th>Note</th>
</tr>
</table>

<script type="text/javascript">
     (function ($) {

     Appointment = Backbone.Model.extend({
         person: null,
         meeting_at: null,
         note: null
     });

     Appointments = Backbone.Collection.extend({

       update_appointment_counter: function() {
           $("#number-of-appointments").html(this.length);
       },

         initialize: function(models, options) {
           $("#number-of-appointments").html(this.length);

           this.bind("add", options.view.add_appointment_row);
           this.bind("add", this.update_appointment_counter);
       }

     });

     AppView = Backbone.View.extend({
       el: $("body"),

       initialize: function() {
         this.appointments = new Appointments(null, {view:this});
       },

       events: {
         "click #add-appointment": "add_appointment"
       },

       add_appointment: function() {
           var person = $("#new-appointment 
           ↪td input[name=person]").val();
           var meeting_at = $("#new-appointment 
           ↪td input[name=meeting_at]").val();
           var note = $("#new-appointment 
           ↪td input[name=note]").val();

           this.appointments.add({person: person, 
           ↪meeting_at: meeting_at, note: note});
       },

       add_appointment_row: function(model) {
           $("#appointments").append("<tr><td>" + 
           ↪model.get('person') + "</td>" +
   "<td>" + model.get('meeting_at') + "</td>" +
   "<td>" + model.get('note') + "</td></tr>");
       }
     });

     var appview = new AppView;

     })(jQuery);
</script>

</body>
</html>
Conclusion

Backbone.js is one of the smallest and easiest-to-understand MVC frameworks for JavaScript applications. It has become quite popular, as evidenced by the number of blog posts about it in the past few months. The support that its authors, Jeremy Ashkenas and others at DocumentCloud, have offered to many Backbone.js users has been quite impressive to see as well.

Although this column obviously didn't go into great depth about Backbone.js, one shortcoming in this application should have been obvious. What happens when the user wants to store data? Right now, the appointment calendar is not only simple-minded in its interface and execution (for example, there's no way to look at just today's appointments, let alone remove or edit existing ones), but it also fails to provide persistent storage.

Next month, I'll discuss how you can connect a Backbone.js application to a persistent back-end database or server-side MVC application (thus providing an MVC-squared solution), giving users and developers the best of both worlds—flexible development with dynamic JavaScript, but with a robust back end that can persist data easily.

Resources

The home page for Backbone.js is on GitHub, at documentcloud.github.com/backbone. This page points not only to the code, but also to some tutorials and documentation. In a step that I hope many other authors will follow, the authors of Backbone.js put up a copy of the source code, thoroughly commented in a beautiful format, at documentcloud.github.com/backbone/docs/backbone.html.

I encourage anyone interested in Backbone.js to read through the code and comments. I certainly learned some things about Backbone.js in particular and JavaScript in general from reading through this code.

A number of tutorials and blog postings describe how to do interesting things with Backbone.js. A short and to-the-point tutorial is at www.plexical.com/blog/2010/11/18/backbone-js-tutorial.

A more involved example by Alex Rothenberg, who packaged up this work as a Ruby gem, is at www.alexrothenberg.com/2011/02/11/backbone.js-makes-building-javascript-applications-fun.html.

Finally, an excellent two-part tutorial on Backbone.js is available at liquidmedia.ca/blog/2011/01/backbone-js-part-1 and liquidmedia.ca/blog/2011/01/an-intro-to-backbone-js-part-2-controllers-and-views.

Reuven M. Lerner is a longtime Web developer, architect and trainer. He is a PhD candidate in learning sciences at Northwestern University, researching the design and analysis of collaborative on-line communities. Reuven lives with his wife and three children in Modi'in, Israel.

Load Disqus comments