backbone.js attaching multiple Routers to the same route

September 16, 2011 3 Comments by Richard

Recently I had need to attach multiple backbone.js Router objects to the same route; the purpose was to create a page which could have multiple sections that were unaware of each other, since the path gave them all the information they needed. This allowed a much looser form of coupling and gave us a bit more of a MVP feel rather than a traditional MVC.

However, after a lot of discussion we ended up scrapping this plan for a slightly different one.  I had already written a patch to make backbone.js routers support multiple route handlers, though, and I didn’t think it made sense to just throw it out, so I’ll provide it here.  Keep in mind that this may not work with future versions of backbone.js!

So, without further ado, here it is:

define(["require", "./underscore", "./backbone"], function(require, _, Backbone) {
    // Backbone.js doesn't quite do what we want it to, but it almost does;
    // we can modify it so that it does do what we want pretty easily, thanks
    // to the eminent hackability of javascript.
    //
    // We may need to update this when backbone.js updates! :-/

    var patched_methods = {
        // Save and rename the old route function, modified to work with the new
        override_route : function(route, callback) {
            this.handlers.unshift({route : route, callback : [callback]});
        },

        // Add a route to be tested when the fragment changes.
        // RB 9/15/2011: We don't want routes added later to
        //               override previous routes; we want
        //               routes added later to get called as well.
        //
        //               With this change, callback becomes an array
        //               of callbacks instead of just one.
        route : function(route, callback) {
            var appended = _.any(this.handlers, function(handler) {
                if (handler.route.toString() == route.toString()) {
                    handler.callback.unshift(callback);
                    return true;
                }
            });
            if (!appended) {
                this.handlers.unshift({route : route, callback : [callback]});
            }
        },

        // Add a feature to remove a route, since we no longer override them
        unroute : function(route, callback) {
            var found_and_removed = _.any(this.handlers, function(handler) {
                if (handler.route == route) {
                    var idx = handler.callback.indexOf(callback);
                    if (idx > -1) {
                        handler.callback.splice(idx, 1);
                        return true;
                    } else {
                        return false;
                    }
                }
            });
        },

        // Attempt to load the current URL fragment. If a route succeeds with a
        // match, returns `true`. If no defined routes matches the fragment,
        // returns `false`.
        // RB 9/15/2011: overriding this function
        loadUrl : function(fragmentOverride) {
            var fragment = this.fragment = this.getFragment(fragmentOverride);
            var matched = _.any(this.handlers, function(handler) {
                if (handler.route.test(fragment)) {
                    _.each(handler.callback, function(callback) {
                        callback(fragment);
                    });
                    return true;
                }
            });
            return matched;
        }
    };

    _.extend(Backbone.History.prototype, patched_methods);
    return Backbone;
});