I have a simple function to show a spinner overlay while a new page request is being made.
$(window).on("beforeunload", function() {
$('#nav-spinner').show();
});
This works perfectly.. but, it is used on a complex WordPress site and there are other (third party) components that also use this event to sometimes cancel the navigation (for instance, a confirmation when navigating away from a partially filled form).
Is there any way to determine if another function cancelled the page unload so I can remove my overlay immediately when they are remaining on the page.
I would like to do this when an actual navigation is cancelled - using a timer to remove the overlay will result in either the overlay being hidden prematurely or remaining for longer than it should.
Test Case that shows the problem
So the following code shows what you have currently. I am setting the background red since it is a minimum amount of code.
window.addEventListener("beforeunload", function(event) {
document.body.classList.add("red");
});
// 3rd party code that is showing the "are you sure"
window.addEventListener("beforeunload", function(event) {
event.preventDefault();
event.returnValue = 'I am 3rd party code';
});
.red {
background-color: red;
}
<form>
<button>Click Me then click "Cancel"</button>
</form>
Solving the problem
So now that we have the test case for what is wrong. The background should NOT remain red when the user clicks cancel. So how can we detect it? Well there is NO events that tells you what the user did.
So the only thing you can do would be to add a timer to remove what you added when the user cancels it. So if they click cancel, the timer runs and removes it.
But how do we keep it there if they do not cancel it? We use unload to kill the timeout that hides it. So remove the timeout and it will not fire.
var timer
// update the page as page exits
window.addEventListener("beforeunload", function(event) {
document.body.classList.add("red");
// if this timer runs, it means beforeunload was cancelled
timer = window.setTimeout( function () {
document.body.classList.remove("red");
}, 50);
});
// remove the timer when the pages finally exits so the class is not removed.
window.addEventListener("unload", function(event) {
window.clearTimeout(timer)
})
// 3rd party code that is showing the "are you sure"
window.addEventListener("beforeunload", function(event) {
event.preventDefault();
event.returnValue = 'I am 3rd party code';
});
.red {
background-color: red;
}
<form>
<button>Click Me then click "Cancel"</button>
</form>
You may have to play with the timeout millisecond values. The flash of content for showing could be lessened with a transition, but hopefully the browser does not kill that unload.
Related
Found a working script on the internet, except that I can't change the message for FF. Which would be great. (Works for Chrome)
window.onbeforeunload = function(e) {
if(!e) e = window.event;
e.cancelBubble = true;
e.returnValue = 'You sure you want to leave?';
if(e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
};
What ever, this works great. But I wanna do something when the user clicks on abort to keep visiting the page.
How I could do that?
You could use a timed function to do something if user decides to stay on page. The confirmbox shown by onbeforeunload is blocking the execution untill user clicks either OK or Cancel. If Cancel will be clicked, the timed function will be executed, otherwise the page is closed, and the timed function will never be executed.
The code would be something like this:
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = 'Do you want to leave the page?';
setTimeout(function () { // Timeout to wait for user response
setTimeout(function () { // Timeout to wait onunload, if not fired then this will be executed
console.log('User stayed on the page.');
}, 50)}, 50);
return 'Do you want to leave the page?';
});
A working demo at jsFiddle.
You can't show a message in the confirmbox in FF for the reason explained in the accepted answer in the post RGraham has linked in their comment.
I develop a web GUI for a special tablet. This tablet is running with Linux and the used browser is Chromium. The application is a web application with PHP, HTML5, JQuery and JavaScript. Now I run into a problem. The screen is a touchscreen and the user is able to navigate through the application by touch the screen. However now we decided to add a feature for saving electricity. This feature will shutdown the background light after three minutes. To turn on the backlight again, the user should touch the screen again. This leads to this problem, because on any touch the buttons are also pressed even if the background light is shutdown. I want to prevent this by discarding all clicks on the touchscreen if a cookie is set. If this cookie is not set the touchscreen and the application should work as desired. How can I solve this problem?
I installed an event listener to register all clicks and to reset the time.
window.addEventListener('mousedown', function(e){
$.get('php/timeupdate.php', function(){});
}, false);
Code used to stop the execution:
$(document).on('click', function(event) {
$.get('php/getwakeup.php', function(e){
if(e==='true'){
//event.preventDefault(); // I tried all three possibilities
//event.stopImmediatePropagation();
event.stopPropagation();
}
});
});
You can try this:
$(document).on('click', function(event) {
// get your cookie
if( cookie is set ) {
event.stopPropagation();
}
});
event.stopPropagation(); stops every event handling from where you called it =)
EDIT:
You have to set your $.get call synchronous or do it completely diffrent. Take a look at the jQuery.Ajax documenation. There is a parameter called "async".
But be careful unless the call is ready nothing else will be executed on you page! So if your script doesn't answer nothing else will work on your site.
The better solution would be setting ja recurring call that will get the information you need. Set it to every two seconds (setInterval is your friend here). If your response is true than set a global variable that you can check in your onDocumentClick event.
window.isBacklightOff = false;
setInterval(function() {
$.get('php/timeupdate.php', function(e) { window.isBacklightOff = !!e; })
}, 2000);
$(document).on('click', function(event) {
// get your cookie
if( window.isBacklightOff === true ) {
event.stopPropagation();
}
});
When the back light goes off you can set some flag handleEvents=false;
So when the flag is on don't handle any events.
Now when the back light is on you can set handleEvents = true.
$(document).on('click', function(event) {
// get your flag say handleEvents
if( !handleEvents ) {
event.stopImmediatePropagation();
return;
} else {
//do your biz logic send ajax..etc
}
});
Reason why your code above is not working:
$(document).on('click', function(event) {
$.get('php/getwakeup.php', function(e){
if(e==='true'){
//event.preventDefault(); // I tried all three possibilities
//event.stopImmediatePropagation();
event.stopPropagation();
}
});
});
The function inside $.get is async and called on success in that you are setting the event to stop propagating...well by that time when the success function is called the event is already complete and has called all the listeners.
So in short you must not do the event stop propagation inside the success function.
Need your valuable feedback on this. I have implemented idletimeout functionalty so that session will expire in 3 minutes if the user is idle.
In three scenario, I am resetting the timer.
On click or tap
after 2 seconds while processing is in progress
on scroll or scrollstart
The problem is sometimes session is getting timeout before the 3 minutes even if I tap, click or scroll and user is redirected to login page even if the function is gets called on tap click or scroll and resettimers is getting called. I am facing a bit hard time to figure out the loophole.
I am posting the code; please let me know if you notice anything.
// Set timeout variables.
var timoutNow = 180000 ;
var ua = navigator.userAgent;
var event = ((ua.match(/iPad/i)) || (ua.match(/iPhone/i)) || (ua.match(/iPod/i))) ? 'touchstart' : 'click';
var logoutUrl = Mobile+'/login.html'; // URL to logout page.
var timeoutTimer;
// Start timers.
function StartTimers() {
timeoutTimer = setTimeout("IdleTimeout()", timoutNow);
//console.log(timoutNow);
}
// Reset timers.
function ResetTimers() {
clearTimeout(timeoutTimer);
StartTimers();
}
// Processing time check.
function Laodtimercheck()
{
setInterval(function(){
if($("body").hasClass("loading-processing")==true)
{
ResetTimers();
}
}, 2000);
}
// Logout the user.
function IdleTimeout() {
sessionStorage.clear();
document.location.href = Mobile+'/login.html';
}
$(document).live(event, function(){
//console.log("Reset timers: ON TAP OR CLICK");
ResetTimers();
});
$(document).mouseover(function() {
//console.log("Reset timers: ONMOUSEOVER");
ResetTimers();
});
$(window).scroll(function() {
//console.log("Reset timers: SCROLL");
ResetTimers();
});
$(document).live("scrollstart", function(){
//console.log("Reset timers: SCROLLSTART");
ResetTimers();
});
EDIT: setTimeout only working first two times; next time ResetTimers are getting invoked but the setTimeout is not working or I might be missing something here as the session is getting timed out as per pervious two call time only....
The real problem that you're having is the folowing: "ResetTimers" not being invoke enough.
Why is not being invoked enough? I'll try to answer that.
All the logic is Ok with a few exceptions. There are two "problematic" events that not work or I think don't work like you want.
1.- LIVE (event)
That event is not being fired never. You cannot attach a live event to a document, yo need to specify a node, like html or body.
$("body").live(event, function(){
//console.log("Reset timers: ON TAP OR CLICK");
ResetTimers();
});
That's why when clicked the timer don't reset.
Another (and recomended) way to use a variable for binding events is to use .delegate().
Since jQuery 1.4.3+ is the recomended way of doing this.
$(document).delegate("body", event, function(){
//console.log("Reset timers: ON TAP OR CLICK (delegate)");
ResetTimers();
});
Any of those (live on body or delegate) would work and timer get reset on click or tap event.
2.- MOUSEOVER
There isn't a problem per se with this event, but I think it would be insuficient. MouseOver only fires where the pointer get on screen first time, if the mouse don't leave the window the mouseover never fires again. Maybe, a better or added way of control "mouse hovering" on the document is to use onmousemove event. Like I said in a comment before, I don't know if you want to be strict on this, so I left you a proposal and let's see if it fits your needs.
$(document).mouseover(function() {
console.log("Reset timers: ONMOUSEOVER");
ResetTimers();
});
In my tests, events get fires a lot, and the timers get reset on each event without problems. I hope it helps you.
I'm creating a popup window that has a beforeunload handler installed. When the "Close" file menu item is used to close the popup, the beforeunload handler is called twice, resulting in two "Are you sure you want to close this window?" messages appearing.
This is a bug with Firefox, and I've reported it here, but I still would like a way to prevent this from happening. Can you think of a sane way of detecting double beforeunload to prevent the double message problem? The problem is that Firefox doesn't tell me which button in the dialog the user elected to click - OK or cancel.
<script type="text/javascript">
var onBeforeUnloadFired = false;
window.onbeforeunload = function ()
{
if (!onBeforeUnloadFired) {
onBeforeUnloadFired = true;
event.returnValue = "You have attempted to leave this page. If you have made any changes to the fields without clicking the Save button, your changes will be lost. Are you sure you want to exit this page?";
}
window.setTimeout("ResetOnBeforeUnloadFired()", 10);
}
function ResetOnBeforeUnloadFired() {
onBeforeUnloadFired = false;
}
</script>
Set a variable in the handler to prevent the dialog coming up the second time. Use setTimeout to reset it afterwards.
This is definitely a FF bug. I've reported it at https://bugzilla.mozilla.org/show_bug.cgi?id=531199
The best solution I've found is to use a flag global variable that is reset after so many milliseconds, say 500 (this ensures that the function can be called again, but not immediately after its appearance).
See last code in:
http://social.msdn.microsoft.com/Forums/en/sharepointinfopath/thread/13000cd8-5c50-4260-a0d2-bc404764966d
I've found this problem in Chrome 21, Firefox 14, IE 7-9, Safari 5 (on PC).
The following works on all of these browsers. If one removes the window.onbeforeunload function during the event this will prevent the second call. The trick is to reset the window.onbeforeunload function if the user decides to stay on the page.
var window_on_before_unload = function(e) {
var msg;
// Do here what you ever you need to do
msg = "Message for user";
// Prevent next "window.onbeforeunload" from re-running this code.
// Ensure that if the user decides to stay on the page that
// this code is run the next time the user tries to leave the page.
window.onbeforeunload = set_on_before_unload;
// Prepare message for user
if (msg) {
if (/irefox\/([4-9]|1\d+)/.test(navigator.userAgent))
alert(msg
+ '\n\nThe next dialog will allow you to stay here or continue\nSee Firefox bug #588292');
(e = e || window.event).returnValue = msg;
return msg;
}
};
// Set window.onbeforeunload to the above handler.
// #uses window_on_before_unload
// #param {Event} e
var set_on_before_unload = function(e) {
// Initialize the handler for window.onbeforeunload.
window.onbeforeunload = window_on_before_unload;
}
// Initialize the handler for window.onbeforeunload.
set_on_before_unload();
Create a global variable that is set to true inside the handler. Only show the alert/popup when this variable is false.
I use the following snippet to track the exitcount
When the page loads the following variable exitCount is initialized
if (typeof(MTG) == 'undefined') MTG = {};
MTG.exitCount=0;
and in the Window unload event
$(window).bind("beforeunload", function(){
if (MTG.exitCount<=0)
{
//do your thing, save etc
}
MTG.exitCount++;
});
I've found that instead of doing your own call to confirm(), just do even.preventDefault(); within the beforeunload event. Firefox throws up its own confirm dialog.
I'm not sure if this is the correct/standard thing to do, but that's how they're doing it.
I have a document opening another popup window with window.open. In the original window I have registered (with jquery) a listener for "unload" event like this:
var popup_window = window.open(...)
$(popup_window).on('unload', function(event) ...
I have came across this page because the event was effectively triggering twice. What I have found is that it is not a bug, it triggers twice because it fires once for "about:blank" page being replaced by your page and another for your page being unloaded.
All I have to do is to filter the event that I am interested in by querying the original event:
function (event) {
var original_url = e.originalEvent.originalTarget.URL;
if (original_url != 'about:blank')
{
... do cool things ...
}
}
I don't know if this applies to the original question, because it is a special case of a window opening another, but I hope it helps.
A user is clicking the button with id continueButton. If they stay on the same page upon after clicking the button, then I trigger UK-AwesomeEvent.
My effort so far
Bootstrapper.on('click', '#continueButton', function() {
setTimeout(function() {
//if nothing happens then trigger this event
if (document.title === 'Current address' && doNotGetRedirectedToAnotherPageWhenButtonIsClicked) {
Bootstrapper.ensEvent.trigger("UK-AwesomeEvent");
}
}, 250);
});
Desired outcome
In place of doNotGetRedirectedToAnotherPageWhenButtonIsClicked I want a method that basically states that a user is not redirected to another page. I couldn't find anything online.
In general, you can call the preventDefault() method of the event object…
Bootstrapper.on('click', '#continueButton', function(event) {
event.preventDefault()
to prevent whatever the default behaviour is (and in this context it sounds like that behaviour is "A submit button is triggering the submission of a form").
However, because you have used setTimeout, it is too late. The event handler function has already finished and the form submission started.
The closest you could come would be to always prevent the default behaviour and then restart the form submission if you later change your mind:
Bootstrapper.on('click', '#continueButton', function(event) {
event.preventDefault()
setTimeout(function() {
if (document.title === 'Current address') {
Bootstrapper.ensEvent.trigger("UK-AwesomeEvent");
} else {
event.target.form.submit();
}
}, 250);
});
(Note there is a fair chunk of educated speculation here because you didn't provide a complete, reduced test case)