Advertisement

Connected to the Backbone

by

Here's the thing: if you can't fathom why you'd need a framework like Backbone, then chances are, you don't! Perhaps you're exclusively working on simple websites or basic WordPress themes; in these cases, a structured JavaScript framework will likely be overkill.

However, there will surely come a day when you realize that all of that spaghetti code within script tags at the bottom of your page has suddenly become unmanageable. Not only that, but, due to the way you structured your code, it's also impossible to test. Gasp! What to do?

When that day occurs - and it will - point your browser to Backbonejs.org, and take a step forward, as you advance to the next level in your programming maturity.


What's Backbone.js Again?

Think of Backbone as a small framework (5.6kb, packed) that brings structure to your applications. By implementing its own flavor of the MVC pattern (actually, more like MV*), Backbone provides the necessary tools to separate your data from the presentation. If you think about it, where most of us often stumble is when we're desperately trying to maintain our data. Typically, this results in countless DOM queries, as we frantically fetch the necessary values to keep our application's presentation and data in sync. There must be a better way!

"Get your truth out of the DOM". - Jeremy Ashkenas (Creator of Backbone.js)

The solution, as the creator of Backbone.js, Jeremy Ashkenas, so frequently advocates is to stop tying your data to the DOM. Make your view reflect your data, not the other way around!


MV*

Truthfully, it's probably better if you don't even attempt to convert your existing MVC sensibilities over

You'll often see Backbone, as well as many of its siblings frameworks, referred to as being MV*, rather than MVC. Because the concept of your standard server-side "controller" and "view" doesn't transfer over too well to a JavaScript framework, this can frequently lead to confusion. It certainly did for me!

Truthfully, it's probably better if you don't even attempt to convert your existing MVC sensibilities over; it'll only confuse you. A "view" in Ruby on Rails is not the same as a "view" in Backbone. Or, a "controller" in CodeIgniter doesn't really have a counterpart in Backbone. In fact, controllers don't exist in Backbone! Instead, Backbone borrows bits and pieces from other frameworks, which is why we often refer to it as being closer to MVP (Model, View, Presenter), or MV*: it's not MVC, and not quite MVP; it's its own flavor.


So How Does it Work?

Let's first determine how the logic will be divided in our application. Re-tuning our concept of how JavaScript should be structured can take a while to adapt to, so don't worry if this takes time to wrap your mind around.

Models

In Backbone, data is represented through a model. As an example, consider a Photo model. This model will define the blueprint for a photo: what is the photo's source, its description, who is pictured in the photo, etc. We can even apply validation if necessary, via an optional validate method.

var Photo = Backbone.Model.extend({
    defaults: {
        src: 'images/placeholder.jpg',
        description: 'My Image',
        people: []
    }
});

With the blueprint in place, we can now setup our first photo by creating a new instance of the Photo model, like so:

var photo = new Photo({
    src: 'images/my-image.jpg',
    description: 'With friends for dinner',
    people: ['John Doe', 'Rick James']
});

Tada - you've created your first model. Should you need to fetch or set information from this model instance, it's as easy as using Backbone's getters and setters:

photo.toJSON(); // object containing all props

photo.get('src'); // images/my-image.jpg

photo.set('src', 'images/new-path.jpg');  
photo.get('src'); // images/new-path.jpg

Views

In Backbone, as a best practice, a view is responsible for the representation of a single DOM element and any applicable children, such as a list item, or div - anything you want. This includes registering any applicable templates, listening and responding to events, and monitoring associated models and collections for changes.

Let's create a new view for the image tag, itself.

var ImageView = Backbone.View.extend({
    tagName: 'img',

    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.attr({
            src: this.model.get('src'),
            alt: this.model.get('description')
        });
    }
});

Don't let this code confuse you. It's quite readable and elegant. We'll take it step by step.

To begin, tagName specifies what DOM element the view represents. In this case, we're defining the view for a single img element, though, we could just as easily have bound it to an element that already exists in the DOM, via the el property.

var ImageView = Backbone.View.extend({
    el: '#my-image'
});

