Writing your first application using Backbone.js


Getting Started

As we know, writing a Single Page application using just jQuery is not quite a good idea especially when our application grows bigger and we need to modularize our codebase. To resolve this problem, there are many MV* frameworks available for us.

What is Backbone.js

Backbone.js is one of the many Javascript frameworks for creating MVC-like web applications which comes with a default template engine Underscore.js. Backbone.js lets us split our code into Model, Collection and View. The general idea is to organize an interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page.

On the front-end, it’s my architectural framework of choice as it’s both mature, relatively lightweight and can be easily tested using third-party toolkits such as Jasmine or QUnit.
– Developing Backbone.js Applications by Addy Osmani.

What is MVC

In this case, each contact is our Model, all the contacts group together by Collection and View renders the contacts details. We are going to use contacts mysql table for this application so contacts table is our Collection and each row exists in the table is our Model.

Let us begin

Let’s start by writing our first Backbone.js app – Addressbook in Backbone.js. Here is our 2-column layout, one for side links on the left and another to load forms/grid.

addressbookMVC/index.html – Main Layout

<div class='table abWrapper'>
  <div class='row'>
    <div class='cell abLinks'>
      <a href='#add_new_contact'>Add New Contact</a><br />
      <a href='#list_contacts'>List all Contacts</a><br />
      <a href='#search_contacts'>Search any Contact</a><br />
    </div>
    <div class='cell abPanel'>Loading... Please Wait.</div>
  </div>
</div>

Add New Contact – Backbone.View and Backbone.Model

We’ll write our first view addView and load it inside div.abPanel. We can write our custom view by extending Backbone.View.extend() method and can keep our templates either into a same page or can load it from a separate html file. In this case, we’ll simply wrap our template in <script> tag by giving it a special type and an id:

addressbookMVC/index.html – addNewContact Template


  <h2> Add New Contact</h2>
  
    <div>Full Name:</div>  <span class='false full_name_error'></span><br />
    <div>Email Id:</div>  <span class='false email_error'></span><br />
    <div>Phone:</div>  <span class='false phone_error'></span><br />
    <div>Address:</div> 
    
    <span class='success'></span>
    
  

Backbone View allows us to specify custom methods and properties but there are some predefined attributes you should use to simplify the flow and maintain the unity between all backbone apps.
el – specify the DOM element selector where you want to load your template within a page. In our case its div.abPanel.
template – The _.template method in Underscore compiles JavaScript template into function which can be evaluated for rendering.
initialize – The constructor to keep a piece of code to be executed while creating an instance of a view.

Below is our AB.addView backbone view which compiles the template mentioned above  and load it into el. 

addressbookMVC/js/script.js – addNewContact View

AB.addView = Backbone.View.extend({
  el: 'div.abPanel',
  template: _.template($('#addContactTemplate').html()),
  initialize: function () {
    _.bindAll(this, 'addContactPage');
  },
  addContactPage: function () {
    this.$el.html(this.template());
  }
});

In above code, we have used the _.bindAll method in Underscore which fixes the loss of context for this within methods. So we have to specify all those method names wherein we’ll use this.any_method or this.any_property i.e. this.$el, this.template etc.

In backbone.js, there is no need to grab elements the jquery way and attach some events to them. There is a special attribute events which allows us to attach event listeners to either custom selectors or directly to el if no selector is provided. It takes the form {‘eventName selector’ : ‘callbackMethod’}, So we can attach the submit event to our <form> in order to prevent the default form submission and fetch the form data. And once we get the data, we can simply call model.save() method to save the record into the DB which invokes Backbone.sync function every time we attempt to read or save a model to the server by making a RESTful JSON request.

Lets write our contactModel first. Notice that if a passed JSON data to model.save() contains an id key then a method name will be update in model.sync() else it will be create.

addressbookMVC/js/script.js – Contact Model

AB.contactModel = Backbone.Model.extend({
  sync: function (method, model, options) {
    if (method === 'create' || method === 'update') {
      return $.ajax({
        dataType: 'json',
        url: '../php/addNewContact.php',
        data: {
          id: (this.get('id') || ''),
          full_name: (this.get('full_name') || ''),
          email: (this.get('email') || ''),
          phone: (this.get('phone') || ''),
          address: (this.get('address') || '')
        },
        success: function (data) {
          // put your code after the contact is saved/updated.
        }
      });
    }
  }
});

Now we’ll create an instance of above model in our view and call the save() method.

addressbookMVC/js/script.js – addNewContact View

AB.addView = Backbone.View.extend({
  events: {
    'submit form#frmAddContact': 'addContact'
  },
  addContact: function (event) {
    var full_name = $('#full_name').val(),
        email = $('#email').val(),
        phone = $('#phone').val(),
        address = $('#address').val(),
        id = $('#id').val();
        
    if (id === '') {
      var contactmodel = new AB.contactModel({
        full_name: full_name,
        email: email,
        phone: phone,
        address: address
      });
    } else {
      var contactmodel = new AB.contactModel({
        id: id,
        full_name: full_name,
        email: email,
        phone: phone,
        address: address
      });
    }
    contactmodel.save();
    return false;
  }
});

List contacts – Backbone.View & Backbone.Collection

