Prevent the URL from changing when popstate occurs - javascript

Hi I'm trying to implement a "Are you sure you want to leave this page?" popup when navigating away from a page with a modified form that hasn't been submitted. The website is a single page app built using a custom framework.
Since all anchor links are handled by the framework it can tell that clicking a side nav link will cause a page change and show a Bootstrap confirm dialog. If the user clicks "Ok" the click goes through and an AJAX call is made to pull the new page content. If they click "Cancel" the modal is dismissed and they stay on the current page.
The part I haven't been able to solve is when a user clicks the "Back" button in their web browser. This triggers the "popstate" event. All navigation/history is managed using the history API since it's a single page app.
The issue is that when someone clicks "Back", the URL changes since "popstate" occurred, then I show my "Are you sure you want to leave?" modal. When the event occurs the URL in your address bar now shows the page you would go to from your history, not the page you're currently viewing. If you click "Cancel" in the Bootstrap confirm you are now left with the wrong URL displayed in your address bar.
event.preventDefault, event.stopPropagation, or return false inside popstate don't stop the URL from changing. AFAIK you can't intercept the "Back" button before "popstate". Trying to window.history.replaceState inside "popstate" doesn't seem to work either.
Does anyone have a solution on how to make this work?
Can you stop the URL from changing in the "popstate" event?
Can you rewrite the history somehow in "popstate" to change the URL back to what it was on the current page and retain the previous entry if you do decide you want it to go through after a modal has been accepted?
Some other solution I haven't thought of?
My original thought was to block the URL change in "popstate" then let the framework trigger the link click to load the new content and change the URL if they click "Yes" but I haven't found a way to do that.
Thanks!

I solved this by doing the following;
Each time a history state is created I generate a timestamp as a guid
The current timestamp is stored in a variable on the top level module
When popstate occurs the incoming history state's guid is compared against the top level modules guid
If the new guid is greater we're going forward, if it's less we're going backward
When the user clicks the back/forward button the hash does change to the incomming URL. If the user clicks cancel it runs history.go( direction ) where direction is 1 or -1 depending on the timestamps. This sets the URL back to what it should be and doesn't do anything weird to the history stack. The top level history variable has a flag to know that we're faking a page change so the link load logic in popstate is not executed.
The only quirk is if the user does click yes to navigate away when that request is sent and returned and a history object is created you do not update the top level module's guid. If you do the guid comparison will always think you're going backward because the new event you just added will always be the highest number. This may not be an issue if you don't make new requests for history URL's but our framework does so it doesn't display stale data like the browser typically does.
It's not ideal since you do see the URL change (no content changes) but so far it seems to work well enough. Another solution I found was to store your own site's history in local/session storage and compare URLs instead of using guid timestamps to find the popstate direction. This won't work in Safari's private browsing mode since both storage layers are disabled so I opted for guid's.

Related

What can be the event similar to onpopstate that works across documents?

