Question

I use the window.onhashchange function to execute code when the User changes the hash of the page:

window.onhashchange = function() { /* do something */ };

In some functions I also set the hash via JavaScript:

window.location.hash = "#abc";

I want to prevent the onhashchange event from firing when I set the hash via JavaScript.

What I have tried so far:

var currently_setting_hash = false;

window.onhashchange = function() {
  if (currently_setting_hash)
    return;
 //...
}

currently_setting_hash = true;
window.location.hash = "#abc";
currently_setting_hash = false;

That didn't work because the event is fired with a delay, so the code will first set the hash, then set currently_setting_hash to false and then execute the onhashchange event.

Any ideas how this could be accomplished? Or maybe is there a way to detect if the hash was set by the user or via JavaScript?

Was it helpful?

Solution

You could reset the variable from the event handler itself:

var currently_setting_hash = false;

$(window).on("hashchange", function() {
    if (currently_setting_hash) {
        currently_setting_hash = false;
        return;
    }

    currently_setting_hash = false;
    //...
});

currently_setting_hash = true;
window.location.hash = "#abc";

OTHER TIPS

Since the event is delayed, there is the possibility of events occurring in a different order than you expect (e.g. imagine the user changing the URL by other means either immediately before or after your code does). It is important to make sure that you do not become inconsistent by assuming the event is yours. Therefore I have a suggestion (based on your code and Adam Bubela's):

var expectedHash;

window.onhashchange = function () {
    if (window.location.hash === expectedHash) {
        return;
    }
    expectedHash = window.location.hash;
    // ... do actual reaction to change here ...
}

function changeHash(hash) {
    hash = canonicalizeHashValue(hash);
    expectedHash = hash;
    window.location.hash = hash;
}

// Helper - return the version of the URL that the browser is expected to
// so that the equality test is accurate.
function canonicalizeHashValue(value) {
    // Borrowing an A element's ability to resolve/parse URLs.
    var tmp = document.createElement('a');
    tmp.href = "";
    tmp.hash = value;
    return tmp.hash;
}

This code will suppress the change handler only if the change is to the value you are expecting. (The assignment inside of onhashchange makes sure that the handler also runs if the hash temporarily goes to another value, which I assume is more correct than the alternative.)

The third helper function canonicalizeHashValue is needed only for precision in case you are specifying a non-canonical value, e.g. changeHash('foo') instead of changeHash('#foo').

If you want to use just plain Java Script:

    var currently_setting_hash = false;

    window.onhashchange = function() {
        if (currently_setting_hash){
            currently_setting_hash = false;
            return;
        //...
        }
        alert('outside the script');
    }
    function changeHash(hash){
        currently_setting_hash = true;
        window.location.hash = hash;
    }

Well, since the event is delayed, if you change hash (for any reason) more than once, then more events will fire in a row. In such case you should increment an integer every time hash is changed.

var setting_hash = 0;

window.onhashchange = function() {
    if (setting_hash){
        setting_hash--;
        return;
    }
    //code here
}
function changeHash(hash) {//hash without '#'
    if (hash!=window.location.hash.substr(1)) {
      setting_hash++;
    }
    window.location.hash = hash;
}

Now You could use the History.replaceState() method. It replaces the hash without triggering the "onhashchange".

window.onhashchange = function() { /* do something */ };

// ... 

history.replaceState(undefined,undefined,'#1234');  // This will replace the hash without triggering 'onhashchage'.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top