Backbone.js Tips : Lessons from the trenches

At SupportBee, we are building a Gmail like single page app for customer support (a Zendesk Alternative). Naturally, this is extremely javascript heavy. To organize our code, we use Backbone.js and Jasmine for testing (if you are looking for an introduction to Backbone, you can also checkout my Backbone.js talk at RubyConf India). Over the last few months, we have written (and rewritten) a lot of code using Backbone.js and during this time, several patterns have emerged that were not obvious from reading the docs/examples available on the net when we started out.

Models should not know about Views

This may seem like an obvious one but unfortunately the official Todo example preaches this by saving a reference to the associated view in the model and using it to remove the view when the list is cleared. The problem with this pattern is that it does not scale well when you have a complex app with several views associated with a model. A better way is to create a view and bind it to the model's destroy event and remove itself whenever the model is destroyed. This way, the model does not have to worry about view cleanup. The same goes for re-rendering/updating the view on changes to the model. Don't make the model do the book-keeping. Update: The To-do example has been updated now.

Views Collections should keep track of the sub views

The most common backbone pattern is to have a model and a collection of models and initialize a view that initializes this collection. The collection then fetches and parses the response and stores the model. It also fires an add event that views can bind to (to add or remove subviews). In fact it fires a remove event too when models are removed. Here is a code snippet from the TicketList View class. this refers to the TicketView class and this.ticketList is an instance of TicketList collection.

this.ticketList.bind("add", this.addOne);
this.ticketList.bind("remove", this.removeOne)

In the callback, you can create views for that model (in our case that means a ticket summary row for every ticket)

    addOne: function(ticket) {
      view = new SB.Views.TicketSummary({
        model: ticket
      });
      this._viewPointers[ticket.cid] = view;
   }

In the above code snippet, we are storing a reference to the view corresponding to the model in the main view's collection's _viewPointers variable.

This way, everytime a model is removed from the collection (for example, when a ticket is deleted), you can handle the remove event easily

   removeOne: function(ticket) {
      this._viewPointers[ticket.cid].remove();
    }

No need to store the view in the model. This is very similar to how Backbone stores a reference to the model in a collection internally. Notice that the view collection is not storing a reference to the view that initializes it, but the views that it initializes. (thanks to vmind for the feedback on this section) Please look at thorn's comment on this. I copied the right code but mixed up collections and view.

Events are you friend

This follows from the first two.

There is a good reason why Backbone Model, Collection and View all include the Event module (pardon the ruby terminology). Raising and binding to events is the clean way to do things in Backbone (and Javascript). Apart from the events already provided by backbone, you can raise custom events very easily. Given the fact that all model events are propagated up by the collection, you can really simplify your View update logic.

Make sure jQuery isn't unloading your events when you don't want it to

If you are building an app where you create views on the fly and attach/remove them to the dom, you may have a problem. Everytime you remove a view from the dom, jQuery unloads all the events. So you can't have a reference to a view and remove it from the dom and then re-attach it later. All your events would have been unloaded. If you wanna keep the views around, a better idea is to hide them using display:none. However, you should not abuse this and recycle views that you are not going to use for a while (and prevent memory leaks).

Models for objects not persisted on the server

Almost all of the examples on the net talk about Backbone models that are persisted on the server or using local storage. However, you should look into creating models (and corresponding collections) for objects that will not be saved on the database. For example, in case of SupportBee, we have a Screen model that is used for adding, well screens to the page. We also have a global Screens collection that creates a view everytime a screen is added to it. The screen objects are never persisted on the server but creating a model and collection helps us use events and other backbone patterns to simplify our code. So we are able to do something like

mainView.screens.add(new SB.Models.Screen({
      title: 'Unassigned',
      href: '#unassigned',
      buttons: [SB.Views.ArchiveButton,
                SB.Views.AssignButton],
      default: true,
      displayTotal: true,
      listings: [
          {
            title: 'Unassigned',
            listingName: 'unassigned_and_unanswered',
            displayTotal: true
          }
      ]
    }))

mainView is the global top level view object and screens is the collection instance. Adding the new screen model fires an add event which then goes and setups up the route for #unassigned url and renders the ticket listings etc. This way, we can dynamically add screens anytime we want.

Finally, always test your javascript code. We extensively use Jasmine and Sinon.js. As the client side logic grows, you want good test coverage to be able to refactor mercilessly. More on that in another post. If you have spotted any patterns or have any feedback on the post, please leave a comment.



blog comments powered by Disqus