31

I want to implement a navigation history using jQuery and AJAX in a cross-browser manner. My approach is to use window.history.pushState and fall back to a hash url /#!/url in browsers that do not support window.history.pushState.

For example:

<a href="/home">home</a>
<a href="/about">about</a>
<a href="/contact">contact</a>

On browsers that support window.history.pushState, clicking on one of these links should change address without page refresh to http://domain.com/home, http://domain.com/about etc. When the browser does not support window.history.pushState, it should use a fragment identifier, i.e: http://domain.com/#!/home, http://domain.com/#!/about.


Update: Based on the feedback here I have implemented Ajax SEO (git) that uses jQuery Address for HTML5 History API with old browser fallback to /#!/url.

Ben Lee
  • 50,019
  • 12
  • 118
  • 142
Binyamin
  • 6,158
  • 10
  • 58
  • 78
  • I am wondering, if you need to support browsers that don't support pushState, why not just use the hash url method for all browsers? Other than for "correctness" what advantages are there to pushState that compensate for the complication of supporting multiple methods? – David Hammond Jan 08 '14 at 15:37
  • @DavidHammond, it is what directly https://github.com/laukstein/ajax-seo supports. – Binyamin Jan 08 '14 at 19:07

3 Answers3

22
// Assuming the path is retreived and stored in a variable 'path'

if (typeof(window.history.pushState) == 'function') {
    window.history.pushState(null, path, path);
} else {
    window.location.hash = '#!' + path;
}
Ben Lee
  • 50,019
  • 12
  • 118
  • 142
6

Something i've been using with fallback hash URL's:

History = History || {};
History.pathname = null;
History.previousHash = null;
History.hashCheckInterval = -1;
History.stack = [];
History.initialize = function () {
    if (History.supportsHistoryPushState()) {
        History.pathname = document.location.pathname;
        $(window).bind("popstate", History.onHistoryChanged);
    } else {
        History.hashCheckInterval = setInterval(History.onCheckHash, 200);
    }
};
History.supportsHistoryPushState = function () {
    return ("pushState" in window.history) && window.history.pushState !== null;
};
History.onCheckHash = function () {
    if (document.location.hash !== History.previousHash) {
        History.navigateToPath(document.location.hash.slice(1));
        History.previousHash = document.location.hash;
    }
};
History.pushState = function (url) {
    if (History.supportsHistoryPushState()) {
        window.history.pushState("", "", url);
    } else {
        History.previousHash = url;
        document.location.hash = url;
    }
    History.stack.push(url);
};
History.onHistoryChanged = function (event) {
    if (History.supportsHistoryPushState()) {
        if(History.pathname != document.location.pathname){
            History.pathname = null;
            History.navigateToPath(document.location.pathname);
        }
    }
};
History.navigateToPath = function(pathname) {
    History.pushState(pathname);

    // DO SOME HANDLING OF YOUR PATH HERE

};

You could bind your click events to this with:

$(function(){
    $("a").click(function(){
        var href = $(this).attr('href');
        History.navigateToPath( href )
        return false;
    });
});

If you need some more explanation on this example i'll be glad to hear it.


EDIT

Please see my other answer.

Koen.
  • 21,087
  • 6
  • 75
  • 76
4

It has been a while since my original answer and I would now suggest using Backbone.

An implementation could be:

// First setup a router which will be the responder for URL changes:
var AppRouter = Backbone.Router.extend({

  routes: {
    "*path": "load_content"
  },

  load_content: function(path){
    $('#content').load('/' + path);
  }

});
var appRouter = new AppRouter;

// Then initialize Backbone's history
Backbone.history.start({pushState: true});

Excerpt from the documentation:

To indicate that you'd like to use HTML5 pushState support in your application, use Backbone.history.start({pushState: true}). If you'd like to use pushState, but have browsers that don't support it natively use full page refreshes instead, you can add {hashChange: false} to the options.

And now every time Backbone.history.navigate is called, the AppRouter will perform an AJAX load of the path into the #content div.

To handle all links with AJAX you could use the following:

$("a").on('click', function(event){
    event.preventDefault();
    Backbone.history.navigate( event.currentTarget.pathname, {trigger: true} )
});

Take note of the {trigger: true} which causes the handler in the router to be called (otherwise only from url changes).

Fiddle with example code: http://jsfiddle.net/koenpunt/pkAz7/1/

Community
  • 1
  • 1
Koen.
  • 21,087
  • 6
  • 75
  • 76