Skip to content

Instantly share code, notes, and snippets.

@tbranyen
Created September 22, 2011 16:51
Show Gist options
  • Save tbranyen/1235317 to your computer and use it in GitHub Desktop.
Save tbranyen/1235317 to your computer and use it in GitHub Desktop.
backbone.js sub routing
/* Pretend app setup stuff is here */
/* Kick off app */
jQuery(function($) {
var Gallery = app.module("gallery");
app.Router = Backbone.Router.extend({
initialize: function() {
this.gallery = new Gallery.Router("gallery/");
}
});
// Actually initialize
new app.Router();
});
(function(Helper) {
Helper.SubRoute = Backbone.Router.extend({
constructor: function(prefix) {
var routes = {};
// Prefix is optional, set to empty string if not passed
prefix = prefix || "";
// Allow for optionally omitting trailing /. Since base routes do not
// trigger with a trailing / this is actually kind of important =)
if (prefix[prefix.length-1] == "/") {
prefix = prefix.slice(0, prefix.length-1);
// If a prefix exists, add a trailing /
} else if (prefix) {
prefix += "/";
}
// Every route needs to be prefixed
_.each(this.routes, function(callback, path) {
if (path) {
return routes[prefix + path] = callback;
}
// If the path is "" just set to prefix, this is to comply
// with how Backbone expects base paths to look gallery vs gallery/
routes[prefix] = callback;
});
// Must override with prefixed routes
this.routes = routes;
// Required to have Backbone set up routes
Backbone.Router.prototype.constructor.call(this);
}
});
})(app.module("helper"));
@geekdave
Copy link

geekdave commented Apr 6, 2012

Thanks for this code! For me, subroutes were not firing on the initial load of a complex URL. Subroutes were only working for changes to the URL after the subroutes were loaded. To fix it, I added this code to the end of the constructor() function, right after the call to Router.prototype.

// grab the full URL
var hash = Backbone.history.getHash();

// check if there is already a part of the URL that this subview cares about...
var hashPart = hash.substr(prefix.length, hash.length);

// ...if so, trigger the subroute immediately.  this supports the case where 
// a user directly navigates to a URL with a subroute on the first page load.
if (hashPart && hashPart != "") {
    Backbone.history.loadUrl(prefix + hashPart);
}

With this code, I am able to navigate to http://example.org/#a/b/c, and have the "b" subroute trigger correctly. Please let me know if this sounds like a good approach.

@tbranyen, I would love to use this as a basis for my first public Github project. Would you be willing to include an MIT license at the beginning of your helper.js file so that I can use it?

@tbranyen
Copy link
Author

tbranyen commented Apr 6, 2012

@geekdave its public domain, just credit me. i'd rather not license it

@jessebeach
Copy link

I got the subrouter code working as a module (loading with require), and I wired it into Tim Branyen's Boilerplate. Examples below. It's very nifty.

https://github.com/jessebeach/boxes
https://github.com/jessebeach/boxes/blob/master/app/main.js
https://github.com/jessebeach/boxes/blob/master/app/modules/example/example.js

If you got to http://domain.com/example, the index route will apply and you'll get the Example module's index function to render the page.

http://domain.com/example/ (with a trailing slash) doesn't work yet. Still looking into that.

@geekdave
Copy link

geekdave commented May 9, 2012

@jessebeach : With tbranyen's permission (above) I've created a repo to maintain this subrouter code. Check it out here: https://github.com/ModelN/backbone.subroute . I've already fixed an issue where the subroute was not firing the first time, if you navigate directly to a sub-route URL (for instance, when opening a bookmark without the app already being loaded). I'd welcome any other fixes in this repo, so that we can all share.

I've also written a blog entry about this with some examples and use-cases. http://www.geekdave.com/?p=13 Feedback is welcome.

Eventually I'd like to create more of a robust project page for this repo, with some API documentation and example apps. If you'd like, I could link your "boxes" example.

@tbranyen
Copy link
Author

tbranyen commented May 9, 2012

@geekdave nice, mind updating my last name in your readme tho, it's Branyen not Branyan =)

@jessebeach
Copy link

@geekdave, I referred to your repo as I was creating the 'boxes' repo. It was super helpful, but it doesn't describe how your module plugs into backbone with an example. That's why I made the boxes repo, to demonstrate how your code wires into a live example.

Your blog post is really great for illustrating how a sub-module can be extended. I don't see how it hooks into the main.js file. Am I just missing something?

@geekdave
Copy link

geekdave commented May 9, 2012

@tbranyen : D'oh! Just fixed it. I'm normally more sensitive to name misspellings with an especially easy-to-misspell last name, myself. :)

@jessebeach : Yes, your live example is just what's missing from my repo. If you'd rather keep is separate, that's totally fine. But I'd welcome a pull request into an "examples" subdirectory of my repo, if you'd be interested in combining efforts. As for integration with RequireJS, in my local copy, I just wrapped the entire contents of backbone-subroute.js with: require(['backbone'], function(Backbone) { ... }) Then you can just reference backbone-subroute in your main.js file's list of dependencies. Not everyone has drunk the RequireJS Kool-Aid yet, so I didn't want to make a hard dependency on it. I have seen some creative ways to conditionally check for AMD support inside a library, so perhaps that would be the best way to go.

@jessebeach
Copy link

@geekdave, I can definitely send a pull request; happy to combine efforts.

@carpeliam
Copy link

Have you guys tried this out in IE8/9 yet, by any chance? I'm getting a stack overflow, but only IE... trying to track down the recursion now.

@jessebeach
Copy link

WAH-WAH. Let us know what you find.

@carpeliam
Copy link

I ended up logging an issue against @geekdave's repo at BackboneSubroute/backbone.subroute#4 - I'm not 100% sure everything is correct (it's a little handwavy, I'm under pressure to get something out the door blahblahblah so I had to move on), but I think that's the gist of things. Hopefully I'll have time to come back to it, if @geekdave isn't able to get to the bottom of it himself.

@SamDecrock
Copy link

And how to dynamically remove the subroute?

@Daveawb
Copy link

Daveawb commented Jun 24, 2014

@SamDecrock you can't without manipulating Backbone.history and allowing your application to have knowledge of all the paths that were added. This can be quite a complex procedure if you haven't built your application with an ability to get paths from individual subroutes. I tend to create wrapping objects that have an activate and deactivate method that are managed by a facade to a Backbone router.

considering that you have a list of paths (allRoutes in this example).

for (var path in allRoutes) {
    var combinedPath = routerPrefix + '/' + path;

    // This removes all trace of the routes from the history
    Backbone.history.handlers = _.reject(
        Backbone.history.handlers, function(value) {
            return String.prototype.search.call(
                value.route, 
                combinedPath
            ) !== -1;
        }
    );
}

// Unfortunately, we still have a route in the history for the routerPrefix 
// (in fact we should have two, the one used by the primary router and
// one created by the sub router (the latter takes precedence as it has 
// been unshifted onto the handlers array. The next step therefore is to 
// get all the primary routes registered in the history.
var primaryHandlers = _.filter(Backbone.history.handlers, function(value) {
    return String.prototype.search.call(value.route, routerPrefix) !== -1;
});

// If the result has a length greater than 1 (this will be the case most of
// the time) then we need to remove the first one that was registered by 
// the subrouter, this leaves the primary route still available to start the 
// sub router again when it is needed. Else we want to keep the route as
// it was set by the primary router.
if (primaryHandlers.length > 1) {
    Backbone.history.handlers.splice(
        Backbone.history.handlers.indexOf(primaryHandlers[0])
    , 1);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment