Question

In a web app I'm working on, I'm capturing onBeforeUnload to ask the user whether he really wants to exit.

Now, if he decides to stay, there are a number of things I'd like to do. What I'm trying to figure out is that he actually chose to stay.

I can of course declare a SetTimeout for "x" seconds, and if that fires, then it would mean the user is still there (because we didn't get unloaded). The problem is that the user can take any time to decide whether to stay or not...

I was first hoping that while the dialog was showing, SetTimeout calls would not fire, so I could set a timeout for a short time and it'd only fire if the user chose to stay. However, timeouts do fire while the dialog is shown, so that doesn't work.

Another idea I tried is capturing mouseMoves on the window/document. While the dialog is shown, mouseMoves indeed don't fire, except for one weird exception that really applies to my case, so that won't work either.

Can anyone think of other way to do this?

Thanks!


(In case you're curious, the reason capturing mouseMove doesn't work is that I have an IFrame in my page, containing a site from another domain. If at the time of unloading the page, the focus is within the IFrame, while the dialog shows, then I get the MouseMove event firing ONCE when the mouse moves from inside the IFrame to the outside (at least in Firefox). That's probably a bug, but still, it's very likely that'll happen in our case, so I can't use this method).

Was it helpful?

Solution

What I ended up doing was hooking into the click event for my document, and once I received a click I considered the user had stayed.

It's not a good solution, in most cases (if you are a DiggBar) you'll have lots of false negatives (people will have stayed and you'll never know it), but in our case it made perfect sense, because people interact heavily with our site, not only with the framed sites.

Esentially, my code does this...

function OnBeforeUnload() {
    Event.observe(document, "click", UserStayed);
    if (IConsiderTheIFrameMightBeTryingToPopOut) {
        return "The site is trying to escape. Do you want to stay?";
    }
}

function UserStayed() {
Event.stopObserving(document, "click", UserStayed);
    // New we know the user is still with us for sure.
}

OTHER TIPS

I've been researching this question online for the past few days and the answer, invariably, is "no" you cannot tell, for sure, that a user stayed or left (other than the fact that your app quits working if the user leaves). I hate "no" answers. I came up with the following utility.

var OnBeforeUnload = (function(){
    var
    FDUM = new Function,
    AFFIRM = function(){ return true; };

    var _reg = function(msg,opts){
        opts = opts || {};
        var
            pid = null,
            pre = typeof opts.prefire == 'function' ? opts.prefire : FDUM,
            callback = typeof opts.callback == 'function' ? opts.callback : FDUM,
            condition = typeof opts.condition == 'function' ? opts.condition : AFFIRM;

        window.onbeforeunload = function(){
            return condition() ? (pre(),setTimeout(function(){ pid = setTimeout(callback,20); },1),msg) : void 0; 
        }

        window.onunload = function(){ clearTimeout(pid); };

    }

    var _unreg = function(){ window.onbeforeunload = null;  }

    return {
        register : _reg,
        unregister : _unreg
    };

})();

So, a call such as

OnBeforeUnload.register('Hello!',{ 
    condition:function_that_returns_true_to_fire_event_false_to_ignore,
    prefire:function_to_call_on_page_exit_attempt, 
    callback:function_to_call_if_user_stays
});

condition will be called within the handler to see if it should activate or not. If so, prefire is executed before the popup is shown to the user (you may want to pause a video) and callback will occur ONLY if the user stays.

My logic was to initiate a setTimeout in the body of the event handler. The setTimeout will not execute until the user presses one of the buttons. After they do, it fires immediately. The setTimeout in this case does not call the callback, but a proxy function that executes another timeout for the callback with a delay of 20ms. If the user stays on the page, the callback executes. If not, then another handler is attached to onunload which clears the timeout that will call the callback, ensuring that the callback is never called. I've only had the chance to check it in Firefox, IE and Chrome, but it has worked thus far in all three without a hiccup.

I hope this helps people as frustrated as I was by the patent "no" given universally to this question. This code may not be perfect, but I think it's on the right track.

This is the top hit for this type of question, and I know the specific case (from 8 years ago) could not use this solution because it was in an iFrame, but any future comers will probably be more helped by the answer below than the ones above:

You can easily tell if they've stayed with a body event listener, I think mousemove and keydown combined should be enough.

To tell if they left, you're going to need some server side stuff.

All in all, it'll look something like this (You'll have to fill in the ajax calls to the server yourself):

window.addEventListener("beforeunload", function(event){
    //tell your server they are attempting to leave
    var tryLeaveID = serverLogAttempToLeave();

    //an element with giant letters and warning text, because you can no longer set text on the alert box
    var unsavedWarning = document.getElementById("unsavedChangesWarning");
    unsavedWarning.style.display = "block";

    var onStay = function() {
        //if they stay, tell your server so
        serverRevokeAttemptToLeave(tryLeaveID);
        unsavedWarning.style.display = "none";
        document.body.removeEventListener("mousemove", onStay);
        document.body.removeEventListener("keydown", onStay);
    }

    document.body.addEventListener("mousemove", onStay);
    document.body.addEventListener("keydown", onStay);


    var dialogText =  "undisplayed text";
    event.returnValue = dialogText;
    return dialogText;
});

On the server side, you're going to have to use something ugly, a timer. Make a stack of attempts to leave, and remove them either as requested on stay, or after a minute or so as a confirmed leave. I can't imagine any case where knowing if the user left is any more time sensitive than a few minutes, but if you have one, please comment because I'd like to think about it.

Why not just use the method that StackOverflow does, where you get a popup box that lets you make the choice:

Are you sure you want to navigate away from this page?

You have started writing or editing a post.

Press OK to continue, or Cancel to stay on the current page

Then it doesn't matter how long they take. If they click one button, they leave. If they click the other, they stay.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top