Joao Carlos

Proper link handling for applications using pushState

Have you ever come across some web application that uses pushState and doesn't let you use Cmd + Click to open links in new tabs? I know I have. More worrying is that sometimes these so called links are just div elements, not a elements at all. Let's fix this problem.

We can start with a very simple link:

<a href="/profile">Profile</a>

The value of the href attribute on all internal links is going to start with a slash, so we can react to those. The important thing is that we stay out of the way if any modifier key is being pressed. We can achieve our desired behavior with the following code (using jQuery):

$(document).on("click", "a[href^='/']", function(event) {
  if (!event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
    event.preventDefault();
    // Trigger the route change
  }
});

Now users can expect your application to work just like any other website. Pressing a modifier key while clicking the link will let the browser handle the link without any JavaScript interference. As an added bonus you can even deal with other things here in addition to the route change, such as notifying your web analytics service of a new page view.

At TenFarms we use Backbone.js so here is the above example with actual route changing:

$(document).on("click", "a[href^='/']", function(event) {
  if (!event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
    event.preventDefault();
    var url = $(event.currentTarget).attr("href").replace(/^\//, "");
    app.navigate(url, { trigger: true });
  }
});

Pretty straightforward stuff: we grab the URL from the a element, remove the leading slash, and call navigate on our instance of Backbone.Router (here named app). The trigger option will make it call the route function for the URL we are navigating to.

Note: We don't actually attach event handlers like presented. We have a Page view that represents the body element. All global events are defined in that view. It's probably a good idea, at least in large applications.

Update: No longer using rel="internal" on the links, which was my original suggestion. Thanks Alan Hogan for pointing out why it was a bad idea, and Ben Cherry for suggesting a better solution.

This article was originally posted on the TenFarms development blog, but is here also for reference.

This article was posted on 2012-12-12. Like it? Share it.


Joao Carlos, that's me!

Meet the author:

Hi, I'm Joao Carlos, an iOS and Ruby developer currently living with my wife in Helsinki, Finland. I can make some pretty neat apps and I write about what I learn on the way. More about me »