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 -
- When the page is loaded it initializes the code to create a
ListView
. ListView
then fetches the resources through collection and renders in a tabular format.- Each row in the table itself is rendered with a
ModelView
. - 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.