168

I am looking into using the HTML5 History API to resolve deep linking problems with AJAX loaded content, but I am struggling to get off the ground. Does any one know of any good resources?

I want to use this as it seems a great way to allow to the possibility of those being sent the links may not have JS turned on. Many solutions fail when someone with JS sends a link to someone without.

My initial research seems to point to a History API within JS, and the pushState method.

http://html5demos.com/history

Kyle Strand
  • 14,120
  • 3
  • 59
  • 143
Mild Fuzz
  • 26,058
  • 28
  • 95
  • 142

9 Answers9

181

For a great tutorial the Mozilla Developer Network page on this functionality is all you'll need: https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history

Unfortunately, the HTML5 History API is implemented differently in all the HTML5 browsers (making it inconsistent and buggy) and has no fallback for HTML4 browsers. Fortunately, History.js provides cross-compatibility for the HTML5 browsers (ensuring all the HTML5 browsers work as expected) and optionally provides a hash-fallback for HTML4 browsers (including maintained support for data, titles, pushState and replaceState functionality).

You can read more about History.js here: https://github.com/browserstate/history.js

For an article about Hashbangs VS Hashes VS HTML5 History API, see here: https://github.com/browserstate/history.js/wiki/Intelligent-State-Handling

thaJeztah
  • 23,621
  • 8
  • 58
  • 85
balupton
  • 42,415
  • 27
  • 116
  • 167
33

I benefited a lot from 'Dive into HTML 5'. The explanation and demo are easier and to the point. History chapter - http://diveintohtml5.info/history.html and history demo - http://diveintohtml5.info/examples/history/fer.html

Kiran Aghor
  • 457
  • 6
  • 8
29

Keep in mind while using HTML5 pushstate if a user copies or bookmarks a deep link and visits it again, then that will be a direct server hit which will 404 so you need to be ready for it and even a pushstate js library won't help you. The easiest solution is to add a rewrite rule to your Nginx or Apache server like so:

Apache (in your vhost if you're using one):

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.html [L]
 </IfModule>

Nginx

rewrite ^(.+)$ /index.html last;
Mauvis Ledford
  • 35,240
  • 13
  • 77
  • 84
  • THAT is the real answer. This is nowhere on the Balupton's History.js wiki/tutorials etc. In fact, History.js don't use hash so you need to use a .htaccess redirection ! – adriendenat Jul 06 '12 at 10:03
  • 13
    Ideally your server/app should respond to the route appropriately without the need for this rewrite. – sholsinger Jul 16 '12 at 23:46
  • Agreed, except in the case of many modern JavaScript frameworks like Backbone.js, Spine, Ember, etc. These are all essentially "one page" JavaScript applications. One could work out a solution of serving the write backend template for SEO, ect, but in the meantime this would be necessary. – Mauvis Ledford Jul 17 '12 at 05:27
  • *right. I write about this in more detail here: http://readystate4.com/2012/05/17/nginx-and-apache-rewrite-to-support-html5-pushstate/ – Mauvis Ledford Oct 08 '12 at 21:53
6

The HTML5 history spec is quirky.

history.pushState() doesn't dispatch a popstate event or load a new page by itself. It was only meant to push state into history. This is an "undo" feature for single page applications. You have to manually dispatch a popstate event or use history.go() to navigate to the new state. The idea is that a router can listen to popstate events and do the navigation for you.

Some things to note:

  • history.pushState() and history.replaceState() don't dispatch popstate events.
  • history.back(), history.forward(), and the browser's back and forward buttons do dispatch popstate events.
  • history.go() and history.go(0) do a full page reload and don't dispatch popstate events.
  • history.go(-1) (back 1 page) and history.go(1) (forward 1 page) do dispatch popstate events.

You can use the history API like this to push a new state AND dispatch a popstate event.

history.pushState({message:'New State!'}, 'New Title', '/link'); window.dispatchEvent(new PopStateEvent('popstate', { bubbles: false, cancelable: false, state: history.state }));

Then listen for popstate events with a router.

Erik Ringsmuth
  • 1,083
  • 9
  • 8
  • 1
    Is it just me, or does `new PopStateEvent(...)` seem to not work in IE11? Is there a workaround that anyone knows of? – David Alan Hjelle Jul 15 '15 at 12:53
  • Looks like IE 11 needs something like: `var pop_state_event = document.createEvent('Event'); pop_state_event.initEvent('popstate', true, true); window.dispatchEvent(pop_state_event);` – David Alan Hjelle Jul 15 '15 at 15:09
4

You could try Davis.js, it gives you routing in your JavaScript using pushState when available and without JavaScript it allows your server side code to handle the requests.

Oliver Nightingale
  • 2,218
  • 1
  • 16
  • 23
4

Here is a great screen-cast on the topic by Ryan Bates of railscasts. At the end he simply disables the ajax functionality if the history.pushState method is not available:

http://railscasts.com/episodes/246-ajax-history-state

deb
  • 11,136
  • 19
  • 62
  • 83
3

I've written a very simple router abstraction on top of History.js, called StateRouter.js. It's in very early stages of development, but I am using it as the routing solution in a single-page application I'm writing. Like you, I found History.js very hard to grasp, especially as I'm quite new to JavaScript, until I understood that you really need (or should have) a routing abstraction on top of it, as it solves a low-level problem.

This simple example code should demonstrate how it's used:

var router = new staterouter.Router();
// Configure routes
router
  .route('/', getHome)
  .route('/persons', getPersons)
  .route('/persons/:id', getPerson);
// Perform routing of the current state
router.perform();

Here's a little fiddle I've concocted in order to demonstrate its usage.

aknuds1
  • 57,609
  • 57
  • 177
  • 299
2

You may want to take a look at this jQuery plugin. They have lots of examples on their site. http://www.asual.com/jquery/address/

Nathan Totten
  • 8,828
  • 1
  • 33
  • 41
  • Again, this solution seems to fail when JS is off. I think history API has the power to work in conjunction with modrewrite so that links are always processed in the first instance by the server, without needing redirection from the JS layer. – Mild Fuzz Oct 25 '10 at 15:17
  • You are on the right track with the modrewrite. The solution of managing the history API and handling when the user doesnt have JS are really two separate things. If you dont have JS, you must handle the user with standard hrefs and server responses. The history API could be build as sort of a "nice to have" if the user's browser supports it. – Nathan Totten Oct 25 '10 at 23:04
  • Both the Express and State samples that ship with jQuery Address 1.3 work fairly well when JavaScript is disabled. The second one uses PHP with mod_rewrite. – Rostislav Oct 26 '10 at 07:56
  • Exactly my tact. I intend to build the site without JS, add AJAX elements and then use history to rewrite the URL, preserving deep linking. Theoretically, it should be a better method than any I have seen as the site will not depend on AJAX, at any stage – Mild Fuzz Oct 26 '10 at 09:43
  • 1
    -1 as jQuery Address isn't a direct port of the HTML5 State API - does not support data or titles, and replaceState. – balupton Jan 30 '11 at 13:41
1

if jQuery is available, you could use jQuery BBQ

sprugman
  • 17,781
  • 31
  • 105
  • 160