9

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).

Chad Birch
  • 73,098
  • 23
  • 151
  • 149
Daniel Magliola
  • 30,898
  • 61
  • 164
  • 243

4 Answers4

6

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.
}
Daniel Magliola
  • 30,898
  • 61
  • 164
  • 243
  • code is fine. I want to run logout() function if user clicks on "Leave this Page" pop button. But how can I detect whether user have clicked on "Leave this Page" or not? – Bit_hunter Aug 01 '15 at 13:21
  • You cannot. Also, if the user decided to leave, it's too late to try to logout, the HTTP request you're probably trying to send to the server would get cancelled immediately and never reach your server. – Daniel Magliola Aug 02 '15 at 13:43
4

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.

Kiefer
  • 41
  • 1
  • This only worked for me if I set the timeout to several seconds. Otherwise, when the user clicked the button to leave the page, the 'callback' (function_to_call_if_user_stays) still got called. In my case, this is because the requested link that triggered the onbeforeunload() takes several seconds to return from the server, so onunload() is not called for a few seconds. – CyberMonk Jan 31 '14 at 18:36
0

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.

Seph Reed
  • 8,797
  • 11
  • 60
  • 125
-7

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.

Community
  • 1
  • 1
Chad Birch
  • 73,098
  • 23
  • 151
  • 149
  • I AM doing that. But I want to do some stuff once they stayed, ONLY IF they stayed. I'm basically letting the server know that they did stay, which means that the unload was caused by something trying to get out of an IFrame, and not by them closing the window. This lets me log possible causes for the unload, which I can later do stuff about. But I only want to do this IF they stayed, I want to be sure that they stayed before notifying the server. – Daniel Magliola May 05 '09 at 18:02
  • So use the "I want to stay" button-click in the popup to trigger the execution of the "stuff". Maybe I'm not understanding the problem. – Chad Birch May 05 '09 at 18:07
  • 1
    Hmmm, the "i want to stay" button belongs to the browser, not to me. I can't touch it or do anything to it, it's a standard dialog that I can't interact with at all. And as far as I know, there is no specific event in the window/document that triggers when the user decides to stay. – Daniel Magliola May 05 '09 at 18:22
  • Well, assuming it's a confirm() dialog like SO uses, you can use the return value of the confirm() call to determine which button they clicked. I'm not 100% sure offhand, but I believe if they click "OK", you get a return of true, and if they click "Cancel" it returns false. So you can then process that to take your actions. – Chad Birch May 05 '09 at 18:40
  • If it's a confirm() dialog, then it's not what i'm using. Basically, the only way to keep the page from unloading it to return some string in the onBeforeUnload event, and that string will be shown to the user in a custom dialog. I guess i *could* show a confirm() dialog, but whatever they answer there wouldn't stop the page from being unloaded... – Daniel Magliola May 05 '09 at 19:06
  • Hmm, maybe you can do some digging into SO's javascript to see how they're using that dialog. It's seemed to work well all the times I've tried to leave/close the page when I've already started writing something. And they obviously have some way to stop you from leaving the page if you click Cancel. – Chad Birch May 05 '09 at 19:13