Problem:
I came in to work today to users of a company website complaining that the sites keeps asking for confirmation before navigating to any other page. I tested it, before and after updating Chrome (which I’d left open) and it started with the latest version of Chrome (118) for me and this particular site.
An answer on here suggested that it was caused by beforeunload events, and to disable any such events. I tried that, yep, that prevented the dialog.
Here’s the thing. This site syncs with the server when the page navigates away and it uses that event to do so.
…So how am I supposed to proceed?
Is there a different way of syncing with the server a final time? Am I implementing beforeunload (etc) wrong? Why did it work yesterday but not today (I mean why did Chrome fix something that wasn’t broken)?
I should note, I’m self taught though been doing it for a few years. I don’t enjoy the JS side so much as the PHP side, everything I do in JS feels like it’s hacky and possibly wrong but it gets the site out so it can be used.
I tried using sendBeacon at one point but it just never actually worked. Maybe I misunderstood how it works, maybe it doesn’t actually work, who knows? So I use a post method that is just a wrapper around JQuery $.ajax but with other stuff pertaining to the particular site.
So there is a common method (it’s in an app object that is loaded on every page), which is adapted from some answer here in the past:
addUnloadEvent( unloadEvent ) {
let executed = false;
let exec = function ( event ) {
event.preventDefault();
if ( !executed ) {
executed = true;
return unloadEvent();
}
};
document.addEventListener( 'visibilitychange', function ( event ) {
if ( document.visibilityState === 'hidden' ) {
exec( event );
}
}, { capture : true } );
window.addEventListener( "pagehide", exec, { capture : true } );
window.addEventListener( "beforeunload", exec, { capture : true } );
window.onbeforeunload = exec;
window.addEventListener( 'unload', exec, { capture : true } );
},
Probably seems like a shotgun approach, ugly, bad, but y’know, I just have to get this stupid thing working so customers can use the site.
Different controllers have their own js files that extent the app method and may run an init method, and in one particular init function for one particular controller we have this:
....
$app.addUnloadEvent( function () {
return $app.syncBasketWithServer();
} );
....
That method is this:
syncBasketWithServer() {
return $app.post( '/order/syncBasket', { 'Basket' : $app.Basket }, function ( data ) {
return data.status === 'success';
} );
}
Solution:
So the beforeunload
event will prevent users from leaving IF some conditions are met:
- Calling the event object’s preventDefault() method.
- Setting the event object’s returnValue property to a non-empty string value or any other truthy value.
- Returning any truthy value from the event handler function
see: https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
since the FIRST thing you do in the event handler is calling preventDefault(), the message will be triggered.
Anyway, making requests when the user whats to leave is (I would say) bad practice.
Better sync the Basket on change, than on leave.
If you need the information you may send the request but as far as i know there is no guarantee that the request will be sent.
If you realy need a signal that a user left, maybe open a connection to the user (Websocket, Open HTTP Connection), wich will trigger the Beacon if the user disconnects.