Logo

JsonData

  • Archive
  • RSS
  • Ask me anything

Grokking Backbone Views

This is my fourth post in my series on Backbone.js. Make sure to read the first three if you haven’t had a chance to check them out yet.

  1. Hey, check out my Backbone
  2. Aligning my Backbone
  3. Backbone Routers Are Configuration

In my last post, I started digging into the guts of how Backbone Routers work. In this post we’re going to dive into Backbone Views. In Backbone, Views take a more active role than you may be used to if you’ve been working with frameworks like Rails or ASP.NET MVC. On the server, Views are generally logic-lite templates. They’re typically HTML markup fragments with a smattering of code blocks to handle anything that needs to be dynamic.

Backbone Views, however, are all code (though mostly configuration) and contain any necessary logic for the View to render itself in the DOM and wire up any event handlers it needs to worry about. In Backbone, Views are, in many ways, like Controllers (far more than Routers are). Views serve as your app’s entry point for user interaction and will be responsible for kicking off events that your apps will use to actually get things done. Backbone Views serve as a window into your Model’s state for the user.

The basics

Lets start with an example of a basic View, annotated with comments of course…

var FooView = Backbone.View.extend({

    // `tagName` and `className` are standard parts of Backbone.View that
    // are used to dynamically create the view's `el` property.
    tagName: 'li',
    className: 'foo',

    // `template` is an HTML fragment that will be populated with data
    // from the model. More on this further on in the post.
    template: '<strong><%= name %></strong><a href="#" class="fooButton">Click me</a>',

    // `events` functions as a hash table of events and function names
    // for a Backbone.View to map DOM events to. Think of it as the Router's
    // `routes` hash table for your View.
    events: {
        'click .fooButton': 'fooClicked'
    },

    // `initialize` is used as an entry point into the View. It functions
    // like a constructor.
    initialize: function (options) {

        // If your view needs to be dynamic, you'll want to pass it a model.
        this.model = options.model;

        // Here, we're listening to the `change` event on the model. Any time
        // the Model this View is watching changes, its `render` method will
        // get called, allowing the view to keep up to date with the Model state.
        this.model.on('change', this.render);
    },

    // `render` dictates how the Model will be represented in the DOM. You can use
    // whatever methods you like to create your View's content. The most popular
    // way of doing so is to use a client-side templating system.
    render: function () {

        // Creating an HTML string populated with the Model our view is watching.
        // Here, we're using Underscore's built in `template` function, but you
        // can use any other templating system, or concatenate strings manually
        // if that's your thing.
        var html = _.template(this.template, this.model.toJSON());

        // Inject the HTML fragment into our view's `$el` alias (more on this)
        // below...
        this.$el.html(html);

        // It's always good practice to return `this` from your render method
        // so it can be chained off of.
        return this;
    },

    // Here's our click handler. If you're using jQuery, this will be a 
    // jQuery click event.
    fooClicked: function (e) {
        alert('ouch!');
        return false;
    },
});

So with the implementation now in context, let’s take another look at how you would use this view. Remember, we’re only going to be directly dealing with Views from within our Presenter. For posterity, I’ll also include a basic implementation of some of the other parts so you can see how things work together.

/**
 * Setting up a very basic Model and Collection structure here...
 */
var Foo = Backbone.Model.extend({}),
    FooCollection = Backbone.Collection.extend({
        model: Foo
    });

/** 
 * Here's our presenter. Just much like the one in my second post...
 */
var FooPresenter = function (options) {
    this.fooCollection = options.model; 
};

FooPresenter.prototype.showView = function (view) {
    if (this.currentView) {
        // Note that we're calling `close` here. This method does not exist in 
        //Backbone.View yet. I'll demonstrate how to add this extension method 
        // and why it's important further along in this post.
        this.currentView.close();
    }

    this.currentView = view;

    // Note the chaining when passing the view's `$el` to jQuery
    $('#container').html(this.currentView.render().$el);
};

FooPresenter.prototype.showFoo = function (id) {
    var view = new FooView({ model: this.fooCollection.get(id) })
    this.showView(view);
};

/**
 * Our super basic Router.
 */
var FooRouter = Backbone.Router.extend({
    routes: {
        'foos/:id': 'showFoo'
    },
    initialize: function (options) {
        this.presenter = new FooPresenter(options);
    },
    showFoo: function (id) {
        this.presenter.showFoo(id);
    }
});

/** 
 * Here's our app's entry point inside a jQuery "document ready" handler
 */
$(function () {
    // Setting up some dummy data...
    var fooCollection = new FooCollection([
            new Foo({ id: 1, name: 'foo1' }),
            new Foo({ id: 2, name: 'foo2' })
        ]),
        router = new FooRouter({ model: fooCollection });
    Backbone.history.start();
});

What’s the deal with el and $el?

The el property is the DOM element that the View is watching. Think of el as the representation of your View in the DOM. Every Backbone View has one, whether it’s been inserted into the DOM yet or not. If the HTML element your View is supposed to know about is already on the page, you can define it explicitly using a CSS selector. The $el property is a jQuery (or Zepto) alias of el that gets created when the View is initialized. So assuming you have an HTML fragment that looks like this:

<div class="wrapper">
    <section id="foo-container"></section>
</div>

You could have a Backbone View that’s defined like this:

var FooView = Backbone.View.extend({
    el: '#foo-container',
    // ...
});

The thing to remember is that generally, you’ll want to do this when the element your View is concerned with is already present on the page. For example, if you’re rendering all of your HTML on the server, or if you’re trying to fit Backbone into a legacy application for a more modern twist to your UI code. If you don’t define el explicitly, Backbone will create it for you based on the tagName, className and id properties you configure.

