I planned to learn backbone to prepare for the startup weekend on 5-7 Feb. I participated in it so that I can build a social product idea I wanted to build from long. Here it goes.

Introduction

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.

With Backbone, you represent your data as Models, which can be created, validated, destroyed, and saved to the server. Whenever a UI action causes an attribute of a model to change, the model triggers a “change” event; all the Views that display the model’s state can be notified of the change, so that they are able to respond accordingly, re-rendering themselves with the new information. In a finished Backbone app, you don’t have to write the glue code that looks into the DOM to find an element with a specific id, and update the HTML manually — when the model changes, the views simply update themselves. The single most important thing that Backbone can help you with is keeping your business logic separate from your user interface.

Model

  • Orchestrates data and business logic.
  • Loads and saves from the server.
  • Emits events when data changes.

A Model manages an internal table of data attributes, and triggers “change” events when any of its data is modified. Models handle syncing data with a persistence layer — usually a REST API with a backing database. Design your models as the atomic reusable objects containing all of the helpful functions for manipulating their particular bit of data. Models should be able to be passed around throughout your app, and used anywhere that bit of data is needed.

View

  • Listens for changes and renders UI.
  • Handles user input and interactivity.
  • Sends captured input to the model.

A View is an atomic chunk of user interface. It often renders the data from a specific model, or number of models — but views can also be data-less chunks of UI that stand alone. Models should be generally unaware of views. Instead, views listen to the model “change” events, and react or re-render themselves appropriately.

Collection

A Collection helps you deal with a group of related models, handling the loading and saving of new models to the server and providing helper functions for performing aggregations or computations against a list of models. Aside from their own events, collections also proxy through all of the events that occur to models within them, allowing you to listen in one place for any change that might happen to any model in the collection.

API Integration

Backbone is pre-configured to sync with a RESTful API. Simply create a new Collection with the url of your resource endpoint:

var Books = Backbone.Collection.extend({
  url: '/books'
});

The Collection and Model components together form a direct mapping of REST resources using the following methods:

GET  /books/ .... collection.fetch();
POST /books/ .... collection.create();
GET  /books/1 ... model.fetch();
PUT  /books/1 ... model.save();
DEL  /books/1 ... model.destroy();

When fetching raw JSON data from an API, a Collection will automatically populate itself with data formatted as an array, while a Model will automatically populate itself with data formatted as an object:

[{"id": 1}] ..... populates a Collection with one model.
{"id": 1} ....... populates a Model with one attribute.

However, it’s fairly common to encounter APIs that return data in a different format than what Backbone expects. For example, consider fetching a Collection from an API that returns the real data array wrapped in metadata:

{
  "page": 1,
  "limit": 10,
  "total": 2,
  "books": [
    {"id": 1, "title": "Pride and Prejudice"},
    {"id": 4, "title": "The Great Gatsby"}
  ]
}

In the above example data, a Collection should populate using the “books” array rather than the root object structure. This difference is easily reconciled using a parse method that returns (or transforms) the desired portion of API data:

var Books = Backbone.Collection.extend({
  url: '/books',
  parse: function(data) {
    return data.books;
  }
});

View Rendering

Each View manages the rendering and user interaction within its own DOM element. If you’re strict about not allowing views to reach outside of themselves, it helps keep your interface flexible — allowing views to be rendered in isolation in any place where they might be needed.

Backbone remains unopinionated about the process used to render View objects and their subviews into UI: you define how your models get translated into HTML (or SVG, or Canvas, or something even more exotic). It could be as prosaic as a simple Underscore template, or as fancy as the React virtual DOM. Some basic approaches to rendering views can be found in the Backbone primer.

Routing with URLs

In rich web applications, we still want to provide linkable, bookmarkable, and shareable URLs to meaningful locations within an app. Use the Router to update the browser URL whenever the user reaches a new “place” in your app that they might want to bookmark or share. Conversely, the Router detects changes to the URL — say, pressing the “Back” button — and can tell your application exactly where you are now.

Sample app - Service form

Next task is to make a service form as explained in Your First Backbone.js App - Service Chooser.

Some of the issues I faced while creating this form:

  1. The article hasn’t specified any css. So I copied the css from the demo.
  2. Collection.where : http://backbonejs.org/#Collection-where
  3. tagname: this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view’s tagName, className, id and attributes properties. If none are set, this.el is an empty div, which is often just fine. An el reference may also be passed in to the view’s constructor.
  4. listenTo: http://backbonejs.org/#Events-listenTo
  5. .prop: http://api.jquery.com/prop/
  6. this context: Scope and this in javascript

Hello Backbone

This explains the modularity brought by Backbone, in a very simple way.

Some of the issues I faced:

  1. bindAll: http://underscorejs.org/#bindAll, http://blog.bigbinary.com/2011/08/18/understanding-bind-and-bindall-in-backbone.html
  2. el vs tagName: http://stackoverflow.com/questions/7432560/backbonejs-in-a-view-whats-the-difference-between-el-and-tagname
  3. _([1,2,3]).each: http://underscorejs.org/#chaining

