An Introduction to Backbone.js

Last week I gave a presentation on Backbone.js to the rest of the developers here at Clock. The amount of exposure we've had to client side heavy-lifting varies, so the idea was to get everyone on the same page. The other guys found it pretty handy, so I thought I'd write it up here where there's a chance it could be useful to others.

Why do we use Backbone?

For those that don't know – Backbone.js is a JavaScript library that runs in the browser. We use it to structure client-side applications – those that run in a web browser.

From Fancy Scripting to Software Engineering

Increasingly, application logic and heavy lifting is heading in to the browser. It used to be that JS was used for fancy effects and embellishments. Now though, it's stuff that matters – proper application code as well as the niceties. That means we need to take more care to organise and structure our source.

Some of the things we care about:

  • Maintainability
  • Compatibility
  • Testability
  • Reusability
  • DRY-ness
  • Consistency
  • Modularity

What about jQuery?

But we already use a framework, jQuery on pretty much all of our sites. Doesn't that help? Well, jQuery is great for a number of things:

  • Ironing out browser inconsistencies
  • Improving the DOM interface
  • AJAX
  • Animation

These definitely make our lives easier, but what they don't do is help to structure applications. That's because jQuery is a utility belt.

We need no greater proof that jQuery doesn't help us to do the things that Backbone does, than the fact jQuery is a dependency of Backbone's.

Backbone sits on top of Underscore.js, as well as jQuery, to provide a platform for building applications.

So, we use Backbone to structure our applications with proven software patterns, separating UI logic from business logic.

Alternatives

It's worth mentioning that, of course, alternatives to Backbone exist, for example:

… and many more. Personally, I've never looked beyond Backbone – quite simply because it has done everything I've wanted from such a tool.

I will round off this introduction with an excerpt from the Backbone docs that I think succinctly and accurately specifies the problem that Backbone addresses:

When working on a web application that involves a lot of JavaScript, one of the first things you learn is to stop tying your data to the DOM. It's all too easy to create JavaScript applications that end up as tangled piles of jQuery selectors and callbacks, all trying frantically to keep data in sync between the HTML UI, your JavaScript logic, and the database on your server. For rich client-side applications, a more structured approach is often helpful.

What does Backbone do?

Backbone provides the reusable building blocks that are required for the majority of applications – loosely following the MVC pattern. I say loosely – MVC is actually a very specific and well-defined pattern composed of Models, Views and Controllers. Of these components, Backbone only has models and views, and the view behaves like a view and and a controller – technically violating the pattern.

The amount of code in Backbone is remarkably small for a library and it does surprisingly little, actually. I recommend reading or at least scanning the annotated source to get a grasp on the internals.

So it provides a bit of code, but more importantly it provides a set of conventions about the structure of applications.

I will now take you through the 'building blocks' that Backbone provides, with code examples. If you want to run any of the examples, head over to the Backbone site, fire up your console and paste the code in.

Backbone.Events

Events is a trait that can be mixed in to other objects to give them event triggering and listening behaviour – a very effective way of promoting decoupling between components. The following example shows how to use Backbone.Events on your own objects:

var o = {}
_.extend(o, Backbone.Events)

o.on('zap', function () {
  console.log('zapped')
})

o.trigger('zap')
//=> outputs 'zapped'

This Events trait is inherited by all of the other prototypes that Backbone provides.

Backbone.Model

Backbone models are for encapsulating entities that are associated with data. They provide accessor and mutator access to the data through get() and set() methods. This level of indirection allows other parts of the system to be notified when things change – useful, for example, for a view that should update when its underlying model changes.

The following is an example of instantiating and using a stock Backbone.Model:

// Create a new model by passing in some data
var person = new Backbone.Model({ name: 'Bob', age: '30' })

// Access the data
person.get('name')
//-> returns 'Bob'

// Observe the data for changes
// using the event methods that
// the model has inherited
person.on('change', function () {
  console.log('Something about this person changed')
})

// Mutate the data
person.set('name', 'Robert')
//-> outputs 'Something about this person changed'

So that's pretty handy for wiring up simple models where you just need access to the data, but what if your models need to be more complex than that? This is where you need to extend the stock model with your own logic. In the first example, our model contained an age attribute – this is poor practice, because if that data was persistent it would have to be maintained every time the date changed. In the following example, the model will have a date of birth and the age will be calculated on the fly:

