Adding a Centralized Event Dispatcher on Backbone.js

This article contains my solution to adding a simple global event dispatcher to Backbone. It should also help noobies (myself included) understand how Backbone events work (which has one big gotcha).

Sharing Events = Global Dispatcher

Today, I came across a fairly mundane – and probably common – problem: I have two views that need to talk to each other. An example for this would be when you have a status bar in one corner that gets updated when a user completes an action in another area. It turns out that Backbone (as far as I can tell) does not support this use case very well out of the box. Well, it does: it expects you to build that yourself.

Backbone supports a model where you publish and subscribe to things happening in your views/models (commonly known as an “event dispatcher“). For example, you can make your application do stuff when a user clicks on something or if a model attribute changes. The native solution localizes these events (probably a good thing) to the model/view that you’re working with. This works for simple stuff, but when you need it in other parts of your application, things break down. The short answer is that you need to build a global event dispatcher A.K.A. global event “hub.” Everything publishes (“trigger”) and listens (“bind”) to events from this object.

This is a common pattern. In fact, I found two solid articles on this topic. The first explains in detail why you need an event dispatcher. The second shows you how to make the dispatcher natively accessible by all of your Backbone classes.

Event Dispatcher Gotcha: Native Events

However, I felt both solutions weren’t quite there. The first forces you to pass around an Event object everywhere you need it. This is nice from a decoupling standpoint, but highly error prone. The second solution is nice, but makes the caller oblivious to the global event namespace they are triggering/binding to. This is dangerous because Backbone has native generic events that are fired when certain things happen. For example, when you edit a Model, it automatically fires a “change” event into its event dispatcher (which bubbles up to its Collection too). This means if that dispatcher was global, every object would see a “change” firing every time every Model changed. Not good.

Goals

I came up with my own solution that accomplishes three main goals:

  1. Retain native Backbone event triggering/binding
  2. Allow the developer to trigger/bind to global events
  3. Not require the developer to pass things around

Solution Code

(If you can’t read CoffeeScript, just use this converter to convert it back to JavaScript)

The following (very simple) code modifies your Backbone definition to attach the global dispatcher to all objects. The dispatcher is easily accessible from any Model, Route, View, or  Collection through the global_dispatcher parameter. I thought about having it trigger events to the local dispatcher as well, but I decided that keeping them fully separate was in everybody’s best interest (to prevent events from accidentally colliding):

Example Code

The next section is a simple class I wrote that will demonstrate how these events work. I am defining a simple Collection and a Model. Note that I named the global and local events the SAME. This was to help demonstrate that the namespaces are in fact completely separate (as in, just because they are named the same doesn’t mean a local event will ever trigger a global event with the same name).

The following snippet illustrates triggering the events attached to the Collection. Note that we add a Model into this collection (which has no impact on the output). The trigger_stuff method triggers both a local and global event (in that order). The output shows that the events were picked up in the order fired (local, then global). Note that the Collection also listens to the “model_custom_action” event, which coincides with an event the Model triggers. This is very important.

The order of operations looks like this:

  1. Fire a local event
  2. The Collection picks it up
  3. END OF FIRST EVENT
  4. Fire global event
  5. The Collection’s globally attached event handler picks it up
  6. END OF SECOND EVENT

The next snippet shows what happens when you fire an event on a Model inside a Collection. It is also why I decided to keep things separate. This is a little hairy, so pay special attention.

The order of operations looks like this:

  1. Fire a local event
  2. The Model picks it up
  3. The Collection picks it up since all events in children bubble up
  4. END OF FIRST EVENT
  5. Fire global event
  6. The Collection’s globally attached event handler picks it up
  7. The Model’s globally attached event handler picks it up
  8. END OF SECOND EVENT

Notice that in step #6, the Collection’s binding fires BEFORE the Model’s. This is key.

Step #3 fires because there is a LOCAL binding to the “model_custom_action” event in the Collection. The local binding is reacting to an event triggered in the child Model’s local event dispatcher. In other words, any event triggered from the local event dispatcher in a Model will bubble up to the Collection’s local event dispatcher and be otherwise indistinguishable from events triggered directly from that Collection.

Step #6, however, is different. That event did NOT originate from the child model’s local event dispatcher. Instead, it is reacting to the global bindings, which happen to share the same name as the event we saw earlier in step #3. Because it didn’t bubble up, the events are being processed in the order they were bound (via the bind() function). The Collection was defined before the Model, thus, the Collection’s global binding fires first.

In this last snippet, we demonstrate how Models behave when not inside a Collection.

  1. Fire a local event
  2. The Model picks it up
  3. END OF FIRST EVENT
  4. Fire global event
  5. The Model’s globally attached event handler picks it up
  6. END OF SECOND EVENT

This is very straight forward if you managed to follow the last example. To get additional clarity, you may want to try renaming the events in the above class and re-run the examples.

By having the global dispatcher separate, you can consciously decide when an event should be “public,” as well as not clobber any existing Backbone functionality. Backbone is still really young (pre 1.0!), so I wanted to avoid using any solution that might break if they changed the internals. Also, completely preserving the behavior of event bubbling for Model-Collections is important to future proof my hack.