If you are rendering your HTML on the fly, or fetching it from the server via AJAX later on, Backbone’s View implementation will create el for you in memory if it’s not present in the DOM. This is my preferred method of doing things, especially if I’m using a templating system in conjunction with my Views. I like to keep things as tidy as possible. So I usually like to create and remove DOM elements on the fly rather than taking the hide/show approach of toggling views.

Rendering Templates

In Backbone, you can use any templating method you want to populate your View’s el. I’ve used the basic templating system that comes baked into Underscore.js as well as Mustache and Handlebars. So far, Handlebars is my favorite. The syntax is super simple (much like Mustache’s), but offers a little bit more utility than Mustache alone does.

The nice thing about this approach is that you can use any means necessary to populate your View’s el container with content. If you really love concatenating strings together, knock yourself out.

How does the eventing system work?

Backbone uses a well-known hash table called events on the View. It works in much the same way that routes works on the Router. When your View is initialized, Backbone will iterate through each member of that hash table and create an event handler for it. It uses this pattern:

events: {
    'eventName selector': 'methodName'
}

On the left side of each item in the hash, Backbone will parse out the DOM event name and the CSS selector of the element it’s delegating to. On the right hand side of the hash is the name of the View’s method that will handle the event. If you’re using jQuery, Backbone will use jQuery’s delegate and undelegate functions to set up and tear down these events. Additionally, Backbone’s View system implements two methods called delegateEvents and undelegateEvents to provide additional utility, should you ever need it.

Extending Backbone.View

Have you ever used a web app that uses a lot of JavaScript, and it just feels sluggish? Me too. It’s no fault of JavaScript’s that the app reacts slowly. The fault lies squarely on the shoulders of the developer. Odds are, when you run into something like this, it’s because the developer is not doing a very good job of managing memory.

To be clear, JavaScript is not an unmanaged language. It has a garbage collector. When the runtime isn’t using an object, it gets eaten just like you might expect in any other managed language. The main ‘gotcha’ happens when you have objects loaded into memory that are associated with DOM elements. To illustrate, if you have a large array that gets created in an event handler, and say that event goes out of scope once it completes, there’s no guarantee that the array will be eaten by the garbage collector right away, because the DOM element still exists on the page. This is quite a bit more prevalent in certain browsers (cough IE cough) than it is in other, more modern browsers.

These issues most likely won’t ruin your app. I’m sure we’ve all written enough nasty looking spaghetti-code JS apps to prove that it’s not the end of the world if you’ve been guilty of this sort of thing in the past. However, it is nice to know it exists and that Backbone provides a nice way of cleaning up after yourself. It wasn’t until I began writing larger scale JavaScript applications that I realized I needed to start worrying about memory management.

Remember that line of code in the FooPresenter implementation of showView above, where I called this.currentView.close()? Backbone’s View implementation does not have a close method. So here’s how I implemented it (which I’m pretty sure I got from Derek Bailey’s blog):

Backbone.View.prototype.close = function () {

    // A View's `remove` method will call `$el.remove()` to remove the View's element
    // from the DOM.
    this.remove();

    // Calling `unbind` here will unbind any DOM events that the View's `el` element 
    // or any of its children were listening for
    this.unbind();

    // I also do a little check here to see if the current View has defined an `onClose`
    // method. If so, I will call it.
    if (typeof this.onClose === 'function') {
        this.onClose();
    }
};

Here’s what a View’s onClose implementation might look like:

var FooView = Backbone.View.extend({
    onClose: function () {
        // In `close`, you can unbind any events you listened for in `initialize`
        this.model.off('change');

        // Additionally, if your View has any child Views that it created at any point,
        // you can iterate over them and call `close` on each child View.
        _.each(this.childViews, function (view) {
            view.close();
        });
    }
    // ...
});

So that provides a way of keeping your memory footprint nice and small whilst helping keep the DOM free of clutter, and it does so in a way that you can use across any Backbone View. I find myself adding this to almost every Backbone project that I work on.

I hope this post helped you grok Backbone’s View system more fully. If you have any questions or advice, please feel free to leave me a comment!

    • #javascript
    • #backbone.js
    • #underscore.js
    • #handlebars
    • #mustache
  • 10 months ago
  • Comments
  • Permalink
  • Share
    Tweet

Recent comments

Blog comments powered by Disqus
← Previous • Next →

About

Avatar Random thoughts by a software craftsman & design nerd.

Pages

  • About Json
  • Backbone.js

json.elsewhere

  • jasonoffutt on Dribbble
  • jasonoffutt on Behance
  • JasonOffutt on Forrst
  • @JasonOffutt on Twitter
  • JasonOffutt on Foursquare
  • JasonOffutt on github

Latest Tweets

loading tweets…

Following

My favs

  • Post via izs
    Beliefs

    Don’t believe everything you hear. You will by default. It takes a lot of effort to un-believe it once you hear it.

    Don’t believe...

    Post via izs
  • Post via izs
    On ES 6 Modules

    A few things have rubbed me the wrong way about the current Modules and Module Loader specification. I regret that I have not been...

    Post via izs
  • Post via penderworth
    What's wrong with Android

    image

    In the recent months, I have spent a lot of time using an HTC ThunderBolt after selling my iPhone 4 because AT&T’s...

    Post via penderworth
See more →
  • RSS
  • Random
  • Archive
  • Ask me anything
  • Mobile

Effector Theme by Carlo Franco.

Powered by Tumblr