Lets Talk – An EventBus in Backbone.js

A few days back, we ran into an issue on a project using Backbone.js where we needed two Backbone views to be able to talk to one another. This is not an issue when there is a view A that has a reference to view B and binds to any events that propagate from B. Our issue was a little more in-depth because these views did not have references to each other, they only shared a model. We discovered two ways to handle this communication issue: the hack and the right way.

The Setup

We have a main view rendering a preview page view as well as individual item views. The main view gives the preview view a collection of items to generate and show item previews. The previews include a title, description and comment count of the items. When a user clicks a preview view to see the full version, the main view builds the full item view and swaps it in on the page (in place of the preview page). We hold copies of the full item views and the preview view in memory to allow easy swapping between pages without needing to re-render.

The Problem

When we load the full item view, we then load in all the comments associated with it. We display the comments on the page and fill in a comment count on the full view. But wait, didn’t we already have a comment count for the preview item? We did, but that was only a static attribute on the item. In the full item view, when we add or remove a comment, the comment count changes to reflect the actual count. With this setup, we never updated the comment count on the preview. Since our app swaps out the preview page and full item pages via JavaScript with the JSON provided on the full page load, that static comment count becomes stale.

The Fixes

Our goal was to give the comment collection to the item preview view when we pulled it from the server during the full item view creation. This would allow the preview view to stay up-to-date with comment count changes and keep the UI consistent. The only thing these view have directly in common was the item model (remember that the item previews were children of the preview view, which is a sibling of the full item views).

Initially our solutions were ugly. We could have the preview view bind to an event on the model and the full view trigger that event on the model, passing along the comment collection. This sounds reasonable, except that we are binding and triggering on a model that has no notion of this. It worked, but it felt dirty to just happen to have a common model to pass data over. More importantly, it would cause rework if we used two different models to render the full view and the preview view – suddenly there is no shared model.

The next idea was to pass the comment collection, after it loaded, up to the master view. The master view would then have to pass the collection to the preview view, which would then pass it down to the item preview view. This solution would work too, but would be a headache to implement. We had done this elsewhere, and it was time consuming. It not only took time, it also posed a risk if we were to add or remove a view in the chain – breaking the data sharing.

The EventBus

Backbone.js has events and event binding without a notion of a shared communication channel like an EventBus. Our final solution to our issue (surprise!) was to implement one. Below is our setup.

<code class="language-coffeescript">
MyApp.EventBus = _.extend({}, Backbone.Events)

Simple, huh? In our application namespace we extended an empty object (with the power of underscore) to create a Backbone.Events object. This allows us to simply bind and trigger on the object from anywhere in the app.

<code class="language-coffeescript">
vent = MyApp.EventBus

vent.bind 'foo', -> console.log 'bar'

vent.trigger 'foo'