18

I'm using the jQuery BBQ plug-in to track the users progress through the page. However, I only want to create 1 additional entry in the user's history, not one for every hash change.

I've tried the jQuery.bbq.pushState and merge_mode methods, without success: New history entries are still added:

jQuery.bbq.pushState({ sort: encodeURIComponent(sort) });

I have also tried location.replace(), but that doesn't work for Safari 5.1.2.

location.replace('#' + encodeURIComponent(sort))

What's the cross-browser solution to modify the hash, without adding too much entries to the history?

I. J. Kennedy
  • 24,725
  • 16
  • 62
  • 87
Hoppe
  • 6,508
  • 17
  • 60
  • 114
  • 1
    Have you already used `replaceState` instead of `pushState`? – Rob W Feb 10 '12 at 21:51
  • I don't see any such method in the library http://benalman.com/code/projects/jquery-bbq/docs/files/jquery-ba-bbq-js.html @Rob W – Hoppe Feb 10 '12 at 22:11
  • 1
    I actually meant the [`history.replaceState`](https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_replaceState().C2.A0method) method. *See also [History.js](https://github.com/balupton/history.js/tree/master/scripts/bundled/html5)*. – Rob W Feb 10 '12 at 22:18
  • history.replaceState doesn't work in < IE 9, so I'll check out history.js next @RobW – Hoppe Feb 14 '12 at 16:23
  • @RobW, it seems like HTML4 browsers don't allow this functionality to work **and only create 1 entry in the history**. Thank you for your help though! – Hoppe Feb 14 '12 at 17:00

2 Answers2

39

First, I show the definition of function replaceHash, which accepts only one argument: The new location hash. A detailed explanation of the logic can be found at the bottom of the answer.

Code:

// Should be executed BEFORE any hash change has occurred.
(function(namespace) { // Closure to protect local variable "var hash"
    if ('replaceState' in history) { // Yay, supported!
        namespace.replaceHash = function(newhash) {
            if ((''+newhash).charAt(0) !== '#') newhash = '#' + newhash;
            history.replaceState('', '', newhash);
        }
    } else {
        var hash = location.hash;
        namespace.replaceHash = function(newhash) {
            if (location.hash !== hash) history.back();
            location.hash = newhash;
        };
    }
})(window);
// This function can be namespaced. In this example, we define it on window:
window.replaceHash('Newhashvariable');

Function logic

  • When history.replaceState is supported, the function will always replace the current hash, without any side effects.
  • Otherwise, a reference (hash) to the very first location.hash property is created, and the following function is defined:

    1. If location.hash != hash, then we know for sure that the history's state is at least past the first page view. We can safely go back in the history, without unloading the page. history.back(); // Go back in the history.
    2. Then, set the location.hash property. If we went back in the history in the previous step, the history entry is overwritten.


    The fallback (last) method may not always replace the history:
    When location.hash == hash, either of the following is true:

    1. The hash hasn't changed yet, so it makes no sense to go back to the previous page.
    2. It's possible that the user navigated backwards, to the original page's state. If we use history.back();, the page might be unloaded, which is not desirable.


    So, to be safe, we never unload the page when the hash is equal to the saved original hash.

    Note: It is important to run this code before a hash change. When the hash has already been changed, the script is not reliable any more. The user could have navigated to the very first hash state, which is not equal to the saved hash. Consequently, history.back() unloads the page.

Rob W
  • 341,306
  • 83
  • 791
  • 678
23

You could use the replace-method from window.location, without the second newSubStr-argument. This works in all known browsers, even oldIE:

function replaceHash(hash) {
  return window.location.replace(
    '#' + hash.replace(/^#/, '')
  );
}

Note, that if there's a valid element (as per spec) in the document, the page will jump/scroll to it.

yckart
  • 32,460
  • 9
  • 122
  • 129