Usually we make an AJAX request to fetch all the data in JSON format but there is a special method collection.fetch() available to fetch the default set of models for the collection from the server based on the url defined in the collection. We’ll write our collection by specifying the model and the url:

# addressbookMVC/js/script.js – Contacts Collection

AB.contactsCollection = Backbone.Collection.extend({
  model: AB.contactModel,
  url: '../php/listContacts.php'
});

And we’ll put the templates in index.html:

addressbookMVC/index.html – listContacts Template


  <h2>List Contacts</h2>
  <table id='contactsGrid' width='100%' border='1' cellspacing='1' cellpadding='5'>
    <tr>
      <td width='25%'><b>Full Name</b></td>
      <td width='25%'><b>Email ID</b></td>
      <td width='15%'><b>Phone</b></td>
      <td width='25%'><b>Address</b></td>
      <td width='10%' align='center'><b>Action</b></td>
    </tr>
    
      <tr><td colspan='5'>No Record Found</td></tr>
    
          &lt;tr data-id=&gt;
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td align='center'><a>Edit</a> | <a>Delete</a></td>
          </tr>
    
  </table>

And then call the fetch() within its View:

addressbookMVC/js/script.js – listAllContacts View

AB.listView = Backbone.View.extend({
  el: 'div.abPanel',

  template: _.template($('#listContactsTemplate').html()),

  initialize: function () {
    _.bindAll(this, 'listContactsPage', 'render');
  },

  render: function (response) {
    var self = this;

    this.$el.html(this.template({contacts: response}));
  },

  listContactsPage: function (querystring) {
    var self = this;

    AB.contactscollection.fetch({
      data: querystring,
      success: function (collection, response) {
        self.render(response);
      }
    });
  }
});

Search contacts – Backbone.View and Backbone.Collection

I will not explain this section in detail because it is pretty much similar when it comes to load a searchContacts template into a page and to show all the contacts based on the search criteria, we can reuse the same method listContactsPage({full_name: ‘alice’}) from AB.listView class. We’ll get it once we go through the codebase. Lets move on to Backbone.Router.

Backbone.Router

In Backbone, routers are used to help manage application state and for connecting URLs to application events. This is achieved using hash-tags with URL fragments, or using the browser’s pushState and History API. Some examples of routes may be seen below:
http://addressbook.com/#addNewContact
http://addressbook.com/#editContact/6
When all of our routers have been created, and all of the routes are set up properly, call Backbone.history.start() to begin monitoring hashchange events, and dispatching routes. Inside run method, we are updating the hash to #add_new_contact and triggering it immediately so that renderAddNewContactPage will be called immediately once the page is loaded.

# addressbookMVC/js/script.js – Backbone.Router

var AB = {
  run: function () {
    this.addview = new this.addView();
    this.listview = new this.listView();
    this.searchview = new this.searchView();
    this.contactscollection = new AB.contactsCollection();
    this.router = new this.Router();
    Backbone.history.start();
    this.router.navigate('add_new_contact', {trigger: true});   
  }
};

AB.Router = Backbone.Router.extend({
  routes: {
    'list_contacts': 'renderListContactsPage',
    'add_new_contact': 'renderAddNewContactPage',
    'search_contacts': 'renderSearchContactsPage',
    'edit_contact/:id': 'renderEditContactPage'
  },

  renderAddNewContactPage: function () {
    AB.addview.addContactPage();
  },

  renderListContactsPage: function () {
    AB.listview.setElement('div.abPanel');
    AB.listview.listContactsPage();
  },

  renderSearchContactsPage: function () {
    AB.searchview.searchContactsPage();
  },  

  renderEditContactPage: function (id) {
    AB.addview.addContactPage(id);
  }
});

Summary

I simply love to use Backbone.js in my projects, its amazing.

Download the source code from github

And do share your thoughts in the comment section below.

Updates:

Since a very long time, I wanted to try out Require.js in my projects and what could be the best way than to rewrite the addressBook in backbone.js and require.js again.
So here it is: addressBook in Backbone.js and Require.js

Happy Hacking 🙂

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.
Advertisement

jQuery video series: The making of Address book


Hello friends,

I really enjoyed making these screen-casts for the first time and it was an awesome experience. I personally believed that to learn any new technology or library, one has to get acquainted with the basics and write a small generic application to get the hang of it. Below are the things that I’ve covered in this video series:

The Addressbook in jQuery
The Addressbook in jQuery
  • Basic Instinct 
    • Why to use jQuery and Where to get it from?
    • $ and beyond (overriding $)
    • Difference between document.ready and window.load
    • Selectors, dom traversing and events
    • Ajax and writing custom jquery function
  • Welcome to the jungle of jQuery
    • Selectors to fetch the values of form elements
    • Ajax calls to save the data (using php and mysql)
  • A list apart
    • List all the contacts in a tabular format
    • Delete contact functionality
  • Searching through
    • Search contacts by name or email

You can download the code from github.com.

References: 

P.S. There are many ways to improve the code. You can add edit-contact functionality or you can avoid unnecessary ajax calls to load forms/grids by simply keeping them in index.html and show/hide on demand instead of calling `$.load()` every time. Plus, its not always a valid choice to use only jQuery to write a Single Page app as there are lots of good MVC or MVVM frameworks available that can save your substantial amount of time and effort.

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.