From mozilla docs:
A popstate event is dispatched to the window every time the active
history entry changes between two history entries for the SAME
DOCUMENT.
Which window event should i use if i need to listen to 'session history changes' ACROSS DOCUMENTS in a browsing context?
I am writing a js library that helps tracking navigation when user use back/forward browser buttons. The library would record when user is navigated from page B to page A (backwards) or page A to page B(forward). I've achieved the tracking for the pages that use hashes using the 'popstate' event but when user navigate from page A to page B this event doesn't work.
I've looked at pageshow/pagehide events but they fires on simple page loads as well and not just when the 'session history' changes (ie page fetched from history).
What should i be looking at to know "browser has looked into session history to fetch the current page"?
You're going to have to handle the Window's beforeunload event, and also put code on whichever page loading event is appropriate for your use case.
Note that for beforeunload, you don't have time to write something server-side. You'll have to track this in LocalStorage.
There is something weird in your requirements:
You say you are writing a js library, but for this library to work across different documents, for a start, it would at least have to be executed on every documents navigated by the user.
And even if it were the case, there wouldn't be any solid way to do what you want.
Browsers' behavior regarding history navigation vary a lot:
For instance, FF will keep in memory its current state, and will not reload the page per se, but simply reactivate its saved state (i.e you won't even be able to know that the user came back to this page (apart from ugly polling of performance.navigation.type, which despite what MDN says is only absent in Safari).
So all in all, what you want to make is a job for a browser extension, not for a web-library.

How can I listen to back/forward button click in my SPA?

I have a Backbone app, it uses Backbone.history.start({pushState: true}). I render pages based on URL change, back and forward buttons work just fine, views are rendered well.
There is a problem though, page scroll positions are't kept when you click back or forward button. It makes it feel very bad.
I thought I would keep an array of pages visited and I would store scroll position for each of them. Then I would listen if user click back/forward and I would load appropriate scroll position.
But.
I just found out I am unable to tell when user click a back or forward button. If I could, I would just read stored scroll position of previous page and apply it. What am I doing wrong?
You could store the scroll location as a part of the route, using something like:
router.navigate('/my/route/scroll/[my_scroll_location]', {replace: true});
so It won't add a new history point, but will modify the current one. this way, when you hit "back" it will go to the previous "page"/route, and then hitting "forward" have enough information to scroll to the right point.

What's window.onunload?

Im a beginner and i see that line of code a lot on javascript files , for example :
window.onunload=function(){};
when should i use this and what is it role exactly ?
thank you .
This function gets called when the user closes the browser or navigates away from the page.
https://developer.mozilla.org/en/DOM/window.onunload
You also might want to check out onbeforeunload, which allows you to prompt the user with a confirmation message before leaving the page. This can be useful for reminding the user to save their changes, or making sure the user doesn't actually want to claim their free iPad 2.
onunload is an event that is triggered when the user navigates away from your page, or when the page is "unloaded".
It's triggered when a user follows a link, or closes the tab. It's used for clean up. Like saving a user's data when they leave the page. Usually it's paired with onbeforeunload (which is called before onunload is using the same criteria) to warn a user that they have unsaved data.
if a page has an onunload handler, browsers that restore the page state (remembering changed form field values, script environment) when you navigate away and back to the page do not-
that is, they load the page as if it was the first time it was opened, with no user applied changes.

can window.onPopState() event handler know direction of navigation? (forward,backward)

i have a site that uses ajax navigation with the pushState() method and an onpopstate() handler.
when first visiting the main page, i display a default content (without ajax).
when someone navigates away from the default page and then goes "back," a null state is supplied when they should be returning to the default page. thus, they stay on the second page of the website instead of the first ( although the handler can easily use ajax to bring the initial content back).
my only way to fix this was to do a pushState on my initial page load with information on what the initial content is. with this method, the "back" button works great!
BUT if someone was at www.stackoverflow.com before going to my site, the goes "back" to stackoverflow and then "forward" to my site again, the initial content is shown, but then reloaded again because the "popped" state is not null.
so can i tell, in the onPopState() handler, whether the user is going backward or forward?
I've had some success with using the pageshow event in Firefox to trap that initial entry into the site through forward and back buttons.
can window.onPopState() event handler know direction of navigation? (forward,backward)
There is an issue. You can skip states (hold the back button, select the state you want). This makes it incredibly hard.
when someone navigates away from the default page and then goes "back," a null state is supplied when they should be returning to the default page. thus, they stay on the second page of the website instead of the first ( although the handler can easily use ajax to bring the initial content back).
You can fix that with History.js with it's optional data persistance.

Alternatives to disabling / disable back button in firefox and IE

Our application forbids going back for several reasons.
Basically because that's just how our application works (JSF with facelets as GUI)
You always have to enter on the welcome site, once you chose an application-flow you can only leave / abort when you tell the application (e.g. press a button). If you just browse away e.g. enter "example.com" in the address bar the state of your flow gets saved and once you relogin, you can resume the work. Going back is only possible when it was specifically designed like this with a 'back' submit - button.
Of course users keep pressing the 'back' button (i would do so as well) and they keep getting 'error: session out of synch'. This is a learning process and a couple years ago we just disabled the back-button to make things clear. Sadly this is no longer supported.
So instead of teaching the user the hard way and forcing him to relogin, are there some good alternatives I'm missing?
i found this link which should offer 3 methods to disable the back button - but in reality it just further confirms the fact that it is impossible to do it in a semi-nice way.
when the user tries to go to a previous page you can redirect him to the page he should be at in other words catch the "out of sync" and redirect him
You might find a workable solution here How do I insert an entry into browsing history via JavaScript
by inserting an extra step into the browser's history (perhaps a link to the current page with query string parameters that result in a nice big red box message to the user), or you could try attaching an event handler to the OnBeforeUnload event so the user gets a confirmation dialog when trying to leave the page (you'd want to remove the handler when the submit button was clicked).

Categories