Backbone.js Basics: Models, Views, Collections and Templates
In this tutorial, we’re going to explore the underlying fundamentals of the popular MV* framework, Backbone.js. We’ll take a look at models, views, collections, and templates, and see how each builds off of each other when building an application. We will also touch on responsibilities and separation of concerns, ultimately paving the way for us to build a scalable application with a sane code base.
Backbone only has one hard dependency, which is Underscore.js. We will also leverage jQuery for easy DOM manipulation. Your typical document structure should look like this:
<html>
<body>
app content here
<script src="path/to/jquery.js">
<script src="path/to/underscore.js">
<script src="path/to/backbone.js">
<script src="path/to/app.js">
</body>
</html>
All app scripts must be included after the various libraries. Other than that, we’re ready to dive in!
Backbone.js & the MV* Pattern
Backbone falls under the umbrella of MV* frameworks, which means that it is primarily composed of Models and Views. Traditional MVC frameworks contain that extra C, which stands for controller. Historically, a controller would respond to some kind of user input, and communicate that input to the model, or back to the view. In MV* frameworks like Backbone, though, the controller logic is handled inside the view itself. To understand a bit more about the anatomy of a JavaScript MV* framework, check out this post.
We’ve jumped the gun a bit here though, as we haven’t looked at what models and views are. Well, it’s not that complicated! I try to think of models as a “model of data”, the same way an architect might have a model of a house, or an engineer might have a model of an aerofoil. With this in mind, it’s easier to understand how a particular model is a representation of a certain set of data. In a production grade application, that data is likely stored in a database somewhere. Models can thus communicate with that database, and perform certain actions, such as CRUD operations.
What about views? Given the name “view”, it’s pretty easy to draw assumptions about their responsibility. If you thought that it was to render data for the end user, then you’re mostly correct. Views are indeed responsible for this. In Backbone, however, they house one other major function as I mentioned before. They handle the controller logic. In the second part of this series, I’ll get into event handling inside the view, communicating with the model, and then sending updates back to the view. For now though, it’s just important to understand that this logic does indeed exist, but resides inside the view. Let’s jump into models, and gain a better understanding of them.
Digging into Models
Here’s a little excerpt from the Backbone docs on models:
Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.
With that in mind, let’s draft up a little example that we’ll use going forward. Imagine we’re running a surf shop, and we want to build a database of surfboards that we carry. That way, if a customer asks if we have a board from a particular manufacturer, or an exact model of board, or both, we can do a quick look up. Let’s also assume that we want to keep track of the stock. Our model hierarchy would look like this:
Surfboard
|__manufacturer
|__model
|__stock
In Backbone, we create a new model by extending Backbone.Model
like this:
var Surfboard = Backbone.Model.extend({
});
Now, we can create a new instance of this model using the constructor like this:
var board1 = new Surfboard({
manufacturer: 'Channel Islands',
model: 'Whip',
stock: 12
});
The variable board1
now references a new instance of our Surfboard
model, and contains its own set of values. As it stands though, we can pass in any values. Let’s make use of the defaults
function to add some default attributes to our model. Now, it should look like this:
var Surfboard = Backbone.Model.extend({
defaults: {
manufacturer: '',
model: '',
stock: 0
}
});
If we wanted to fetch some data from that instance, we would use get
, which gets the current value of an attribute from a model. Imagine we had this markup in our document:
<table class="table">
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Stock</th>
</tr>
<tr>
<td id="board1-manufacturer"></td>
<td id="board1-model"></td>
<td id="board1-stock"></td>
</tr>
</table>
We’re able to populate those fields like this:
$('#board1-manufacturer').html(board1.get('manufacturer'));
$('#board1-model').html(board1.get('model'));
$('#board1-stock').html(board1.get('stock'));
Here’s the result, with a second model instance included:
See the Pen Backbone.js Basics – Part 1 by SitePoint (@SitePoint) on CodePen.
This all seems a bit long-winded for now…we could’ve, after all, just manually output this data. However, by grouping models into a collection, we’re able to perform iterative procedures for outputting data, registering events, changing models, re-rendering data, and the works.
Remember also that in a production application, we’re likely to be generating model instances by reading from a database. This too adds to the necessity of grouping models into collections. Without further ado, let’s look at that.
Creating a Collection
Let’s imagine that during our initial application load, we ran a query to our database of surfboards, looping over all of them. During that looping process, we grabbed some data and created new model instances. Since we don’t have a database at the moment, I’ll just use the two model instances I created already, and add a third.
Now, in our app, we want to output all of the data in a tabular manner. Before, we manually output each model instance’s attributes, which is hugely impractical. This time though, let’s create a Backbone collection (an ordered set of models), and add our models to it. We’ll start off by creating our collection, called Surfboards
:
var SurfboardsCollection = Backbone.Collection.extend({
});
Let’s set the collection model to our Surfboard
model, which allows us to specify the model class that the collection contains:
var SurfboardsCollection = Backbone.Collection.extend({
model: Surfboard
});
Now that our collection is set up, we can use the add
method to add models to the collection. Let’s add our three surfboards by first creating a new instance of the collection, and then adding the board instances one at a time:
var Surfboards = new SurfboardsCollection;
Surfboards.add(board1);
Surfboards.add(board2);
Surfboards.add(board3);
All of our models are now grouped and ready to be rendered. In our HTML, let’s remove the table rows that we had before, and add just a plain table body. Our table should look like this now:
<table class="table">
<thead>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Stock</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
Now, we can use one of the Underscore methods available for us to loop over the collection and output the data. We’ll use each
, which we can apply directly to our collection, and which also accepts one argument that represents the model. Therefore, on each iteration, we have access to the current model, and can perform model methods. With that in mind, rendering a table of results is simple:
Surfboards.each(function(surfboard) {
$('#table-body').append(
'<tr>' +
'<td>' + surfboard.get('manufacturer') + '</td>' +
'<td>' + surfboard.get('model') + '</td>' +
'<td>' + surfboard.get('stock') + '</td>' +
'</tr>'
);
});
Here’s the demo of this in action:
See the Pen Backbone.js Basics – Part 2 by SitePoint (@SitePoint) on CodePen.
So far, it’s looking great, and in some ways, we’ve manually rendered a view. Doing it this way is very limiting though, and doesn’t allow us to utilise Backbone’s full array of methods. It also doesn’t allow us to carry out any controller logic on the fly. We need to properly render our data using Backbone views, so let’s move onto that.
Rendering Data with Views & Templates
Before we get into building up our views, let’s touch on a neat subject – templates. Templates allow us to easily render the UI as an alternative to direct DOM manipulation. Although we’re allowed to use whatever we want, Underscore ships with its own templating engine, so we’ll make use of that.
In our HTML, let’s create a template that will be responsible for rendering the model data. It should look like this:
<script type="text/template" id="surfboard-template">
<td><%= manufacturer %></td>
<td><%= model %></td>
<td><%= stock %></td>
</script>
Our template is now ready to be called inside a view for easy rendering of the data. Notice that I haven’t included the wrapping tr
element, and you’ll see why shortly.
Let’s move onto creating the views now. According to the docs:
The general idea is to organize your interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page.
Now think now about our app, and think how we can make it somewhat future proof. In this tutorial, we’re only rendering data once. But what if in the future, we wanted to be able to update model instance properties? We’d definitely want to have reference to each model instance, and attach the necessary controller logic to that particular instance. With that in mind, we’ll want two views for our app:
- A master (or parent) view, that contains all the sub-views. Let’s call this
SurfboardsView
. - A sub-view, which is responsible for rendering one single instance of a model. Let’s call these
SurfboardView
.
We’ll first create our SurfboardsView
by extending Backbone’s View method, like this:
var SurfboardsView = Backbone.View.extend({
});
Let’s now include the following 3 things:
- The
el
, or the DOM element. Views must have a DOM element at all times. In the master view case, we’ll use#table-body
. - The
initialize
function, which immediately runs when a new instance of the view is created. - The
render
function, which renders the view template from model data.
Here’s our updated view:
var SurfboardsView = Backbone.View.extend({
el: '#table-body',
initialize: function() {
},
render: function() {
}
});
Because we’re manually inserting model instances into our collection, we don’t have to wait for any events to be fired to signal the completion of data fetching. This means that we can immediately call the render
function in our view. Our initialize
function now looks like this:
initialize: function() {
this.render();
}
Inside our render
function, let’s do a few things to start. First, we’ll make sure our element in question has no HTML. Secondly, let’s iterate over our collection. Thirdly, let’s return this
, which enables chained calls. This gives us:
render: function() {
this.$el.html('');
Surfboards.each(function(model) {
// do something...
});
return this;
}
So far so good. We just need to finish up the render
function. But what exactly do we need to do here? Here are the steps:
- On each loop iteration, we need to create a new
SurfboardView
view, and make sure the correct data gets passed in (i.e. the current model). - From the created view instance, we need to call that view’s render function, and return the populated HTML element.
- Upon retrieval of the rendered
SurfboardView
, we need to append it to the table body element that we set.
Keep in mind that a view’s element, denoted by el
, can be retrieved at any point. With that knowledge, here’s what the render
function for the SurfboardsView
looks like:
render: function() {
this.$el.html('');
Surfboards.each(function(model) {
var surfboard = new SurfboardView({
model: model
});
this.$el.append(surfboard.render().el);
}.bind(this));
return this;
}
Take note of a couple things here. The first is that we’re passing in the current model to each new SurfboardView
view instance, so that its attributes can be accessed from inside each of these instances. Secondly, we need to bind this
to the each
loop due to a new scoping context that gets created.
Right now, nothing should actually happen if you run the script. That’s because we haven’t defined and set up our SurfboardView
yet. Let’s do that. We’ll start, once again, by extending Backbone’s view:
var SurfboardView = Backbone.View.extend({
});
This time, we want to define a tagName
that will be populated with the model attributes. If you remember our template from before, you’ll also remember that we never included the wrapping tr
element. That’s because this is where we define it, ultimately creating a new element.
We’ll also want to create a reference to our view template, which we will use in our render function
. Finally, inside the render
function, we populate the view’s element by leveraging the template and the model attributes and return this
for chaining, like before. Here’s the final script for the SurfboardView
:
var SurfboardView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#surfboard-template').html()),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
Nice! Now our various instance creations inside the SurfboardsView
have access to the corresponding render
function and element.
Last, but certainly not least, we need to run the app by instantiating the SurfboardsView
, like this:
var app = new SurfboardsView;
Awesome, you should see the table populated with our surfboard models! Here’s the demo to see it in action:
See the Pen Backbone.js Basics – Part 3 by SitePoint (@SitePoint) on CodePen.
Wrap Up
That’ll just about do it for this segment of Backbone.js exploration. We covered a lot of fundamentals here today, and also discussed some great techniques for scalability and forward movements. Next time, we’ll look into implementing some controller logic, and we’ll explore some view-to-model communication with events, and view updating. If you have any questions or comments, I’d be glad to hear them in the discussion below. Thanks for reading!
Want more on Backbone?
SitePoint Premium has a new course on Backbone.js. Join Premium to access it, and the entire SitePoint library of resources!