Since we were already using
coffeescript we added the
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
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
ListViewthen fetches the resources through collection and renders in a tabular format.
- Each row in the table itself is rendered with a
- Create and update forms are managed using a
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
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
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.