Do You Want to Save Your Changes?

By on June 18, 2004

Update: See the comments. I have found a way around this in both IE and Mozilla. I thought about deleting the post, but if taken with the comments, it’s still a pretty good tutorial of how to do this.

Another Update: One of our commentors points out that Mozilla 1.7 — released June 17 — now supports the onbeforeunload event, making this system one step closer to cross-browser. See the release notes.


Web apps are great, but they have interface problems. A Web-based interface is just never going to be as rich as a traditional client-server app. Spolsky alluded to this in a post I made yesterday. HTML can really go just so far before you run into limitations. Here’s a big one:

That’s a pretty common dialogue box, but very hard to do on the Web. Picture this:

Joe User has just spent 20 minutes in the article editing interface of your Web-based content management app. He’s worked over this article until it’s just perfect, laboring intensively over every word. Then he accidentally hits Refresh and it’s all gone. Joe is not happy.

That’s the problem with Web forms — there’s no real way to stop the user from screwing themselves this way.

After they start work on the form content, they can (1) click a link and navigate away from the page, (2) press the Reset button on the form (never put these on your forms — think about it: when was the last time you used one), (3) close the browser, (4) refresh the page, etc. There’s any number of things they can do to wipe out their form data, and it’s very hard to stop them. I know this because I tried…

I set out this morning to try and minimize these risks in an app I’m developing. I come away from this process pretty convinced that it’s not practical. Here’s what I tried to do:

In my page, I bound a JavaScript function to the unload event:

window.onunload = warn_if_form_dirty;

This means the “warnifform_dirty” function will run whenever the current page leaves the browser window for any reason. Note that you can’t stop the event — the page is leaving no matter what, but you can do a few things before it goes. (If you could actually stop the page from unloading, then pop-up advertisers would have a field day with it.)

I also set two variables:

form_dirty = false;
warn_onunload = true;

The first variable indicates whether or not the form data is “dirty” — whether it’s been changed since they loaded the page. The second variable indicates whether or not we should warn them that they’re going to lose their data. When should we NOT warn them? When they submit the form. Remember that the unload event fires whenever the page unloads, even if it’s because they submit, in which case they WANT it to unload.

I added this to all my form fields:

onchange="form_dirty=true;"

So whenever a form field changes, the “dirty” switch is flipped. And I added this to my FORM tag:

onsubmit="warn_onunload=false;"

Because, remember, if they submit the form, I don’t want to warn them. In this case the unload event is fine.

Then I wrote the actual function that will run when they attempt to navigate away from the page while the form is dirty:

function warn_if_form_dirty()
{
    if(warn_onunload == true && form_dirty == true)
    {
    alert('WARNING: You have chosen to navigate away from this page.  You have UNSAVED CHANGES in this form.  If you want to save these changes, click the Back button on your browser to return to your changes and press the "Save" button.');
    }
}

So, the system works something like this:

  1. User loads the page and starts working. “form_dirty” is false, because no changes have been made. “warn_onunload” is true because we want to warn them.

  2. User changes some form data. When this happens “form_dirty” is now true because they have done some work and are at risk of losing it.
  3. User accidentally hits the Refresh button (stupid user…). The onunload event fires, the “warn_if_form_dirty” function runs. It finds that both “form_dirty” and “warn_onunload” are true, so it pops the warning.

The warning, for its part, basically says, “you may have screwed up, and here’s how to fix it if you want.” Remember that I can’t stop the event. It would be nice if I could, but the potential for abuse is huge, so you can’t. All I can do is tell them what they did, and how to fix it.

The problem: the advice isn’t always true.

It would be nice if you could always hit Back and get back to your data, but this turned out to be a lie. I tried it on a number of machines here in the office, and whether or not this is true depends on the browser and on the cache settings.

It seemed to work on Mozilla and Firefox all the time, but IE was fussy. For some people it worked, but for others, IE pulled the page fresh every time. It didn’t work more often than it did.

So I come away from his experiment thinking that fighting this scenario is a losing battle. If you’re on an intranet, and everyone is using IE with loose security, you could conceivably do some ActiveX mojo to head it off, but that isn’t an option for me.

