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"));
@jrgns
Copy link

jrgns commented Feb 8, 2012

How can this be implemented in the boilerplate? I've tried a couple of things, but my sub routes aren't being triggered.

@corpix
Copy link

corpix commented Feb 12, 2012

@jrgns, +1

@jrgns
Copy link

jrgns commented Feb 13, 2012

I've added the following at the bottom of my modules to get it to work:

new ModuleName.Router();

@tbranyen
Copy link
Author

I'd have to see the code you tried @jrgns. You should be able to just drop this into the boilerplate to get it working.

@jrgns
Copy link

jrgns commented Feb 13, 2012

@tbranyen I've sent a pull request.

@nagyv
Copy link

nagyv commented Feb 15, 2012

I've tried to fix prefix handling and improved the router's navigate method. See my fork. (I would be happy to have it "merged" into this gist if it can be done.)

@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