Blog

27
Jan

An Intro to Backbone.js: Part 2 – Controllers and Views

This is Part 2 of a series of tutorials. You can find Part 1 here.

We’re good with Backbone.Model. We’re good with Backbone.Collection. We’re good with Backbone.Events. So:

Backbone.Controller:

If you’re coming from Rails land, you’ll be pretty familiar with this guy, although it’s got a pretty neat twist. You get to write your routes right in the controller. This is very, very nice to work with.

var MyController = Backbone.Controller.extend({
  routes : {
    "say/:something" : "say"
  },

  say : function(something) {
    alert(something);
  }
});

var yC = new MyController;

Backbone.history.start();

This is pretty straightforward. I defined a prototype and constructor for MyController, complete with one route and the property name, as a string, of the function in this Controller that you want it it to call (analogous to an action in Rails).

Backbone.history is where Backbone stores all of the places you’ve been. This is necessary in order to not break the back button in your browser.

When I called the constructor, MyController gave all of its’ routes to Backbone.history, along with their relevant callbacks.

Next, when I called Backbone.history.start(), Backbone subscribed to the browser’s hashchange event*. Whenever the hash changes, Backbone.history takes care of running your Controller’s function.

*If the browser supports it. Otherwise, it starts monitoring the window.location for hashchanges periodically.

Now if I navigate to “http://myapp.root/#say/hello_world” … I get a butt ugly alert with “hello_world” in it.

Backbone.Views:

Views are designed to encapsulate a DOM element’s functionality. It exists between a Model/Collection and the DOM.

The View has the following responsibilities:

  • rendering the DOM element
  • initializing DOM events for user interactions within the element
  • subscribing to events that are relevant to the DOM element and updating it appropriately, such as:
    • When a Model changes.
    • When a Collection changes.
    • When a specific event occurs elsewhere on the page (navigation, etc, etc).

While this is the general idea of the View, you are perfectly free to do whatever else you’d like with it. Backbone also doesn’t provide any implementation of these details, other than the triggering/binding of Events, so you’re free to implement what you’d like with the framework(s) of your choosing.

Creating a View Prototype

Let’s cut to the chase and make one!

DonutView = Backbone.View.extend({
  tagName : "div",
  className : "donut",

  render : function() {
    this.el.innerHTML = this.model.get('name');

    return this;
  }
});

I’ve overriden three values in my new prototype. The first two are fairly self-explanatory. When you create the donut view, it will create a div element with the class “donut”.

The last — “render” — is the core function of the Backbone.View. The View itself will create an element, but “render” is where you tell your view how to render itself. Backbone has no opinion on how you do this, so feel free to experiment with one of the dozens of JavaScript template formats that have crept up. (Here’s a cool performance comparison). In this case I set the innerHTML of the element to the content I desire. Pretty straightforward.

var bcDonut = new Donut({
  name : "Boston Cream"
});

var bcDonutView = new DonutView({
  model : bcDonut
});

var renderedDonutElement = bcDonutView.render().el;

Binding a View to a Model’s Changes

This is a little bit of overhead for just rendering a model’s name. but this extra layer of abstractions gets us a lot. For instance, what if you want to automatically update the view whenever the model changes?

var UpdatingDonutView = DonutView.extend({
  initialize : function(options) {
    this.render = _.bind(this.render, this); 

    this.model.bind('change:name', this.render);
  }
});

Whoa. Did I blow your mind? First thing we inherited the prototype of DonutView, and overrode its’ “initialize” property with our own function.

Inside initialize we bind this.render to our instance of UpdatingDonutView. The Underscore method ‘bind’ makes sure that when we call “this” in  ”render” it will always reference this UpdatingDonutView instance.

Then we bind to the model’s “change:name” event with “render” as the callback function. It’ll now re-render this view when the name changes. Cool!

var uDonutView = new UpdatingDonutView({
  model : bcDonut
});

var renderedDonutElement = uDonutView.render().el;

Now, if we change the name:

uDonutView.model.set({name : "Lemon-Filled"});

The element reflects the name change!

renderedDonutElement.innerHTML;
-> "Lemon-Filled"

That took a little longer than I’d expected to go over, so we’re going to save collection binding for the next post!


  • Steve

    I’ve been reviewing options for javascript RIA applications for a few days now.. JavascriptMVC, SproutCore, Backbone, etc. Articles like this one are a great resource. Thank you.

    • n_time

      I spent a while assessing the different options. There are definitely pros and cons to each.

  • http://www.alexrothenberg.com alex rothenberg

    Thanks for these examples they are really straightforward and clear.

    Do you have a page somewhere with this example on it?

  • Johan

    Thanks for a great 2nd post – looking forward to the next installment.

  • Johan

    I think

    var renderedDonutElement = dv.render().el;

    should be

    var renderedDonutElement = bcDonutView.render().el;

  • http://rywalker.com Ry Walker

    Thanks for these 2 articles – motivating me to finally start using backbone :)

  • http://mrDarcyMurphy.com Darcy Murphy

    I’ve looked over a handful of backbone tutorials/references and these are by far the clearest. Thanks for writing them.

  • Taler2003

    Is collection binding happening any time soon?

    Thanks.

    • Anonymous

      Hi!

      Thanks for the interest! We’ll be putting it out soon!

  • http://twitter.com/MattMueller MattMueller

    Great stuff! I would absolutely love to see a tutorial on how to use backbone on both client and server with node.js

    • Parkerproject

      Why can’t we just use JS for client, and leave server for other languages

      • Kinakuta James

        what’s wrong with using JS for server?

  • Denis Konchekov

    Line:
    uDonutView.set({name : “Lemon-Filled”});
    Must be:
    uDonutView.model.set({name : “Lemon-Filled”});

  • Viksit

    BTW, Controllers have been renamed to Routers in latest Backbone versions.

  • http://twitter.com/customcommander Julien

    Thank you very much for this. It has been really helpful.

    one little note though : Controller has been renamed to Router since 0.5.0 (from what I can see in Backbone.js Changelog)

  • Maer007

    Backbone.history.start() get error Uncaught TypeError: Cannot call method ‘start’ of undefined

    • Anonymous

      From the documentation….
      “you should use the reference to Backbone.history that will be created for you automatically if you make use of Routers with routes.” …so if you have not created any routes it will be undefined.

  • MRush

    Thank you very much for this great tutorial.It really helped me to understand backbone and MVC  pattern.

    I can’t wait for next tutorial.Keep it up.

  • Mark

    This does not work for me. I copied all the code exactly and it does nothing (yes I try to render it etc.)

  • Hugo Monteiro

    I really liked the tutorial. I was searching for a good and simple backbone tutorial and this was it. Many thanks.

    For curiosity I’m using PlayFramework with Backbone.js.

  • Marten Czech

    Next post! :)

  • Dag Rende

    Good article! Short and concentrated. But why is the text of the comments white against white background?