stop firing popstate on hashchange
Asked Answered
D

5

25

I am working with the History API and using push and pop state. I want to stop the popstate event from firing in some scenario where I am only appending the hash to URL. for example in some cases on click of anchor it appends # to the URL and popstate is immediately fired) I want to avoid all the scenarios where # or #somehasvalue is appended to the URL and stop popstate from firing. I am mainitaing the URL's with query parameters and I do not have any scenario where I need the popstate event to fire with # in the URL.

Here is my code.

if (supportsHistoryApi()) {

    window.onpopstate = function (event) {
    var d = event.state || {state_param1: param1, state_param2: param2};                       
    var pathName = window.location.pathname,
        params   = window.location.search;

    loadData(event, pathName + params, d.state_param1, d.state_param2);

}
Dray answered 3/9, 2014 at 0:30 Comment(0)
S
18

As far as I found you cannot stop the popstate from firing unfortunately.

What you can do is check for the event.state object. That will be null on a hash change. So I'd suggest adding a

if(event.state === null) {
   event.preventDefault();
   return false;
}

To your popstate event handler at the very beginning. I think that's the best way to prevent firing of the popstate handling code on your side when the hash changes, but i'd be interested to know if there are other solutions.

Seclude answered 16/10, 2015 at 11:15 Comment(4)
This doesn't work when you return to the first page you opened. Because then the state is also null, but still no hashchangeCoppage
@HugoDelsing, Maybe always replaceState at page load?Ickes
btw, popState event is not cancelable, so preventDefault-ing it does nothing. Checking for null in combination with calling replaceState on page load seems to work fine though.Thirteenth
I think you meant history.state. You can pass a custom value to history.state when using pushState or replaceState.Thorvaldsen
R
2

I have a solution!

  • When popstate event calls, check the pathname if it has changed, or just the hash changed. Watch below how it works for me:
        window.pop_old = document.location.pathname;
        window.pop_new = '';
        window.onpopstate = function (event) {

            window.pop_new = document.location.pathname;

            if(pop_new != pop_old){

                //diferent path: not just the hash has changed

            } else {

                //same path: just diferent hash

            }

            window.pop_old = pop_new; //save for the next interaction

        };
Rajkot answered 29/2, 2020 at 21:50 Comment(0)
Y
1

I had the same problem in SPA.
I fixed this by checking if current URL and new URL are the same - technically I don't prevent popstate but I prevent fetch if hash only changed.

So, I get current URL when page loaded. It must be var to be global:

var currentURL = window.location.origin + window.location.pathname + window.location.search;

Then when history change event fired (click by link, click native browser buttons 'back' 'forward') I check if current URL and new URL are the same (hash ignored).
If URLs are the same I make return, otherwise I update current URL.

let newURL = window.location.origin + window.location.pathname + window.location.search;
if ( currentURL == newURL ) return;
currentURL = newURL;

Additionally you can to see controller code. I added it to be able stop loading when user click fast a few times 'back' or 'forward' so a few requests starter - but I need to load last one only.

Full solution of load html file when URL changed (ignore when hash only changed), with push to history, and workable 'back' and 'forward' buttons.

// get current URL when page loaded.
var currentURL = window.location.origin + window.location.pathname + window.location.search;

// function which load html.
window.xhrRequestDoc = ( currentLink, pushToHistory = true, scrollTo = 'body' ) => {
    if ( pushToHistory ) {
        history.pushState( null, null, currentLink );
    }

    // get new URL
    let newURL = window.location.origin + window.location.pathname + window.location.search;

    // return if hash only changed
    if ( currentURL == newURL ) return;

    // update URL
    currentURL = newURL;

    document.body.classList.add( 'main_loading', 'xhr_in_progress' );

    // create controler to stop loading - used when user clicked a few times 'back' or 'forward'.
    controller = new AbortController();
    const signal = controller.signal;

    fetch( currentLink, { signal: signal })
        .then( response => response.text() )
        .then( function( html ) {

            // initialize the DOM parser and parse as html
            let parser = new DOMParser();
            let doc = parser.parseFromString( html, "text/html" );

            // insert data and classes to 'body'
            document.body.innerHTML = doc.querySelector( 'body' ).innerHTML;
        } );
}

window.addEventListener( 'popstate', () => {
    // if user clicked a few times 'back' or 'forward' - process last only
    if ( document.querySelector( '.xhr_in_progress' ) ) controller.abort();

    // run xhr
    xhrRequestDoc( location.href, false );
})
Yucca answered 14/11, 2022 at 23:27 Comment(0)
S
0

In case someone still need this:

var is_hashed = false;

$(window).on('hashchange', function() {
    is_hashed = true;
});

window.addEventListener('popstate', function(e){
    // if hashchange
    if (is_hashed) {
        e.preventDefault();
        // reset
        is_hashed = false;
        return false;
    }

    // Your code to handle popstate here
    ..................
});
Sp answered 15/2, 2019 at 16:9 Comment(1)
I can't make it work. Using Chrome 85. The popstate event occurs before hashchangePyrrhotite
S
0

This is pretty simple.

To prevent the popstate event to fire after you click a link with hash you have to eliminate the concequence of clicking the link - which is the addition of the hash to the browser address bar.

Basically you have to create the handler for click event, check if the click is on the element you whant to prevent a hash to appear in the URL and prevent hash to appear by calling event.preventDefault(); in the handler.

Here is the code example:

/**
 * This your existing `onpopstate` handler.
 */
window.onpopstate = function(e) {
    // You could want to prevent defaut behaviour for `popstate` event too.
    // e.preventDefault(); 
    // Your logic here to do things. 

    // The following reload is just an example of what you could want to make
    // in this event handler.
    window.location.reload();
};

/**
 * This is the `click` event handler that conditionally prevents the default
 * click behaviour.
 */
document.addEventListener('click', function(e) {
    // Here you check if the clicked element is that special link of yours.
    if (e.target.tagName === "A" && e.target.hash.indexOf('#the-link-i-want-make-discernable') > -1) {
        // The 'e.preventDefault()' is what prevent the hash to be added to
        // the URL and therefore prevents your 'popstate' handler to fire.
        e.preventDefault();
        processMySpecialLink(e, e.target);
    }

});

/**
 * Just an example of the link click processor in case you want making something
 * more on the link click (e.g. smooth scroll to the hash).
 */
function processMySpecialLink(e, target) {
    // Make things here you want at user clicking your discernable link.
}

Here is the matching HTML markup:

<!-- Somewhere in the markup -->
<span id="the-link-i-want-make-discernable"></span>
<!-- Some markup -->
<a href="#the-link-i-want-make-discernable">My Special Link</a>
<!-- Another markup -->
<a href="#just-common-link">Any other link</a>

This all does what is described above: prevents the default behaviour for a special hash link. As a side effect it makes no popstate event to fire as no hash is added to URL for the special case of clicking the #the-link-i-want-make-discernable hash link.

Sparrowgrass answered 8/5, 2019 at 7:29 Comment(1)
It will not work in a case, when user using 'back' and 'forward' native buttons.Yucca

© 2022 - 2024 — McMap. All rights reserved.