I've been using the git repository backboneboilerplate as a really good starting point and have been adding/modifying it as needed. (There will be a whole other post on this)
One of the first thing I thought about was how the hell am I going to convince my boss to ever accept that we could possibly rewrite our app to be a single page JS application.
I knew I had to make the transition easy, and the first thing I needed to do was make the Backbone routing behave similarly to URL routing in a lot of the web frameworks.
By default, Backbone routes are case sensitive. So if you set your router to match "search",
www.foo.com/#/search will be matched but www.foo.com/#/Search will not. This will result in nothing being called. It would be extremely tedious to manually set routes for all the possible letter casings in all of your paths (Search,sEarch,seArch, etc). Doing a little research I stumbled upon a stack overflow post on how to do this. Worked like a charm. Bacically it comes down to extending Router and overriding the _routeToRegExp function.
_routeToRegExp : function(route) {
route = route.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&")
.replace(/:\w+/g, "([^\/]*)")
.replace(/\*\w+/g, "(.*?)");
return new RegExp('^' + route + '$', 'i'); // Just add the 'i'
}
The second incompatibility with Backbone routing is that the route "www.foo.com/#/search" and "www.foo.com/#/search/" are not the same.
The behavior in many web servers is that either one redirects to the other or they both just pull up some default file (index.html,index.php, etc). Just like in the case with case sensitivity, it would be a pain for you to have to manually add another route in the format of currentRoute + "/" to all your routes.
Seems like other people have been having this problem and submitted an issue on github. However it was closed with no action taken.
Tinkering around, I decided the best way to go about it was to extend backbone router and override the initialize method.
initialize: function(){
for(var routeName in this.routes){
if(routeName[routeName.length - 1] !== '/'){
this.route(routeName + '/', this.routes[routeName], this[this.routes[routeName]]);
}
}
}
There are a lot of purist out there that argue these routes shouldn't be same, but I'm doing this to try to make it convenient for users who may or may not put a / at the end of a path or capitalize a randomly character.
Putting it all together here's what it looks like
var AppRouter = Backbone.Router.extend({
//if you're overriding this make sure to
//call YourRouter__super__.initialize.call(this) if you want to keep functionality
initialize: function() {
for (var routeName in this.routes) {
if (routeName[routeName.length - 1] !== '/') {
this.route(routeName + '/', this.routes[routeName], this[this.routes[routeName]]);
}
}
},
_routeToRegExp: function(route) {
route = route.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&")
.replace(/:\w+/g, "([^\/]*)")
.replace(/\*\w+/g, "(.*?)");
return new RegExp('^' + route + '$', 'i'); // Just add the 'i'
}
});
var SearchRouter = AppRouter.extend({
routes: {
"list": "list",
"search/:id": "search"
},
list: function() {
console.log("list called!!");
},
search: function(id) {
console.log("search called with: " + id);
}
});
new SearchRouter();
Backbone.history.start();
Try it out http://jsfiddle.net/bWAXn/. Tell me what you think!