Sunday, January 13, 2013

Building Web Applications with Backbone - Getting Started

If you're reading this post, you, like me, have already decided to give Backbone a try. You probably looked at the Backbone reference and now are asking yourself - "This looks great! But how do I do use this to make my development easier and my application better?". I often feel this way when reading through a reference document. Its generally not organized into why use this thing here or these two things are designed to work together so use them this way. I'm not going to write a guide here, however, because I found a reasonable reference as a starting point. I recommend looking at the early release of the O'Reilly book Developing Backbone.js Applications. I don't know how long it will be there, but if you have to pay for a digital copy, it is a worthwhile read.

I will say that after the little prototype reporting application I built, I really do like the structure of the Backbone library. I found the implementation of the MVC pattern not so overly strict that I did not feel like I would have any flexibility. In fact, some will find it to be so loosely coupled that they may find it does not fully meet their needs. The real power of the library is a combination of binding events to models and leveraging Underscore to work with collections of models. The router and view components provide basic functionality to connect model events and render data to the screen.

As you start working with Backbone views, you will quickly see that you are writing some of the same basic code over and over again. Because the library makes no assumptions about how you will get data on the screen, you have to wire that all up in each view. Once you pick a methodology for what templates you will use and how you will affect changes to the DOM, each view will have a similar starting point. For larger applications, you can either build some super classes to abstract these functions away from each view or find another library that extends Backbone with these features. Marionette is a good example of a library which adds extra structure to Backbone and can reduce the amount of boilerplate code plus help organize your application (its more opinionated than Backbone on how to layout an application's components).

For my needs, I don't a lot of extra structure or assumptions about how I want to manage my data and rendering. My first goal was to just get some models setup with data and use a view to render this data to the screen. From there, I started layering on the functionality I wanted and attempted to leverage the built-in tools of Backbone when appropriate. As I worked on this project, it was apparent that since Backbone is not forcing you into any specific ways of rendering a collection of data, you can get pretty lazy and eventually find yourself refactoring chunks of your code so you are, again, actually using the features Backbone provides.

As an example, I setup a sample dataset that I wanted to render into a table. My ultimate goal with this table will be to make it sortable, add some animated graphs, and provide some interactive features to explore the data. Starting out, I found a pattern for using Underscore template partials that seemed like a good idea. Its so simple, it almost seemed like overkill to even bother with Backbone - I could just pass an array of data to the Underscore template and be done. However, this is as good a starting point as any, so here's how the code looks to make it work:



var Movie = Backbone.Model.extend({

defaults: {
title: '',
gross: 0,
rank: -1,
year: 1900,
studio: ''
}

});

var Movies = Backbone.Collection.extend({

model: Movie,

comparator: function(movie) {
return movie.get('rank');
}

});

var MovieTable = Backbone.View.extend({

tagName: 'table',
template: null,

initialize: function() {

this.template = _.template( $('#movie-table').html() );
},

render: function() {

this.$el.html(this.template({
movies: this.collection,
movieTemplate: _.template( $('#movie-row').html() )
}) );

return this;
}

});



And here's the template with the partial to render the table:



<script id="movie-table" type="text/template">

<thead>
<tr>
<th column="rank"><div>Rank</div></th>
<th column="title"><div>Title</div></th>
<th column="studio"><div>Studio</div></th>
<th column="gross"><div>Gross</div></th>
<th column="year"><div>Year</div></th>
</tr>
</thead>
<tbody>
<% movies.each(function(movie) { %>
<%= movieTemplate(movie.toJSON()) %>
<% }); %>
</tbody>

</script>

<script id="movie-row" type="text/template">
<tr>
<td><div><%= rank %></div></td>
<td><div><%= title %></div></td>
<td><div><%= studio %></div></td>
<td><div><%= Globalize.format(gross, 'C') %></div></td>
<td><div><%= year %></div></td>
</tr>
</script>


The only thing left to do is to create an instance of the view and render it into the DOM:


$(function() {

var movieList = new Movies(movieData);

var movieView = new MovieTable({ collection: movieList });

$('.wrapper').html( movieView.render().$el.attr('id', 'movies') );

});


So, really what's the problem with this implementation? Well, nothing, until you want to add actions either to the table as a whole or to individual rows in the table. Because all the rendering is done in one spot, any changes in the data or DOM will require either a lot of jQuery magic or completely rendering the whole table again. Neither of those two approaches will really take advantage of what Backbone has to offer.

Another way to render the table is to break it up into the main table view which draws the header and then a series of individual item views which render each row of the table:


var MovieTable = Backbone.View.extend({

_movieRowViews: [],

tagName: 'table',
template: null,

initialize: function() {

this.template = _.template( $('#movie-table').html() );
},

render: function() {

this.$el.html(this.template());

this.updateTable();

return this;
},

updateTable: function () {

var ref = this.collection,
$table;

_.invoke(this._movieRowViews, 'remove');

$table = this.$('tbody');

this._movieRowViews = this.collection.map(
function ( obj ) {
var v = new MovieRow({ model: ref.get(obj) });

$table.append(v.render().$el);

return v;
});
}

});

var MovieRow = Backbone.View.extend({

tagName: 'tr',
template: null,

initialize: function() {

this.template = _.template( $('#movie-row').html() );

},

render: function() {

this.$el.html( this.template( this.model.toJSON()) );

return this;
}

});



Here, the major change is to add the MovieRow view and then setup a function in the MovieTable view to iterate over the collection of models and render each row as a separate view. Now, using this scheme, the advantages of each Backbone view being bound to a specific record and being able to listen to events both on that record and the row in the DOM can later be realized as more functionality is added to the table.

You may be wondering why the line


_.invoke(this._movieRowViews, 'remove');


is in the updateTables() function. If the rows are only ever rendered once, then this line is not necessary. However, my intention is to render the rows at various times during the life-cycle of the application so I need to track all the item views I create and ensure I remove them before creating a new set of rows. If I don't do this, I risk having the dreaded "zombie" problem and memory leaks because events remain bound to variables that continue to persist.

This example doesn't do anything spectacular nor does it really utilize some of the key features of Backbone. However, it does represent a basic boilerplate pattern I would use when working with a table of data. Both the version using partials and the one using item views are on my sandbox for reference. In my next post, I will continue to flush out additional features by making the table sortable.