Tips For Using Backbone.js Routers With HTML5 PushState


IMPORTANT UPDATE!

Jeremy Ashkenas pointed out that this won’t work in Internet Explorer (or other browsers that don’t support PushState). I had tested this, but apparently I didn’t hit the Back button in IE, in my testing. The Back button, indeed, does not work in IE when setting things up this way. So, if you don’t care that IE users can’t use their Back button, this works great… otherwise… I think I need to re-work some of this and post a followup with corrections.

Update #2

To avoid further confusion, I’m striking through everything I said that is wrong, in this post. A new post will be up soon-ish, to provide some real tips.

———————–

I’ve already introduced HTML5 PushState and talked about using progressive enhancement with Backbone.js to make it all work. What I haven’t talked about, though, is how I got my Backbone router to work properly after enabling PushState. I spent some long hours fighting with my router all because of a few very small decisions I originally made in my app. Hopefully these tips will help other avoid the same mistakes I made.

</p>

Tip #1: Don’t Use Backbone.Router

You don’t need a Backbone.Router if you’re using PushState. That’s pretty much the end of it. The rest of the “tips” I was originally going to write are pretty much useless because you don’t need to use a router when you’re using HTML5’s PushState.

WHAT?! How Am I Supposed To … ?!

Here’s the secret: The Backbone.Router doesn’t give you much functionality. Most of what we use a router for is done through the Backbone.History object. Router.navigate? How about History.navigate instead? Router.navigate delegates to this anyways. Route callback methods? Yeah, those are also delegated to History. Routers give us a nice way to organize our route definitions and callbacks in a clean way – and that’s a very important role to play – but end up delegating the majority of their functionality to the History object, anyways.

So, why not use the History object directly if we don’t need routes and callback methods?

Two Strings Attached: PushState And Navigate

There are two strings attached to this. The first is that you need to use PushState and use it properly. Secondly,

</span>

</strike>

you need to stop using `router.navigate` to fire your route methods.

</p>

</p>

The PushState Requirement

Without PushState, you need a router to use hash fragments. Without a router, your hash fragments won’t be able to fire callback methods. Of course you could built your routes into the Backbone.History object directly (once again, Backbone.Router delegates to this). But it gets a little more complicated when you do this yourself. It’s easier to use a router and makes more sense to another developer looking at your code.

If you are using PushState, though, then you’ll never fire a router’s route methods. If you’re never firing a router’s route methods, what’s the point of having a router?

The Navigate Requirement

If you don’t have a router, you won’t be able to call `router.navigate(“…”, true)` with that pesky ‘true’ parameter. But, this shouldn’t be an issue, anyways, You should be building your apps in a stateful manner with state-based workflow instead of using Backbone as if it were a stateless web server. You’ll still want to call `history.navigate(“…”)` to update your browser’s URL. This is done in response to the application being put into a specific state, and not done to put the application into a specific state. Don’t pass the `true` parameter as the second argument to navigate, and you’ll be fine.

Sample Code

Here’s a standard router implementation, not using PushState, with two routes that can be fired from two separate links on the page.

When you click on a link, the route is fired and a message is printed out on the screen. Simple stuff.

Enabling PushState

Now look at the code with PushState enabled.

The only differences in this version of the code are the two links and the use of `{pushState: true}` when starting the router. The two links are standard links without hash fragments. This means that they would make a request back to the server when you click on them. If you run this code and click on a link, it makes a full request back to the server to render the page because the links are full URLs.

When the page loads from the server, you would expect the Backbone.Router to fire it’s route method, but it doesn’t. Backbone routers only fire route methods in two scenarios: 1) when a hash fragment is used as a route, and 2) when you call `navigate` with the second parameter of `true`. Since neither of these criteria are met, the router does not fire it’s route. If a router’s methods never fire, why do you have a router in your app? Delete it.

Hijacking The Links And Using History.navigate

To make use of Backbone’s capabilities with PushState turned on, and to update the URL without making a full request back to the server, we need to hijack the link clicks with a bit of JavaScript code and then use Backbone’s History object to update the URL.

The browser URL updates when you click on a link, but the page does not have to do a full refresh from the server. Of course, if you decide to hit the refresh button on your browser, you’ll hit the full URL and the server will give you the page you expect.

Conclusion: If You’re Using PushState, You Don’t Need A Router

Check out the JavaScript for my BackboneTraining.net site. There are no routers in site. There is only an instance of Backbone.History and a call being made to the history.navigate method to update the URL when a link is clicked. This works because I have PushState enabled and because I am not passing the `true` argument to the `navigate` method.

Given these two assumptions – PushState and not passing ‘true’ to ‘navigate’ – Backbone Routers become far less important to our applications. And I believe this is a good thing as it leads us away from using Backbone as if it were a stateless web app, and toward using Backbone as it truly is – a stateful application framework running on top of a stateless, asynchronous technology stack.

</span>

</strike>

A JSFiddle To Build Backbone.js Fiddles