Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use a central event bus with backbone.js?

I am currently working on my first backbone.js app. The concept of events is quite familiar to me but I am in question if I should use a central event dispatcher or not. Generally I see these two approaches:

  • Directly wire event publishers and event receivers together. (I started with this approach.)
  • Use an event bus and wire publishers and receivers to this bus.

Is it favorable to use an event bus in terms of e. g. long time maintainability of the app and traceability of events?

like image 934
lost Avatar asked Jun 01 '13 15:06

lost


1 Answers

Whether to use a central event bus or to directly wire events together should be assessed on a case by case basis. Depending on the circumstance, there are times where you will favour one over the other.

I will use a central event bus whenever I can due to the fact that the publisher and receiver are not tightly coupled. In my opinion, this makes your code more maintainable and flexible.

I find central event buses very useful when you have:

  1. A single event publishing instance and numerous event receivers.
  2. Many event publishing instances and a single event receiver

The benefits of the central event bus in the above cases becomes even more apparent when the event receiver instances or publisher instances are dynamic and thus being created and destroyed over the life of the Application. As an example, consider the following single page app:

  1. The single page app must stretch to fit the browser window width.
  2. The single page app is primarily a tabbed set of views, however the number of tabs is dynamic as they are created and destroyed by users
  3. The contents of the tabs are all different with the exception that they have a main area of content that must be stretched to fit the available width after accounting for the width of other sibling elements
  4. Resizing needs to occur at many different points due to the contents of tabs continually changing.

In the above case, regardless of the specific scenario, the central bus model works really well. A code example would be:

Application Level

var App = {

    init: function () {
        // Event to resize width of element to remaining width
        App.Events.on("fitRemainingWidth:app", this.fitRemainingWidth);
     },

    // event pub sub for the app.
    Events: _.extend({}, Backbone.Events),

    // function that expands an element, or elements to the remaining width in the window. 
    fitRemainingWidth: function(targetEls) {
        var els = $(targetEls);
        _.each(els, function (e) {
            var el = $(e);
            var cont = el.parent();
            newWidth = cont.innerWidth();
            otherElements = cont.children().not(el);
            otherElementsWidth = 0;

            otherElements.each(function () {
                otherElementsWidth += $(this).outerWidth();
            });

            el.width(newWidth - otherElementsWidth);
        });
    }
}

In your views

Whenever a something needs to be resized, you trigger the event, for example:

App.Events.trigger("fitRemainingWidth:app", this.$(".main-content"), this);

As you can see, this is really useful, because the fitRemainingWidth function can apply to anything at all and you never know which views may want to use it in the future or when it will need to be called.

So I guess I should move onto when I find not using a central event bus preferable. I am sure there are other cases, however the main one for me is when the receiver needs to be wired to a specific instance of the publisher. For example, if we continue on with the tabbed application example, lets say that the contents of each tab is a split view of a particular person's mailbox. In each split view is a list pane showing a collection of emails and a reading pane showing the content of the currently selected message in the list.

In this case we want to trigger a "selected:email" event when an email is clicked and have the appropriate reading pane bound to it so it can display the email's contents.

Say we have ten mailbox tabs open, each with their own email list pane and reading pane. That makes ten email lists and ten reading panes. If I was to use a central event bus for this scenario when I triggered a "selected:email" from one of the lists, the reading panes that are receiving these events from the event bus would need to have their own smarts to try and work out whether the selected email was something they need to display or not. It's not really desirable for each reading pane to try and work this out and there is uneccesary logic involved. Its far better for a reading pane to just receive a "selected:email" event with the email object as it's payload and just display it.

So in this scenario, its better to to make each mailbox tab view responsible for wiring the events of it's specific child view instances and their models and collections. In our example this means a specific instance of an email list view with it's collection and a specific associated instance of a reading pane view.

In summary, I am suggesting there are use cases for each and that you should try to be judicious on choosing the right approach for each circumstance that you come across. Use both and learn to see what fits in which circumstances.

Lastly, in regards to the traceability of events, you can always write a wrapper for the On and Off functions in your event bus that both calls the normal On and Off functions but also adds/removes information to a register containing which objects are bound to which events. You could use this for debugging and write to the console information about these objects so you can have a clear picture of the state of your event bus and its listeners at any point in time. I've not ever thought about doing this, but there is no reason why you couldn't ;)

Post Edit: The comment by tsiki regarding using Local Event buses is a good one. In the above example I made with many tabbed mailboxes, you could use a local event bus for each user mailbox tab view. You might want to do this if each mailbox tab view became quite complicated in its own right containing many nested child views and large amounts of events to be generated/handled.

like image 73
dcarson Avatar answered Oct 02 '22 07:10

dcarson