I hope this is useful for all of your Google-visitors!

  • Robert

    There is a memory leak in this script. When I used it as event dispatcher in a phonegap iOS project, the heap allocations slowly grew because of it. So beware to use it, as it could crash your app. Currently using the “vent” solution of Derick Bailey which doesn’t increase the heap.

    • Robert

      Sorry, pls delete this post. The leak was elsewhere.

  • Jeremy

    Interesting idea, but it seems terrible in practice. The core problem you start out with (two views need to communicate) can be solved so many ways in Backbone (the most obvious being A) sharing a model between the views, B) passing a reference from one view to the other). They may not magically work out of the box, but they’re WAY better than throwing all of your events in to one giant global space.

    If using Backbone (and frameworks like it) has taught us anything it’s that encapsulation is essential to managing serious web apps.

  • Enunna

    This would probably be a great solution were it not for the fucking CoffeeScript. Is JS syntax really so bad that we have to add another layer of abstraction on top that actually removes functionality? I.e. you can’t debug CoffeeScript. And I’m not going to get in an argument about how you can still debug the resultant JS. It’s just not the same. The code that CoffeeScript spits out just isn’t as clear and understandable as hand written JS, simply because YOU DIDN’T WRITE IT, and a human didn’t write it. I’d understand if JS were some sort of bytecode, but jesus people, JS is C-style syntax, which is very readable and meant for human consumption. You are littering the JS landscape with this pseudo-compiled BS. If you want ruby then go work with ruby. Get off my lawn, bah humbug and all that.

    • http://www.michikono.com/ Michi Kono

      Keep in mind people are “littering” their own code bases. I’ve found coffeescript to be less verbose for the same functionality, which means less lines of code to maintain. Also, keep in mind because it compiles, it means one CAN leave it if later it turns out to be a bad idea. Lastly, while it does remove the “feature” to debug, it also ADDS a LOT of features. The list is long and you’d have to read their documentation to see what they are, but some worth mentioning are: automatically protecting the global scope, standardized support for more formal OOP concepts, and string interpolation. I think code readability is a very important, if not the most important, “feature” since you spend 10% of a code’s lifetime writing it and 90% of its time reading it.

    • Frank

      Seriously dude crawl out of your hole and actually try it. CoffeeScript is really great and it is completely manageable and results in less code and less bugs. And yes advanced js syntax is bad and combined with the flexibility that the language provides it results in more bugs when there is a lot of people working on the same code base.

  • http://mutedsolutions.com Derick Bailey

    Augmenting the backbone objects with your dispatcher is a bad idea, IMO. You’re right in that the dependency injection that I showed in my original articles is stupid. I don’t do that anymore, either. It creates a huge mess.

    I think augmenting the Backbone constructs like you’re showing goes against your stated goal in the comment to redsquare, about future proofing against backbone changes. Augmenting an object you don’t own is always dangerous, no matter the language. Ruby, JavaScript and other dynamic languages state “best practices” as not doing this.

    My solution is to keep it simple and just attach my “vent” object to the application namespace: “MyApp.vent = _.extend({}, Backbone.Events)” done. All of my app objects just call out to this … and you are using a single namespace object for your app, right?

  • redsquare

    Damn phone. Apologies for the typos above

  • redsquare

    Nice, is there no better way to make the events that get triggered discoverable. Having them as hardcodef strings makes it a pita to see what is exposed. Could you not use an events object literal at the top of the class ti shiw others what events can be subscribed to rather than having to pick out a hard to find strung?

    • http://www.michikono.com/ Michi Kono

      I’m hitting this exact issue now after implementing this solution where I’m seeing “spaghetti” events. One benefit of creating a global dispatcher is that you are not limited to the way that backbone handles events natively. Of course, my goal was to leave the solution as open as possible to future proof it against backbone’s inevitable coming enhancements. It is entirely possible for you to create your own construct that extends Event (which is then access via global_dispatcher). I haven’t figured out what would be best here, but one idea I’ve thought about is having an event namespace registration process in the initializer that all global events would need to match. In other words, something like:
      // inside the initializer
      this.global_dispatcher.register_namespace(‘panel’)

      // listener
      this.global_dispatcher.bind_one(‘panel’, ‘special_button:click’, my_function)
      // normally might look like: this.global_dispatcher.bind(‘panel:special_button:click’)
      this.global_dispatcher.bind_all(‘panel’, my_function) // listen to all events from this namespace

      // later
      this.global_dispatcher.trigger_namespace(‘panel’, ‘special_button:click’)

      An alternate implementation could recognize such namespaces using the current “:” convention (parse out the first block).

      I’ll definitely post an update if I end up doing something like this. For now, I’m dealing with the event spaghetti by using conventions. I’m fairly new to writing a complex event driven application so I may be overlooking a best-practice for organizing such an app.

      • http://mutedsolutions.com Derick Bailey

        The idea about event registration with “channels” is overcomplicating things, IMO. It would take some code to implement this and may turn into a performance problem – especially if you’re trying to parse “:” in event names to determine the right channel. 

        If you’re publishing so many different events and have so many different event handlers registered at one time, that it becomes a bottleneck in itself to publish a single event, just create a second or third event aggregator as your channels: 

        MyApp.imageEvents = _.extend({}, Backbone.Events); 
        MyApp.userEvents = _.extend({}, Backbone.Events); 
        MyApp.someOtherEvents = _.extend({}, Backbone.Events}); 

        and on like that. There’s instant channels with no performance hit or additional code required.

        • Michi kono

          After some tinkering and getting more familiar, I think your approach makes more sense than mine when it comes to dealing with adding more event dispatchers. Ultimately, I think these solutions (mine included) should be avoided where possible. Once you start writing to global dispatchers, the allure is strong to just dump everything there rather than doing it “right” and encapsulating the view logic properly (just a warning for visitors who might later read this!).

        • http://www.michikono.com/ Michi Kono

          After some tinkering and getting more familiar, I think your approach makes more sense than mine when it comes to dealing with adding more event dispatchers. Ultimately, I think these solutions (mine included) should be avoided where possible. Once you start writing to global dispatchers, the allure is strong to just dump everything there rather than doing it “right” and encapsulating the view logic properly (just a warning for visitors who might later read this!).