#rails#backbone

Using Backbone JS in multipage application

Amitava's avatar

Amitava

Recently we worked on a cloud resouces management application written on rails for one of our clients. Think about this app as GUI for Amazon AWS. All the resources will be displayed in tabular form and ajax requests for management like creation, updation and deletion of resources. Soon application became more complex because of lots of server side javascript code directly interacting with DOM. As a result testing became more complex, DOM changes used to break javascript code, and tests used to break often. This encouraged us to find a better alternative where we could use javascript in more modular fashion and also test it. Hence we decided to use backbone.js

Since we were already using coffeescript we added the rails-backbone gem as it has generators for creating scaffolds in coffeescript. Initially we were using the generated code but as we became more confident we stopped using the generator. Also we didn't use the router as this was not a single page app we were building. We were using backbonejs only for pages where we needed to implement client-side filtering of tabular data. This worked really well than we expected. Not only the filtering code was easier to maintain, it became quite easy to test as well. Did we mention we used jasmine for testing all our javascript code :). Later we started to use backbone for forms and other pages as well.

It was all working fine in terms of performance, but then we found a few challenges as the code-base was growing and becoming complex. Most views and models were using similar code and logic so we decided to abstract common behaviour and logic in base classes and subclass our views and models to share the same code. For the entire application we used the following flow -

  1. When the page is loaded it initializes the code to create a ListView.
  2. ListView then fetches the resources through collection and renders in a tabular format.
  3. Each row in the table itself is rendered with a ModelView.
  4. Create and update forms are managed using a FormView.

To begin with we created a ListView class with common features to fetch and dispaly data in a table.

    class App.Views.ListView extends Backbone.View
      initialize: (options) ->
        # Here we bind collection events
        @collection.listenTo 'add', 'onAdded'
        @collection.listenTo 'remove', 'onRemoved'

      addAll: () =>
        # Iterate the collection and call `@addOne`

      addOne: (model) =>
        # Render each model inside the table

      renderEmpty: =>
        # Render message if the collection is empty

      fetchCollection: (options)->
        # This is where the data is fetched through a collection

      render: (options) =>
        # Call `@addAll` to start the rendering process
        this

Then we built the ModelView to extract all common features to display a single row and handle events.

    class App.Views.ModelView extends Backbone.View
      events: ->
        "click  .edit" : "onEdit"
        "click  .destroy" : "onDestroy"

      initialize: ->
        # Here we bind or initialize model events
        @model.listenTo 'change', 'onChanged'

      onEdit: (e)=>
        # Handle displaying the edit form

      onDestroy: (e)=>
        # Handle deletion of a single model

And finally there is the FormView for which is responsible for rendering the forms for creating and updating resources.

    class CloudGui.Views.ModalFormView extends Backbone.View
      # Since we were using twitter-bootstrap modal, we set the dom attributes here.
      attributes:
        class: "modal hide fade"
        tabindex: "-1"
        "data-backdrop": "static"
        "data-keyboard": "false"

      events: ->
        "submit       form" : "onFormSubmit"
        "ajax:success form" : "onFormSuccess"
        "ajax:error   form" : "onFormError"

      onFormSubmit: =>
        # It does client-side validation if required and then submits the
        # form using ajax.

      onFormSuccess: (event, data)=>
        # Here we add the newly created resource to the collection.
        # For update, it updates the model attributes.
        # In both the cases the `ModelView` automatically renders the changes.

      onFormError: (event, xhr, status, error) =>
        # Handles server-side errors

We did not use JSON format for submitting the data since we had to support file uploads. And also the format of input attributes were different than the models. So we decided to use regular ajax form submission where the data is sent using form-url-encoded (or multipart/form-data for file uploads) content-type and the server returning JSON. The rest is then handled by onFormSuccess or onFormError.

One important lesson we learnt is to be careful with handling events in Backbone. It can create memory leaks if the event binding is not done in a correct manner. We were using on to listen to collection or model events but then we found that it does not release the event handlers when the remove function is called on a view object. We used listenTo instead of on and all the events were unbound when the view was removed.

Using backbone.js outside single page application is easy and worked pretty well for us. It enabled us to have proper frontend architecture (rather than jquery callback spaghetti), it made unit testing frontend features easier without resorting to a integration test framework (like Cucumber), we totally recommend this approach if you are consuming bunch of JSON and don't want to go whole hog single page.

More to follow on other interesting aspects of backbone pagination and real time event handling. Watch out.