One of These Days

Backbone Js

20 Nov 2010

Backbone.js

Backbone.js is a simple, very lightweight MVC framework built on jQuery and Underscore.js, a “utility belt for Javascript”. As with any MVC framework, the core premise of Backbone is to separate an application’s representation and storage of data, from the presentation of it. 2 main classes provide this functionality, Backbone.Model and Backbone.View. The neat thing is that when you associate a view with a model, any changes to the model will be reflected in the view without you having to write any linking code.

This sounds all well and good, but is it really that useful? Yes. Keen to get my teeth into it, I rewrote Vital Gifts app with it. The process took less time than the initial version, and the end result was a marginally larger Javascript file that executed faster while saving a large proportion of my sanity. The reason for this usefulness stems from the modularity it lends to your application; you can easily create self contained visual elements that have all of the data persistence and manipulation they need, and place them onto the page with one line. To illustrate this modularity, let’s create a simple friend selector that could be easily fitted out to use the Facebook Graph API.

This creates an empty model that we can use to store information about the selected friend. Note that we’re not specifying any attributes such as name or photo. This is because the Backbone model is just a JSON object, and so no schema is necessary – we can add and fetch attributes however we see fit.

window.User = Backbone.Model.extend({});

In a typical MVC setup, the view would contain a template with rudimentary data manipulations. Any events received by the view are sent to the controller for processing, and then results are sent back to the view. Backbone ‘breaks’ this model slightly by binding changes to the model directly with the view object. The results are still passable however, with the use of a template framework such as Mustache. We can use the Backbone view object for controller style logic, and keep the presentation in a template.

window.UserView = Backbone.View.extend({

create a new model to store the selected user

    model : new User,  

the text field we’re using for the selector

    el : $("#friend-selector"), 

we want to listen for 2 events on the text field

    events : {
        "focus"     : "selectInput",
        "keyup"  : "friendSelected",
    },

This first section is all we need to get our view up and running. We tell the view what model it represents, what DOM element it is going to use, and we define a set of event/callback pairs to handle user interaction. To do the actual autocompletion we will use the jQuery autocomplete library.

    initialize : function(friends) {

set up autocomplete

        this.el.autocomplete(friends, {
            minChars: 0,
            width: 310,
            matchContains: false,
            autoFill: false,
            formatItem: function (row, i, max) {
               img = "<img src='" + row.picture + "' width='50' height='50' alt='friend-picture' />";
               return img + row.name;
            },
            formatMatch: function (row, i, max) {
               return row.name;
            },
            formatResult: function (row) {
               return row.name;
            }
       });

bind this object to the form, so we can access it from the result event

        this.el.data('scope', this);
        this.el.result(function(e, data, formatted) {
            $(e.target).data('scope').autocompleteSelected(data);
        });
    },

I won’t go into detail about the autocomplete library, as the documentation on it is fairly comprehensive. We are using the initialize method to take in an array of friends that we want to be searchable in the element – this functions exactly as a typical object constructor. The caveat with this however is the last section, which is unfortunately an ugly solution. The callback for the autocomplete plugin is set with the ‘result’ method. From within this, we need to be able to call a method on the view object so that the model can be updated. The view won’t be in the scope of the autocomplete plugin however, so we are using the data property of the text field to store a reference to the view object. Both the autocomplete and the view model are attached to the same element so the data property acts like a form of shared memory.

    selectInput : function(e) {
        $(e.target).select();
    },

    autocompleteSelected : function(friend) {
        if (friend != null) {
            this.model.set({
                name : friend.name
            }); 
        }
    },

    friendSelected : function(e) {
        this.model.set({
            name : this.el.val(),
        });
    

see if they typed in a correct name

     this.el.search();  
    }
});

selectInput is purely a convenience for the user. It will result in the contents of the text field becoming highlighted when clicked on, making it easy to type in a new friend. It should save the user 1 click on average.

autocompleteSelected is called when the user selects a friend that has been autocompleted. It calls ‘set’ on the model and sets the name parameter to be the chosen friend’s name.

friendSelected is called on the keyUp event. It is required in case the user wants to type in a friend that is not registered in the autocomplete. If this were to happen, the autocomplete plugin would never match it, and so the result callback would never be fired. This would leave the user model empty, even though the user has typed in a name. When the event is triggered, the model is set to be the current value of the text field, and ‘search’ is called on the text field. Search is a method that comes from the autocomplete plugin, and it simply forces the plugin to see if the current value is in its list. If it can’t find it, we have already updated the model, keeping it consistent, and if it does find it, autocompleteSelected will be called and the model will still have the friend’s name.

The 2 objects above are all that we need to have a self contained friend selector. To render it on the page, we simply create a new UserView object and pass in a list of friends, assumed for this example to exist as a global variable called ‘friends’.

user = new UserView(window.friends);

That’s it!