1

I have looked at subrouting ideas like those in BackboneMVC and Backbone Marionette. I think I'm looking for something a little different. Has anyone come up with good patterns for, not subroutes, but additive routes?

Say, for example, you have a large profile lightbox that could display on any screen. You'd like to add it to browser history and have the url be able to recreate it. So you might have these urls:

'dashboard/lightbox/profile'
'game/3/lightbox/profile'

In the first route, it should do all of the behaviors for the dashboard route, then apply behaviors for an additive lightbox/profile route. This way the lighbox opens and the dashboard is in the background. In the second case, it should similarly do all the behaviors for the game/3 route, then open the lightbox on top of that.

Is this a pattern anyone has heard of or implemented? I can't think of a way to do this without using splats, like this:

routes: {
  'dashboard/*path': 'showDashboard',
  'game/:id/*path': 'showGame'
},
showDashboard: function(path) {
  //Show the dash
  this._checkIfLightboxShouldOpen(path)
},
showGame: function(id, path) {
  //Show the correct game based on id
  this._checkIfLightboxShouldOpen(path)
},
_checkIfLightboxShouldOpen(path) {
  // Parse the path string for lightbox behaviors
}

Is there a better way?

SimplGy
  • 20,079
  • 15
  • 107
  • 144

1 Answers1

1

I needed this for a recent project, I plan to release this code as open source at some point, but you can do something like this:

Create a global router to handle all routing:

App.GlobalRouter = Backbone.Router.extend({
    initialize: function(){
        this._routes = {};
    },

    registerRoute: function(route, rootRoute){
    var rootName;

    if(rootRoute) {
        route = rootRoute + '/' + route;
        rootName = this.registerRoute(rootRoute);
    }

    if(!_.isRegExp(route))
        route = this._routeToRegExp(route);

        var name = this._routes[route] ? this._routes[route] : _.uniqueId('r');
        this._routes[route] = name;

        this.route(route, name, function(){});

        if(rootName) {
            this.on('route:'+name, function(){
                var args = slice(arguments);
                this.trigger.apply(this, ['route:' + rootName].concat(args));
            }.bind(this));
        }

        return name;
    }
});

Then create a single one:

App.globalRouter = new App.GlobalRouter();

Then create a modified router to extend all your routers from:

App.Router = Backbone.Router.extend({
    constructor: function (options){
        options = options || {};
        if(options.root) this.root = options.root;
        this.globalRouter = App.globalRouter;

        Backbone.Router.apply(this, [options]);
    },

    route: function(route, name, callback, root){
        if(!App.globalRouter) return false;

        // If callback is root param
        if(callback && !_.isFunction(callback)) {
            root = callback;
            callback = null;
        }

        // If no name is callback param.
        if(_.isFunction(name)) {
            callback = name;
            name = '';
        }

        if(!callback)
            callback = this[name];

        var router = this;
        var roots = root || this.root;
        if(roots && !_.isArray(roots)) roots = [roots];

        if(roots) {
            _.each(roots, function(root){
                var globalName = App.globalRouter.registerRoute(route, root);
                router.listenTo(App.globalRouter, 'route:'+globalName, function(){
                    var args = slice(arguments);
                    var callbackArgs = args.slice(callback && -callback.length || 0);
                    callback && callback.apply(router, callbackArgs);
                    router.trigger.apply(router, ['route:' + name].concat(callbackArgs));
                    router.trigger('route', name, callbackArgs);
                });
            });
        } else {
            var globalName = App.globalRouter.registerRoute(route);
            router.listenTo(App.globalRouter, 'route:'+globalName, function(){
                var args = slice(arguments);
                var callbackArgs = args.slice(callback && -callback.length || 0);
                callback && callback.apply(router, callbackArgs);
                router.trigger.apply(router, ['route:'+name].concat(callbackArgs));
                router.trigger('route', name, callbackArgs);
            });
        }

        return this;
    }
});

From here you can create as many routers that are required and register them on the same route, also you can create a router that has route routes to listen on, so in your case you would have probably 2 or 3 routers, here is an example of what you could do:

var defaultRouter = App.Router.extend({
   routes: {
       'dashboard': 'showDashboard',
       'game/:id': 'showGame'
   },
   showDashboard: function() {},
   showGame: function(id) {},
});

var profilerRouter = App.Router.extend({
   root: [
       'dashboard',
       'game/:id'
   ],
   routes: {'profile', 'showProfile'},
   showProfile: function(){//Show lightbox}
});

This will listen for /dashboard or /game/:id and call that funciton on defaultRouter that is listening. Then if the /profile is on the end of the url for either of the routes is will catch that and run the showProfile function on the profileRouter.

NOTE: I've quickly modified the code take from my project to change some of the name/namespace issues, so you might need to check that I haven't missed anything, but the code should be right otherwise

Updated Example:

  • If the user navigates to /game/:id it will call the defaultRouter > showGame with param :id.
  • If the user navigates to /game/:id/profile it will call the defaultRouter > showGame with param :id. It will also call profileRouter > showProfile but with no params (ie. it doesn't send the :id from the /game/:id root).
Cubed Eye
  • 5,581
  • 4
  • 48
  • 64
  • I think I'm missing the key insight here--Is this grabbing the first thing before the `/` and any params using that regexp stuff, then passing on to leaf routes after that? – SimplGy May 22 '13 at 05:57
  • Yeah, totally! And it answers the larger question, which is whether this exists already. I'm surprised it doesn't, I thought this would be a common need. Seems like a great candidate for a library. Writing tests around this behavior is stretching my mind though. – SimplGy May 22 '13 at 16:50
  • @SimpleAsCouldBe if you email me (email is in my profile), I can send you the test suite for now, I have plans to release this as a library in the next couple of weeks, once I have sometime off work... – Cubed Eye May 22 '13 at 21:48