Backbone.js Tips : Lessons from the trenches

At SupportBee, we are building a Gmail like single page app for customer support. 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.



This entry was posted in backbone, javascript. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Han Wei

    “Everytime you remove a view from the dom, jQuery unloads all the events.”

    Are you talking about jQuery.remove() http://api.jquery.com/remove ?

    Maybe you should see jQuery.detach() http://api.jquery.com/detach :
    “The .detach() method is the same as .remove(), except that .detach() keeps all jQuery data associated with the removed elements. This method is useful when removed elements are to be reinserted into the DOM at a later time.”

  • http://www.prateekdayal.net Prateek Dayal

    Hey Han,

    Thanks for the question.

    No, I was not talking about .remove() .. I was talking about the case when you have a html view element thats currently being displayed and then you remove it from the dom. However, you still keep a reference to the view object (in backone) and then reattach the view to the dom. In that case the events would have been unloaded.

  • http://www.prateekdayal.net Prateek Dayal

    Actually, I just checked out detach and it may work too. I will have to try it out. Thanks for the tip

  • http://twitter.com/thorn0 thorn

    Collections are just an aggregate kind of Models. They should not know about Views too. In your example addOne and removeOne should be methods of View, not Collection.

  • http://www.prateekdayal.net Prateek Dayal

    Hi Thorn,

    Good catch. I copied the code from the view file but wrote collection in the post. I have updated the post.

  • http://twitter.com/ryan_stevens Ryan Stevens

    I completely agree with your tip for creating Models / Views that are not persistent on the server. Often times there are UI level interactions that need events to be facilitated and/or emitted to other non-related server oriented models. If these events / UI elements don’t really fit into a model, such as a collection of buttons that flip between different views, utilizing backbone as a mechanism will bring a greater uniformity of code organization.

  • http://multitasked.net Martin De Wulf

    Also about “”Everytime you remove a view from the dom, jQuery unloads all the events.”, do you know about http://api.jquery.com/delegate/

    From the doc: ” Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.”

    What is important, is “or in the future”, meaning that if you add elements matching the selector afterwards, they will have the event handler attached too.

    From what I understand of your article, this could lead to simplification in your code.

  • http://devblog.supportbee.com/2011/08/10/the-pros-and-cons-of-developing-a-complete-javascript-ui/ The pros and cons of developing a complete Javascript UI

    [...] Development stories from SupportBee.com Skip to content « Backbone.js Tips : Lessons from the trenches [...]

  • Pgherveou

    “Models should not know about Views” I agree with having a data driven approach, I am curious about how u deal with certain ui widget (like jquery ui) where most of the logic happen in the view and don’t always facilitate this approach

  • http://www.facebook.com/mihaiocoman Mihai Octavian Coman

    Seriously one of the best backbone articles I’ve seen so far – this is the kind of metatutorial that I’d like to see more of

  • http://twitter.com/prateekdayal Prateek Dayal

    Thanks Mihai

  • Rodolfo Barriga

    Great Article !!

    there is just one thing that I don’t Understand … yo say … hey … “Models should not know about Views” but in the last example .. yo create a Model.Screen .. with an array of view buttons ..???? (in the constructor of the Model) — there is a XXXButton Model … could a view doesn’t have a Model ?? … Best Rodolfo !! :-)

  • http://twitter.com/prateekdayal Prateek Dayal

    Rodolfo,

    The model basically contains the button classes that we want to instantiate buttons from. So it does not contain views. It contains the information to initiate the views. Another class uses this information to create the actual views and keeps track of it.

    We are basically using model to hold data about rendering a view basically.

    Did I answer your question?

  • Rodolfo Barriga

    Yeap, I get the point…. thanks :-)

  • http://www.seorus.com.au/ SEO Melbourne

    Thanx for Sharing !

blog comments powered by Disqus