A possible solution: browser developers should build this functionality into the browser. They should detect whenever a user (not script) has changed form data and warn the user if they try to unload the page. Let the user choose to continue or cancel the unload.

If anyone else has thoughts on theories on this subject, I’d love to hear them.

###

Comments

  1. Chris says:

    I think you could use the onbeforeunload event instead:

    http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/events/onbeforeunload.asp

    That way, the user could cancel the refresh and remain on the same page.

  2. Deane says:

    onBeforeUnload is IE-only, but that's not necessarily bad. Fixing the problem for a vast majority of people is better than nothing, I guess.

  3. Chris says:

    Since the back button worked all the time on Mozilla and Firefox with the method you described, maybe you could use a combination of the two to support all browsers.

  4. Deane says:

    The exact same system I described above using "onbeforeunload" works beautifully in IE. Mozilla seems to ignore it, which I can live with because there are other ways of doing it in Mozilla.

    I'm tempted to delete this entire post.

  5. Dave says:

    I work in a publishing company. We have both thick (win32) and thin (web) clients for our editorial staff to use.

    For the web clients, we've got a system that works for some things, but is not idea. Similarly to what you've done, we've put triggers and "isdirty" state flags all over the place. But in addition, we place the editorial tool inside a full window frameset (only a single full sized frame inside the frameset). And where the frameset is definied, we have a set of variable defs.

    Then when a trigger occurs, we simply copy the relevant information we need to keep into the variable (top.varname = document.formname.inputname.value etc...) and thus the data is safe. Sure it takes up memory, but anything short of closing the browser, and we can still gain access to the users input.

  6. As an independent software developer of a web cms (the Big Medium content management system), these are some useful approaches to protect form-submitted content. Thanks to all for sharing these!

    Just to add to the info above, I see that the recently released Mozilla 1.7 now supports the onbeforeunload event.

  7. Deane says:

    See this post for more about Big Medium:

    http://www.gadgetopia.com/2003/07/04/BigMedium.html

  8. Deane says:

    I found a little glitch -- onbeforeunload runs when you activate a pop-up window.

    I have a helper window that users can access via a button and pop-up, but if they do this when the form is dirty, they get warned. I think I just need to supress the warning before I pop the window and re-activate it afterwards.

  9. There are 2 good articles that implement the same approach. ASP.NET Common Web Page Class Library - Part 2 By Eric Woodruff - Detecting changes in data controls in ASP.NET web forms http://www.codeproject.com/aspnet/EWSWebPt2.asp and Dirty Forms and The UserData Persistence Behavior with XMLDocument Property By Peter A. Bromberg, Ph.D http://www.eggheadcafe.com/articles/20010406.asp.

  10. Mac says:

    You don't necessarilly need to point the reloading button to an existing URL or refresh action. This is a bad bad bad developer who'd do that.. Instead, you can use a simple javascript function. I'd assume any form field would set the variable "isDirty" to "TRUE" upon change (using e.g. or similar).

    It would go like this:

    With this button for reload:

    function checkForm() { var warning="Do you wish to save your changes?"; if(isDirty==true) { // form has changed if(confirm(warning)) { // user wants to save document.getElementById('myForm').submit(); // send the form to save return false; // kill any following actions and quit the function } }

    [reload] // in any other case, you may call the reload routine return true; // and quit the function }

    This fuction should make sure the save would be made if user confirms that he wishes to save data.

    Calling onbeforeunload, onbeforesubmit etc. is very dangerous since their behavior varies from browser to browser and from version to version. I've experinced a situation in which, although I chose to stop the submit process, the data were sent, regardless of my choice. The onbefore* events sometimes behave weird and they DON'T necessarily interrupt the action.

    Hope this helps. ;-)

  11. Anonymous says:

    Ah. To correct my post above:

    [input onChange="isDirty=true;"] - tainting the global variable

    [input type="submit" value="reload" onClick="checkForm;"] - should be the reload button.

    I forgot that any HTML would be thrown away, so nowI had to use [] instead of "spiky brackets" ;-)

  12. Mac says:

    Ah. To correct my post above:

    [input onChange="isDirty=true;"] - tainting the global variable

    [input type="submit" value="reload" onClick="checkForm;"] - should be the reload button.

    I forgot that any HTML would be thrown away, so nowI had to use [] instead of "spiky brackets" ;-)

  13. Martijn says:

    Even nicer would be using a return value for the warnifform_dirty() function so the user is presented with an OK or CANCEL button. See http://www.4guysfromrolla.com/demos/OnBeforeUnloadDemo3.htm for a demo.

  14. Chris (another one=) says:

    Guess, its a bit late now ...

    But it seems that either of the above ways :

    window.onunload = checkForm; or window.onbeforeunload = checkForm; .... if(window.confirm("Data is unsaved - wish to Save ?")) {
    myForm.submit();
    / return false; // kill all following
    } ... does not work the way intented. i.e. the loading of the requested site is aborted correctly but the form is NOT submitted - it is reloaded - thus the changes are lost.

    Don't know why, tho

  15. willem says:

    Hello Deane, Did you find a slotion for your problem? "I found a little glitch -- onbeforeunload runs when you activate a pop-up window.

    I have a helper window that users can access via a button and pop-up, but if they do this when the form is dirty, they get warned. I think I just need to supress the warning before I pop the window and re-activate it afterwards."

    I have the same problem, but until now I have no glue how to resolve this problem. Any help will be greatly appreciated.

    Thanks in advance

  16. Robert says:

    I have developed a page that uses the onbeforeunload event handler. The big difficulty I have had is getting the function calls in the onsubmit to go through. It seems that the former handler prevents functions being run from onsubmit.

    Anybody got a solution?

  17. JasonBsteele says:

    The approach taken in the afore mentioned http://www.codeproject.com/aspnet/EWSWebPt2.asp is to use HTML attributes defaultValue, defaultChecked and defaultSelected as a way of determining whether a control is dirty.

    Even if you are not developing in ASP.NET you may wish to download this article and have a look at DataChange.js. The article and code are very well written.

  18. Robert Bredlau says:

    I just came across the same issue. I don't think it's detrimental to a web application that you can't stop the page from unloading.

    Take a hint from our advertising friends; it wasn't uncommon that when you'd close a popup 5 more would open.

    So just because we can't stop the page from unloading doesn't mean we can't use XHR to synchronously save the data.

    The solution I will probably take is:

    function unloadHandler(){ if(dirty_flag){ var prompt = "You have chosen to close the form you were working on " + "without saving your changes. Click 'OK' to save your changes " + "now and wait for a confirmation message before closing " + "your browser or visiting another page. Click 'Cancel' to " + "throw away your changes and close the form."; if(confirm(prompt)){ // Use synchronous AJAX to save the form if(AJAX.success){ alert("Your changes have been saved."); }else{ alert("An error occurred while trying to save your changes, you're data has " + "most likely been lost. Sorry, but we tried."); } } }

  19. Ron says:

    Thank%252Byou%252Bfor%252Byour%252Bsite.%252BI%252Bhave%252Bfound%252Bhere%252Bmuch%252Buseful%252Binformation...h

  20. judy says:

    Hello%252Bstupid%252Bpendosegi.

  21. Suzan says:

    i+love+this+site.

  22. geoge says:

    pls remove all my most visited sites

  23. Tom says:

    Wanted to comment on Robert Bredlau's comment - just came across this via Google search.

    "// Use synchronous AJAX to save the form if(AJAX.success)"

    There is no such thing as synchronous AJAX - the first A in AJAX stands for Asynchronous.

  24. The solar wind, a stream of charged particles, streams out and around the terrestrial magnetic field, and continues behind the magnetic tail, hundreds of Earth radii downstream. , glamis .com, [url="http://wntzrz.zowutyg.co.cc/glamis-.com.html"]glamis .com[/url], http://wntzrz.zowutyg.co.cc/glamis-.com.html glamis .com, nigkbh,

  25. So any work around found for the page refresh ? I got the windowbeforeunload to work in both IE n FF (FF doesn't show any custom message though). BUT the only frustrating problem is that it would run every time I refresh the page. Please help somebody..

Add a Comment