// Create a new constuctor by extending
// the stock model with our custom method
var Person = Backbone.Model.extend(
  { age: function () {
      return (new Date() - this.get('dob'))
    }
  })

// Instantiate one of the new
// Person objects with some data
var person = new Person(
  { name: 'Bob'
  , dob: new Date('21 Sept 1988')
  })

// Use the custom function
person.age()
//-> returns the number of milliseconds since '21 Sept 1988' (useful!)

Everything we've seen so far is useful for connecting up with views and having a front-end that doesn't need persistence. Models also provided methods for communicating with a server to provide persistence. I won't be covering them here, but you should know that the methods sync() and destroy() are available. Backbone makes it very easy to wire up your models to a restful/crud-y JSON API.

Backbone.Collection

Collections are ordered sets of models, and there's not really a great deal to say about them. They get all of the Underscore array/collection methods for convenient set manipulations.

The most useful thing is that events triggered on models within the collection propagate up and get triggered on the collection instance too.

// Create an array of models that
// can be passed in to a collection
var models = []
for (var i=0; i < 5; i++) {
  models.push(new Backbone.Model({ num: i })
}

// Create collection containing the models
var collection = new Backbone.Collection(models)

// An example of an underscore function -
// some will return true if the function
// passed to it returns true for any of
// the collection's contents
collection.some(function (model) {
  return model.get('num') === 3
})
//-> Returns the model that has { num: 3 }

// An example of events bubbling up to the
// containing collection - bind a listener
// to the change event on the collection
collection.on('change', function () {
  console.log('one of the models changed')
})

// Acces one of the models (not via
// the collection) and change it
models[4].set({ num: 10 })
//-> Logs 'one of the models changed'

Backbone.View

Views are my favourite part (I'm not sure if it makes sense to have a favourite part…).

I personally have spent a lot of time coding UIs without Backbone – not necessarily web-apps but things that have just a bit too much state for a jQuery plugin, so I really feel the benefits.

View objects are associated with a fragment of DOM. They are designed to be tied to models (data) that needs to be presented to the user. Against the traditional MVC pattern, and due to the nature of the DOM, Views act like controllers too. They are both presentational and interactive.

Each Backbone.View, upon creation gets its own 'root' DOM element. They come with a render() method, which is by default a noop. It's a convention. You have to extend Backbone.View with your own methods and implement render in the way that you want to.

var v = new Backbone.View()
$('body').append(v.$el)
//-> Appends the default root element,
//-> a 
, to the document body

In order to acheive anything useful, the view needs to be extended:

// Extend a View with a custom render
// function that writes the current date
var Clock = Backbone.View.extend(
  { render: function () {
      this.$el.empty().append(new Date)
    }
  })

// Instantiate a custom objet
var clock = new Clock()

// Append its element to the document body
clock.$el.appendTo('body')
//-> An empty div is attached to the body
//-> because render() has not yet been called

clock.render()
//-> Document now shows the current time

// Subsequent calls to render update the time…
clock.render()
clock.render()
clock.render()

Hold up though, there's a clock in our UI logic! Uh oh! What if there are other views that wanted to know about the time? The views would become tightly coupled. Instead, we'll factor the clock out into a model and have the view listen for changes:

// Create model to hold the time
var clockModel = new Backbone.Model({ time: new Date() })

// Create view that listens to change
// events on the model, and renders
// each time it changes
var ClockView = Backbone.View.extend(
  { initialize: function () {
      this.model.on('change', _.bind(this.render, this))
    }
  , render: function () {
      this.$el.empty().append(this.model.get('time'))
    }
  })

// Instantiate a view and pass in the model
var clockView = new ClockView({ model: clockModel })

// Append the clockView element to the document body
clockView.$el.appendTo('body')

// Get the time to change every second
// triggering the view to render
setInterval(function () {
  clockModel.set('time', new Date())
}, 1000)

//-> The time in the document updates every second

That brings us the end of this introduction. I hope you found it useful.

As always, if you have any questions, don't hesitate to comment below.

Content on this blog is licensed under a Creative Commons Attribution 3.0 License.

Join Us

Come and work for Clock

Clock is made up of bright, hard-working and talented people and we're always on the look out for more. You can browse the current jobs below or follow us @clock for the latest vacancies.

View
Jobs