[This originally appeared on my dev2dev blog 29-Jan-2008]
Here is the proper way to warn users so they don’t loose changes by accidentally navigating away from a form.
The problem:
The user makes some changes on a form and then does something to navigate away from the form (before submitting it). This could be clicking on some navigation link, clicking the browser back button or even closing the browser tab or window. You would like to let the user know that they have unsaved changes that they will loose if they continue navigating away. This is a helpful thing to do and something that desktop apps have been doing for a long time.
For a web app to warn the user of unsaved changes requires knowing when the user is leaving the page, prompting the user and either saving the changes or stopping the user from leaving the page.
The onunload event would seem to provide the first part of the solution. One problem however, is that there is no way to stop the onunload from happening. This is a good thing. Being able to cancel the navigation during the onunload event would allow for abuse – scripts could take control from the user. The user must have control over browser navigation. Even without being able to cancel the onunload there is still opportunity for abuse, for example opening a new window. Most popup blockers have gotten wise to these shenanigans. Internet Explorer still allows some questionable activities from the onunload handler. For example it is possible in IE to keep the user from leaving a page by setting document.location.href to the current page in document.body.onunload.
WLS Console was prompting the user to save changes and saving them by exploiting one of the loop holes in IE. Console would prompt the user in response to the onunload event on document.body. If the user responded that they wanted to save the changes the script would submit the form. This does not work in Firefox and that started my quest to find the proper way to warn the user of unsaved changes.
The solution:
The onbeforeunload event appears to have been specifically designed to solve exactly this problem. It achieves the correct balance between allowing a script to stop the navigation so the user can save their changes and ensuring that the user is in control at all times. Here is how it works. The onbeforeunload event handler decides if there is a need to warn the user before leaving a page – if there is it returns a message string. The browser is responsible for prompting the user. The browser dialog has a fixed title, message and buttons. The string returned by the handler is added to the dialog to provide context specific information. In this way the browser makes sure that the user stays in control of navigation but the web page can let the browser know if the user should be warned before leaving the page.
Some details:
The onbeforeunload event is not a w3c standard event. It was first added to IE version 4. It is now supported by Firefox and Safari. It looks like it will also be part of HTML 5. IE supports this event on the window object and document.body. The other browsers only support it on the window object.
The Microsoft documentation for onbeforeunload says to assign the message to event.returnValue. This does not work in Safari 3 beta on Windows but simply returning the string works in IE, Safari and Firefox.
IE 6 has a bug where the prompt is shown to the user twice. I don’t know of any workaround for this so I think we just have to live with it.
Unfortunately, Opera as of version 9.25 does not support the onbeforeunload event. I hope Opera gets on board with this useful event soon.
Here is the essence of the code needed:
window.onbeforeunload = function() { if (!isSubmitting && isDirty()) { return warnOnSaveMessage; } }
Variable isSubmitting is a Boolean flag set to true when a form is being submitted so that the user doesn’t get the warning when saving their changes. Function isDirty does the work to determine if changes have been made to the form. There are many ways to implement this function and I won’t go into details here. You can have an explicit dirty flag set from event handlers or iterate over the controls in a form comparing the current and default values. Often a mix of the two methods is needed.