Diving Deeper with Backbone

Developing Backbone.js Applications- By Addy Osmani: Backbone Basics is really great covering a lot of details.

Model

  • initialize()
  • defaults
  • Model.get() provides easy access to a model’s attributes.
  • toJSON(): read or clone all of a model’s data attributes.
    • This method returns a copy of the attributes as an object (not a JSON string despite its name).
    • When JSON.stringify() is passed an object with a toJSON() method, it stringifies the return value of toJSON() instead of the original object.
  • Model.set() sets a hash containing one or more attributes on the model.
    • When any of these attributes alter the state of the model, a change event is triggered on it.
    • Change events for each attribute are also triggered and can be bound to (e.g. change:name, change:age)
  • Models expose an .attributes attribute which represents an internal hash containing the state of that model.
    • Setting values through the .attributes attribute on a model bypasses triggers bound to the model.
    • Passing {silent:true} on set doesn’t delay individual “change:attr” events. Instead they are silenced entirely:

      ```javascript var Person = new Backbone.Model(); Person.on(“change:name”, function() { console.log(‘Name changed’); }); Person.set({name: ‘Andrew’}); // log entry: Name changed

      Person.set({name: ‘Jeremy’}, {silent: true}); // no log entry

      console.log(Person.hasChanged(“name”)); // true: change was recorded console.log(Person.hasChanged(null)); // true: something (anything) has changed ```

  • Listening for changes to your model: If you want to receive a notification when a Backbone model changes you can bind a listener to the model for its change event. A convenient place to add listeners is in the initialize() function as shown below:

    ```javascript var Todo = Backbone.Model.extend({ // Default todo attribute values defaults: { title: ‘’, completed: false },

    initialize: function(){
      console.log('This model has been initialized.');
      this.on('change:title', function(){
          console.log('Title value for this model has changed.');
      });
    },
    
    setTitle: function(newTitle){
      this.set({ title: newTitle });
    }   });
    

    var myTodo = new Todo();

    // Both of the following changes trigger the listener: myTodo.set(‘title’, ‘Check what's logged.’); myTodo.setTitle(‘Go fishing on Sunday.’);

    // But, this change type is not observed, so no listener is triggered: myTodo.set(‘completed’, true); console.log(‘Todo set as completed: ‘ + myTodo.get(‘completed’));

    // Above logs: // This model has been initialized. // Title value for this model has changed. // Title value for this model has changed. // Todo set as completed: true ```

  • Validation
    • Backbone supports model validation through model.validate()
    • By default, validation occurs when the model is persisted using the save() method or when set() is called if {validate:true} is passed as an argument.

      ```javascript var Person = new Backbone.Model({name: ‘Jeremy’});

      // Validate the model name Person.validate = function(attrs) { if (!attrs.name) { return ‘I need your name’; } };

      // Change the name Person.set({name: ‘Samuel’}); console.log(Person.get(‘name’)); // ‘Samuel’

      // Remove the name attribute, force validation Person.unset(‘name’, {validate: true}); // false ```

    • If the attributes provided are valid, nothing should be returned from .validate(). If they are invalid, an error value should be returned instead.
    • On error:
      • An invalid event will be triggered, setting the validationError property on the model with the value which is returned by this method.
      • .save() will not continue
    • Note: the attributes object passed to the validate function represents what the attributes would be after completing the current set() or save(). This object is distinct from the current attributes of the model and from the parameters passed to the operation. Since it is created by shallow copy, it is not possible to change any Number, String, or Boolean attribute of the input within the function, but it is possible to change attributes in nested objects
    • Validation on initialization is possible but of limited use, as the object being constructed is internally marked invalid but nevertheless passed back to the caller.
  • unset() removes an attribute by deleting it from the internal model attributes hash.