Behind the scenes, Backbone will fetch the specified element (#my-image) from the DOM, cache it, and make it available, via ImageView.el and ImageView.$el. The latter version, as you might expect, is the element wrapped in jQuery (or potentially Zepto, if you prefer that library over jQuery).

Next, you can think of the initialize method as the constructor. When a new instance of this view is created, that method will automatically run. This is the perfect place to create any new instance variables, and subscribe to any model or controller events.

Lastly, the render method is responsible for constructing the output. In this case, it will set both the src and alt attributes of the image equal to the data that is stored in the view's associated model.

Let's create a new instance of ImageView, and pass in some data. In a real-world application, this data might come from a form, or something similar.

var photo = new Photo({
    src: 'images/my-image.jpg',
    description: 'With friends for dinner',
    people: ['John Doe', 'Rick James']
});

var imageView = new ImageView({ model: photo });  
imageView.el; // <img src="images/my-image.jpg" alt="With friends for dinner">

This can be incredibly powerful, if you think about it! In Backbone, it's trivial to update a view whenever its respective model is altered, assuming a one-to-one relationship. Let's listen for when the data (or the model) is modified. When it is, we should update the element accordingly.

The following code can be added to the initialize method of the ImageView.

initialize: function() {
    // Listen for when the model is updated
    this.model.on('change', this.render, this);
    this.render();
},

render: function() {
    this.$el.attr({
        src: this.model.get('src'),
        alt: this.model.get('description')
    });
}

Don't underestimate how efficient this PubSub implementation can be. When a model is altered, it makes an announcement, so to speak. "Anyone who is interested - I've just been modified!" Within our view, with a single line of code, we can subscribe to this announcement, and respond accordingly, by updating the image's attributes.

this.model.on('change', this.render, this);

In this case, we re-call the render method, and generate the attributes once again.

Let's test it.

var imageView = new ImageView({ model: photo });
imageView.el; // <img src="images/my-image.jpg" alt="With friends for dinner">

photo.set('src', 'some-new-path.jpg');  
imageView.el // <img src="some-new-path.jpg" alt="With friends for dinner">

Excellent! Should you need to, you can also attach DOM-level event listeners with ease, via an events object that Backbone will automatically hunt down and set for you. Perhaps you want to edit the image when it is clicked:

events: {
    'click': 'edit'
},

edit: function() {
    // edit the image
}

As this particular view represents a single img element, we can stick with click', however, you'll often find that View.el contains nested children. In these cases, you can specify a selector after the event type, like so:

events: {
    'click span': 'doSomething'
},

doSomething: function() {}

The above code, using View.el as the context, will attach a click event to any child spans.

Collections

What good is a model for a single photo? Let's store all photo models in a Photos collection. Think of collections as arrays with added sugar and convenience methods, thanks to Backbone's only hard-dependency, Underscore.js.

var Photos = Backbone.Collection.extend({
    model: Photo
});

Above, we've associated the Photos collection with the Photo model that we've already created. This specifies that all items in this collection will be instances of the Photo model.

Next, we create our instance, and pass in a few models to get us started. In this case, we're hard-coding the values, but you could just as easily fetch the JSON from your server.

var photos = new Photos([
    { src: 'image1.jpg', description: 'Vacation 2012' },
    { src: 'image2.jpg', description: 'My best friend' },
    { src: 'image3.jpg', description: 'Anniversary party' }
]);

Notice how, this time, it appears as if we're simply passing a few objects to the Photos collection. However, behind the scenes, each object in the array will be converted to a Photo model.

Similar to models, collections will announce any alterations, such as when a model is added or removed. This means that you can listen for, as an example, when a new item is added to the collection. When this happens, you can compensate by updating the DOM accordingly with the new item.

this.collection.on('add', this.appendItem);

Additionally, as noted above, a collection provides lots of sugar. For instance, let's say that we need to grab the src property from every model in the collection. That's a cinch with the pluck method!

photos.pluck('src'); // ["image1.jpg", "image2.jpg", "image3.jpg"]

Notice how, at all times, our data is available. We never have to query the DOM to fetch any values. "Keep your truth out of the DOM."

Refer to the Underscore.js documentation for more usage examples.

At this point, we have a collection of photos, but, now, we need a new view that will be responsible for the presentation of the photos container. Remember, ImageView is only responsible for a single image element. Let's create a new view for the wrapping list of photos.

var PhotosView = Backbone.View.extend({
    tagName: 'ul',

    className: 'photos',

    initialize: function() {
        this.render();
    },

    render: function() {
        var imageView;

        this.collection.forEach(function(model) {
            imageView = new ImageView({ model: model });
            this.$el.append( $('<li>').html(imageView.el) );
        }, this);
    }
});

This time, we're creating the view for the wrapping <ul class="photos"> element. When we call render(), the method will filter through all models that are contained in the associated collection (three, in our case), create a new ImageView for each - which constructs the image element - and then we append that generated image element to the .photos unordered list.

Remember: don't worry about repaints or reflows. PhotosView.el has not been injected into the DOM yet.

photosView.el.parentNode // null

So let's put it all together!

// Create a collection of Photo models.
var photos = new Photos([
    { src: 'image1.jpg', 'description': 'Vacation 2012' },
    { src: 'image2.jpg', 'description': 'My best friend' },
    { src: 'image3.jpg', 'description': 'Anniversary party' }
]);

// Create a new PhotosView, and pass in the photos collection
var photosView = new PhotosView({ collection: photos });

// Throw our list of photos into the DOM.
$('body').html(photosView.el);

This may seem confusing at first, but it will certainly become easier the more you work with Backbone. In the previous code snippet, we begin by creating a collection of Photo models. Next, we create a new instance of the PhotosView container. When rendered, this view filters through all of the items in its respective collection, creates new ImageView instances, and appends the resulting elements to the unordered list. Finally, we take the resulting DOM fragment, and throw it into the DOM. Not too tough, ay? And look: structure!


Additional Reading

We've barely scratched the surface of what Backbone is capable of. To continue your learning, refer to the following books, screencasts, and tutorials.


Conclusion

Backbone is sometimes criticized for not offering enough. It doesn't enforce any particular structure, and it doesn't offer UI components that you might receive from Dojo or jQuery UI. Ironically, despite these criticisms, this is what makes Backbone so fantastic. It doesn't enforce its opinions on to you. It does one job, and one job beautifully: provide structure for your applications. You're then free to figure out how to best fit Backbone into your existing projects.

Advertisement