123

When editing a page within my system, a user might decide to navigate to another website and in doing so could lose all the edits they have not saved.

I would like to intercept any attempt to go to another page and prompt the user to be sure they want this to happen since they could potentially lose their current work.

Gmail does this in a very similar way. For example, compose a new email, start typing into the message body and enter a new location in the address bar (say twitter.com or something). It will prompt asking "Are you sure?"

Ideas how to replicate this? I'm targeting IE8, but would like to be compatible with Firefox and Chrome also.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Chris Ballance
  • 33,810
  • 26
  • 104
  • 151

4 Answers4

160

Similar to Ghommey's answer, but this also supports old versions of IE and Firefox.

window.onbeforeunload = function (e) {
  var message = "Your confirmation message goes here.",
  e = e || window.event;
  // For IE and Firefox
  if (e) {
    e.returnValue = message;
  }

  // For Safari
  return message;
};
Eli Grey
  • 35,104
  • 14
  • 75
  • 93
  • 1
    Could you explain the first line (var message = ...)? I dont know what that comma and the second expression is doing. – mtmurdock Jul 27 '12 at 17:01
  • 7
    @mtmurdock That's just Javascript syntax to declare multiple variables. `var foo, bar;` is the same as `var foo;` `var bar;` – T Nguyen Oct 16 '12 at 07:02
  • 4
    @mtmurdock, The second statement "_var_ e = e || window.event;" means set **e** equal to the parameter _e_ (if it's truthy) or window.event if not truthy. It will not be truthy if the parameter _e_ is null. – T Nguyen Oct 16 '12 at 07:09
  • Saved the day, thought this might take me all day. New to Client side and didnt know of the onbeforeunload function! Thanks! – George Norberg Jun 30 '14 at 08:30
  • 3
    @Blieque addEventListener isn't supported in IE8. Don't edit people's answers without even reading the question. – Eli Grey Jan 14 '17 at 10:37
  • @EliGrey Sorry, my mistake. I've submitted a different change that uses `EventTarget.attachEvent` for IE. I maintain that events should not be bound with assignment. – Blieque Jan 14 '17 at 20:50
  • 4
    Doesn't work any more in Chrome (deprecated): https://developers.google.com/web/updates/2016/04/chrome-51-deprecations?hl=en#remove_custom_messages_in_onbeforeunload_dialogs – Micha Schwab Aug 17 '17 at 18:35
  • 1
    @MichaSchwab it does still work but you just cannot specify the message displayed anymore to prevent scams. In Chrome 81 it says "Leave Site? Changes that you made may not be saved. [Cancel] [Leave]" – scipilot May 17 '20 at 01:59
  • Before a page-reload the Chrome preset dialogue is similar but with "Reload" instead of "Leave" in the title and button. – scipilot May 17 '20 at 02:37
26

See this article. The feature you are looking for is the onbeforeunload

sample code:

  <script language="JavaScript">
  window.onbeforeunload = confirmExit;
  function confirmExit()
  {
    return "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?";
  }
</script>
jantimon
  • 36,840
  • 23
  • 122
  • 185
  • 2
    Is there any way to identify that user has opted the "Leave this page" option rather than "Stay on this page"? – Shilpa Soni Apr 20 '16 at 08:53
  • @ShilpaSoni you would shortly get a subsequent unload event, https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event but I don't think there's a synchronous way. – scipilot May 17 '20 at 02:03
6

Instead of an annoying confirmation popup, it would be nice to delay leaving just a bit (matter of milliseconds) to manage successfully posting the unsaved data to the server, which I managed for my site using writing dummy text to the console like this:

window.onbeforeunload=function(e){
  // only take action (iterate) if my SCHEDULED_REQUEST object contains data        
  for (var key in SCHEDULED_REQUEST){   
    postRequest(SCHEDULED_REQUEST); // post and empty SCHEDULED_REQUEST object
    for (var i=0;i<1000;i++){
      // do something unnoticable but time consuming like writing a lot to console
      console.log('buying some time to finish saving data'); 
    };
    break;
  };
}; // no return string --> user will leave as normal but data is send to server

Edit: See also Synchronous_AJAX and how to do that with jquery

Community
  • 1
  • 1
Remi
  • 20,619
  • 8
  • 57
  • 41
  • I like this alternative to the popup. Thanks for the suggestion, Remi. – Chris Ballance Jan 09 '12 at 15:39
  • 2
    that doesn't make absolutely no sense -1. javascript is not threaded what you are doing is doing a postRequest and then pausing the computation of the site without allowing no one to do nothing (including your postRequest, that meanwhile was already sent before the computation pause). – fmsf Jul 04 '12 at 14:12
  • And that is exactly why it works: indeed the post-request was send before the computation pause, but this little blocking of the page before it is actually left assures enough time for the browser client (not javascript) to really finish sending this request properly. I noticed that when I only send the request (without blocking) and leave the page immediately after, the request is not always send. – Remi Jul 06 '12 at 04:55
  • 6
    I wouldn't use that in production. console.log is not crossbrowser; each postRequest suffers from the delay of the previous ones (which also means the page will hang there for the duration of your loop * scheduled requests); the requests aren't started in parallel; you aren't guaranteed to really send your requests. If I were forced to confront with this problem, I'd go with a synchronous ajax request, if there is only one request. With multiple requests I'd use async ajax requests and a loop which checks results (either success or error) from callbacks of the requests. – framp Sep 04 '12 at 21:59
  • I did not know that console.log is not cross-browser and a [Synchronous Ajax request](http://hunlock.com/blogs/Snippets:_Synchronous_AJAX) should indeed be the preferred solution (for a single request). The mentioned `SCHEDULED_REQUEST` is an object that acumulates any non-urgent information that should be send to the server, like a buffer of data, effectively joining multiple requests to one (of course the url of these requests is the same). Using a Synchronous Ajax request before unload should solve the problem cross-browser as you said. Thanks for your valuable comment! – Remi Sep 06 '12 at 08:56
  • 1
    @fmsf holy grammar !! – Mfoo Apr 03 '18 at 16:05
  • But then the user cannot cancel, and discard the changes. – scipilot May 17 '20 at 02:00
0

I have users who have not been completing all required data.

<cfset unloadCheck=0>//a ColdFusion precheck in my page generation to see if unload check is needed
var erMsg="";
$(document).ready(function(){
<cfif q.myData eq "">
    <cfset unloadCheck=1>
    $("#myInput").change(function(){
        verify(); //function elsewhere that checks all fields and populates erMsg with error messages for any fail(s)
        if(erMsg=="") window.onbeforeunload = null; //all OK so let them pass
        else window.onbeforeunload = confirmExit(); //borrowed from Jantimon above;
    });
});
<cfif unloadCheck><!--- if any are outstanding, set the error message and the unload alert --->
    verify();
    window.onbeforeunload = confirmExit;
    function confirmExit() {return "Data is incomplete for this Case:"+erMsg;}
</cfif>
gordon
  • 1,152
  • 1
  • 12
  • 18