Views

  • el is basically a reference to a DOM element and all views must have one.
  • There are two ways to associate a DOM element with a view: a new element can be created for the view and subsequently added to the DOM or a reference can be made to an element which already exists in the page.
  • If you want to create a new element for your view, set any combination of the following properties on the view: tagName, id, and className. A new element will be created for you by the library and a reference to it will be available at the el property. If nothing is specified tagName defaults to div.
  • $el and $(): The view.$el property is equivalent to $(view.el) and view.$(selector) is equivalent to $(view.el).find(selector).
  • When declaring a View, options, el, tagName, id and className may be defined as functions, if you want their values to be determined at runtime.
  • Overriding this.el needs to both change the DOM reference and re-bind events to the new element (and unbind from the old). setElement will create a cached $el reference for you, moving the delegated events for a view from the old element to the new one.
  • render() is an optional function that defines the logic for rendering a template.
    • The _.template method in Underscore compiles JavaScript templates into functions which can be evaluated for rendering.
    • The render() method uses this template by passing it the toJSON() encoding of the attributes of the model associated with the view. The template returns its markup. A common Backbone convention is to return this at the end of render(). This is useful for a number of reasons, including:
      • Making views easily reusable in other parent views.
      • Creating a list of elements without rendering and painting each of them individually, only to be drawn once the entire list is populated.
  • The Backbone events hash allows us to attach event listeners to either el-relative custom selectors, or directly to el if no selector is provided.
    • An event takes the form of a key-value pair 'eventName selector': 'callbackFunction' and a number of DOM event-types are supported, including click, submit, mouseover, dblclick and more.
    • What isn’t instantly obvious is that while Backbone uses jQuery’s .delegate() underneath, it goes further by extending it so that this always refers to the current view object within callback functions.
    • The declarative, delegated jQuery events means that you don’t have to worry about whether a particular element has been rendered to the DOM yet or not. Usually with jQuery you have to worry about presence or absence in the DOM all the time when binding events.
    • Note that you can also bind methods yourself using _.bind(this.viewEvent, this), which is effectively what the value in each event’s key-value pair is doing. Below we use _.bind to re-render our view when a model changes.

      javascript var TodoView = Backbone.View.extend({ initialize: function() { this.model.bind('change', _.bind(this.render, this)); } });

Collections

  • Collections are sets of Models and are created by extending Backbone.Collection.

    ```javascript var Todo = Backbone.Model.extend({ defaults: { title: ‘’, completed: false } });

    var TodosCollection = Backbone.Collection.extend({ model: Todo });

    var myTodo = new Todo({title:’Read the whole book’, id: 2});

    // pass array of models on collection instantiation var todos = new TodosCollection([myTodo]); console.log(“Collection size: “ + todos.length); // Collection size: 1 ```

  • Models can be added and removed using the add() and remove() methods. Both accept individual models and lists of models.
  • When using add() on a collection, passing {merge: true} causes duplicate models to have their attributes merged in to the existing models, instead of being ignored.
  • The most straight-forward way to retrieve model is to use Collection.get() which accepts a single id.
    • In client-server applications, collections contain models obtained from the server. Anytime you’re exchanging data between the client and a server, you will need a way to uniquely identify models. In Backbone, this is done using the id, cid, and idAttribute properties.
    • Each model in Backbone has an id, which is a unique identifier that is either an integer or string (e.g., a UUID). Models also have a cid (client id) which is automatically generated by Backbone when the model is created. Either identifier can be used to retrieve a model from a collection.
    • The idAttribute is the identifying attribute name of the model returned from the server (i.e. the id in your database).
    • The value of a model’s idAttribute should be set by the server when the model is saved.
  • Internally, Backbone.Collection contains an array of models enumerated by their id property.
  • As collections represent a group of items, we can listen for add and remove events which occur when models are added to or removed from a collection. Here’s an example:

    ```javascript var TodosCollection = new Backbone.Collection();

    TodosCollection.on(“add”, function(todo) { console.log(“I should “ + todo.get(“title”) + “. Have I done it before? “ + (todo.get(“completed”) ? ‘Yeah!’: ‘No.’ )); });

    TodosCollection.add([ { title: ‘go to Jamaica’, completed: false }, { title: ‘go to China’, completed: false }, { title: ‘go to Disneyland’, completed: true } ]);

    // The above logs: // I should go to Jamaica. Have I done it before? No. // I should go to China. Have I done it before? No. // I should go to Disneyland. Have I done it before? Yeah! ```

  • In addition, we’re also able to bind to a change event to listen for changes to any of the models in the collection.

    ```javascript var TodosCollection = new Backbone.Collection();

    // log a message if a model in the collection changes TodosCollection.on(“change:title”, function(model) { console.log(“Changed my mind! I should “ + model.get(‘title’)); });

    TodosCollection.add([ { title: ‘go to Jamaica.’, completed: false, id: 3 }, ]);

    var myTodo = TodosCollection.get(3);

    myTodo.set(‘title’, ‘go fishing’); // Logs: Changed my mind! I should go fishing ```

Building a Todo application from scratch

I followed the complete tutorial and partially copied and typed the code. Some doubts which I faced are:

  1. The current css shared in the book is different from the examples for which the book has been written. So I have to find the right css. It can be found here.
  2. Also I got doubts about why to keep app variable in the global scope. Most of the times when I see backbone code, its wrapped in self executing wrapper. I have also heard about require.js.
  3. Also how underscore methods work on back elements.

But after all this I am still not completely satisfied by my learning. So I decided to write the complete todo app on my on again by only looking at the working example. Let see how I do it.

References:

  1. Learn Backbone.js
  2. Backbonejs - Getting Started
  3. Backbone primer
  4. Underscore template
  5. React virtual DOM
  6. Your First Backbone.js App - Service Chooser
  7. Hello Backbone.js
  8. Backbone Basics