Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes

On ASP.Net / AJAX recent project I was asked to ensure that users cannot navigate away from a web form if they have made changes that have not yet been submitted.

Requirements

The requirements were:

  • Enable users to submit the form by clicking the “Save” or “Cancel” buttons.
  • When user navigates away using any other method, show a dialog that states that changes will be lost if the user continues. These methods to include:
    • Any other controls on the page
    • Back button
    • Refresh button
    • Typing another address in the address bar
    • Picking another page from history
    • Closing the web browser
    • Anything else
  • Only show the dialog if the form is “dirty” (ie. there are unsaved changes”
  • Work with IE6 (I know, I know).
  • The solution should work client-side, so must be written in JavaScript.

Note that his solution has not been tested with other browsers.

Preventing Users Navigating

When a user attempts to navigate using any of the methods above, the window.onbeforeunload event will fire. We can use this event to pop-up a dialog that warns the user that she has not submitted her changes, and asks her to confirm that she wants to continue.

Checking for Dirt

In order to check for dirtiness, we must compare the original value of all the controls on the page with their value when they are submitted. A number of sources suggest that the browser maintains the default value for controls, and that we can compare the current value with that default value in the onbeforeunload event handler. However, there are several issues with this approach:

  • The default value is generally reset following an AJAX postback. This means that we must preserve the control’s original values of the controls when they first load, so gain nothing from using the browser’s method for tracking value changes.
  • In IE6, the defaultSelected property of HTML OPTION controls is, by default, set to true for all items in a drop-down list. The ASP.Net list control appears to set this value explicitly, I didn’t want to have to do so on the equivalent HTML control. As a result of the default, however, it is often impossible to determine if the user has unselected an item or if they have simply not selected the item. As a result it is best to track changes to the selection of options ourselves, rather than using the browser’s faulty tracking mechanism.

Allowing Certain Controls to Submit without Warning

It is important that some controls are able to submit the form without the using being warned that changes have been made. Typically, these controls include a button that allows the user to save her changes, and possibly one to exit without saving.

In order to implement these, we set a flag that indicates that a dirtiness check is required, then use the client-side click event of the buttons to reset the flag so that the check isn’t performed when these buttons are clicked.

OnBeforeUnload Sometimes Fires Twice

There is a bug in IE6 that means that, under some circumstances, the onbeforeunload event fires twice. We don’t want the confirm dialog to be displayed twice when the user attempts to leave a page, so we need to take measures. It is easy to set a flag the first time the onbeforeunload event fires that indicates that we can ignore it the second time. The slight complication is that the user may click “cancel” to prevent the navigation when the dialog first appears. If that happens, we don’t want to suppress the confirmation dialog for subsequent attempts to navigate away from the page. The solution is to start a timer that will reset the flag if the user remains on the page, thus allowing the dialog to appear when the user attempts to navigate again.

The Code

Here’s the JavaScript that I use:


<script type="text/javascript">
<!--

// Function to maintain original default states for all fields.
//  In order to test for dirtiness, we will be checking if the default
//  value for each control matches its current value. However, this
//  default is not normally preserved across partial postbacks. We need to
//  preserve these values ourselves.
function keepDefaults(form) {

// Get a reference to the form (in ASP.Net there should only be the one).
var form = document.forms[0];

// If no original values are yet preserved...
if (typeof (document.originalValues) == "undefined") {

// Create somewhere to store the values.
document.originalValues = new Array();

}

// For each of the fields on the page...
for (var i = 0; i < form.elements.length; i++) {

// Get a ref to the field.
var field = form.elements[i];

// Depending on the type of the field...
switch (field.type) {

// For simple value elements...
case "text":
case "file":
case "password":
case "textarea":

// If we don't yet know the original value...
if (typeof (document.originalValues[field.id]) == "undefined") {

// Save it for later.
document.originalValues[field.id] = field.value;

}
break;

// For checkable elements...
case "checkbox":
case "radio":

// If we don't yet know the original check state...
if (typeof (document.originalValues[field.id]) == "undefined") {

// Save it for later.
document.originalValues[field.id] = field.checked;

}
break;

// For selection elements...
case "select-multiple":
case "select-one":

// The form is dirty if the selection has changed.

// For each of the options...
var options = field.options;
for (var j = 0; j < options.length; j++) {

var optId = field.id + "_" + j;

// If we don't yet know the original selection state...
if (typeof (document.originalValues[optId]) == "undefined") {

// Save it for later.
document.originalValues[optId] = options[j].selected;

}
}
break;
}

}
}

// Call function to preserve defaults every time the page is loaded (or is
// posted back).
Sys.Application.add_load(keepDefaults);

// Assume that a check for dirtiness is required.
//  If this value is still true, we will check for dirtiness when the page
//  unloads.
var dirtyCheckNeeded = true;

// Function to flag that a check for dirtiness is not required.
//  Called by Save and Cancel buttons to indicate that a dirty check is
//  not actually required.
function ignoreDirty() {
dirtyCheckNeeded = false;
}

// Function to check if the page is dirty.
//  The function compares the default value for the control (the one it
//  was given when the page loaded) with its current value.
function isDirty(form) {

// For each of the fields on the page...
for (var i = 0; i < form.elements.length; i++) {
var field = form.elements[i];

// Depending on the type of the field...
switch (field.type) {

// For simple value elements...
case "text":
case "file":
case "password":
case "textarea":

// The form is dirty if the value has changed.
if (field.value != document.originalValues[field.id]) {
// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + field.value + ' ' + document.originalValues[field.id]);
return true;
}
break;

// For checkable elements...
case "checkbox":
case "radio":

// The form is dirty if the check has changed.
if (field.checked != document.originalValues[field.id]) {
// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + field.checked + ' ' + document.originalValues[field.id]);
return true;
}
break;

// For selection elements...
case "select-multiple":
case "select-one":

// The form is dirty if the selection has changed.
var options = field.options;
for (var j = 0; j < options.length; j++) {
var optId = field.id + "_" + j;
if (options[j].selected != document.originalValues[optId]) {

// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + options[j].text + ' ' + options[j].selected + ' ' + document.originalValues[optId]);
return true;
}
}
break;
}
}

// The form is not dirty.
return false;
}

// Clicking on some controls in (at least) IE6 caused the onbeforeunload
// to fire *twice*. We use this flag to check for this condition.
var onBeforeUnloadFired = false;

// Function to reset the above flag.
function resetOnBeforeUnloadFired() {
onBeforeUnloadFired = false;
}

// Handle the beforeunload event of the page.
//  This will be called when the user navigates away from the page using
//  controls on the page or browser navigation (back, refresh, history,
//  close etc.). It is not called for partial post-backs.
function doBeforeUnload() {

// If this function has not been run before...
if (!onBeforeUnloadFired) {

// Prevent this function from being run twice in succession.
onBeforeUnloadFired = true;

// If the dirty check is required...
if (dirtyCheckNeeded) {

// If the form is dirty...
if (isDirty(document.forms[0])) {

// Ask the user if she is sure she wants to continue.
event.returnValue = "If you continue you will lose any changes that you have made to this record.";

}
}
}

// If the user clicks cancel, allow the onbeforeunload function to run again.
window.setTimeout("resetOnBeforeUnloadFired()", 1000);
}

// Hook the beforeunload event of the page.
//  Call the dirty check when the page unloads.
if (window.body) {
// IE
window.body.onbeforeunload = doBeforeUnload;
}
else
// FX
window.onbeforeunload = doBeforeUnload;

// -->
</script>

The source code for my Save and Cancel buttons looks like this:


<asp:Button ID="btnEdit" runat="server" Text="Save" OnClick="btnEdit_Click" OnClientClick="ignoreDirty();" />
<asp:Button ID="btnCancel" runat="server" Text="Cancel" CssClass="btn btnCancel" OnClick="btnCancel_Click" OnClientClick="ignoreDirty();" CausesValidation="false" />

References

I looked at a range of solutions before developing the one I present here, including:

Advertisements

40 Responses to Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes

  1. Amanda Myer says:

    This is really nice, however I am running into a problem. Whenever I pull up a page with this code, I get the error “Sys is undefined” and the debugger points me to the line in the code that says “Sys.Application.add_load(keepDefaults);”

    Any ideas on how to resolved this? All my google searches keep talking about an old issue folks used to have with Ajax enabled sites and making changes to the web.config. But I have ajax enabled throughout all my pages and I don’t get this error unless the page is calling this javascript.

  2. Bora Inceler says:

    You need to use Ajax Web Page in Visual Studio.

  3. Bob M says:

    I need to do something like this. In my case the isDirty stuff isn’t important. The problem is that I have a TreeView that is used for selections which are applyed to my database. I can get this to work with all the buttons using the onClientClick event, but any click on the TreeView causes that navigate message and I can find no way to stop it. I’ve tried using ListBoxes instead of the TreeView but I still get the message when the onSelectedItemChage event is posted back. Any solutions?

  4. Rony C says:

    But this does not work in FF.
    FF cannot handle “event.returnValue = “.
    You can change this by passing the event as parameter:

    function doBeforeUnload(e){
    // check for the event parameter
    if (!e)
    {
    e = window.event;
    }
    ….
    further in the code change the following:

    e.returnValue = “If you continue you will lose any changes that you have made”;

    This works in IE and FF.
    See also at: http://stackoverflow.
    com/questions/553240/event-is-not-defined-in-mozilla-firefox-for-javascript-func
    tion

    Also, I’ve used this in my application and noticed that the check :
    if (window.body) {

    does not works like you think it works.
    I only used :
    window.onbeforeunload = doBeforeUnload;

    This works fine for me in IE and FF. So the check is not needed.
    But I have to say that I use IE7.

  5. Vicki says:

    How can I check for the isDirty when a user navigates between tabs within a tab control?

    Thanks is advance.

  6. Akshit says:

    i get error sys is undefined at below line :
    Sys.Application.add_load(keepDefaults);

  7. Rockstar says:

    var allowPrompt = true;

    window.onbeforeunload = WarnUser;

    function WarnUser()

    {

    if(allowPrompt)

    {

    return “Type Your Message here”;

    }

    else

    {

    allowPrompt = false;

    }

    }

    function NoPrompt()

    {

    allowPrompt = false;

    }

    For not showing msg twice on a link.

  8. Rockstar says:

    This is works 100% for me. hope it works for you.

  9. canada goose says:

    supérieure. Typiquement, la veste est plus courte que répondre. La longueur de la veste est exactement là où votre poignet atteint quand vous vous tenez la posture normale. Voici quelques conseils pour déterminer la qualité supérieure d’une veste à partir de son appearance.

  10. et plus nettes émission pousser à se répercussions cabinet extraordinaire adoptée par l’avant et plus bas, instantanément marquant le gardiennage et la sécurité de vos pieds et les orteils sur le whole.

  11. set up your own minecraft sever today…

    […]Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes « All Wrong[…]…

  12. Tom says:

    This is just what I needed thanks! The only thing left is to get this to work with a Telerik RadEditor control. In the isDirty() method, it is being returned as a textarea but no changes are being refected. Any ideas???

  13. cpa beyond says:

    Hello There. I found your blog using msn. This
    is a really well written article. I will make sure to bookmark it and return to read more of your useful
    information. Thanks for the post. I will certainly return.

  14. 他のフランスのフ
    ァッションハウスは、完全に紙の上で衣装をデザインしたが、ココは、彼女が雇ったライブモデ
    ルまたは ‘マネキン’に直
    接彼女のスタイルを作成しました

  15. Hello there! I recently desire to offer a massive browses upwards
    for that great details you have below about this submit.
    I will likely to end up coming back in your website for
    more soon.

  16. So it is very essential one. 3 It is also important if our lives are sedentary or active, if
    weight loss pills for men you don’t find time for jogging or swimming. The effects of green tea contains more fluoride than other types of bioactive compounds in Nuphedragen to help you lose weight. It was created to give a false sense of weight loss supplements online pertains to the convenience that this method provides.

  17. Marlene says:

    Pretty! This was an extremely wonderful post.
    Many thanks for providing this information.

  18. bez biku says:

    After looking over a few of the blog articles on your web site, I truly appreciate your way of writing a
    blog. I book-marked it to my bookmark webpage
    list and will be checking back in the near future. Please check out my website too and tell me what you think.

  19. Hey just wanted to give you a brief heads up and let you know a
    few of the pictures aren’t loading correctly. I’m not sure
    why but I think its a linking issue. I’ve tried
    it in two different internet browsers and both show the same results.

  20. My relatives all the time say that I am wasting my time here at net, however I know I am getting familiarity
    every day by reading such good articles or reviews.

  21. drama method says:

    My brother suggested I might like this blog. He was once entirely right.

    This submit actually made my day. You cann’t imagine just how much time I had spent for
    this information! Thank you!

  22. Issac says:

    Hey there! I’m at work browsing your blog from my new
    apple iphone! Just wanted to say I love reading through your blog and
    look forward to all your posts! Carry on the fantastic work!

  23. Kindra says:

    I just couldn’t depart your web site before suggesting that
    I really loved the standard info an individual provide to your visitors?
    Is going to be again often in order to inspect new posts

  24. Now I am going away to do my breakfast, afterward having
    my breakfast coming yet again to read further news.

  25. Thanks for the marvelous posting! I certainly enjoyed reading it, you will be a great author.I will
    make certain to bookmark your blog and will come back at some point.
    I want to encourage you to ultimately continue your great job, have a nice morning!

  26. Hey there, You have done an incredible job. I’ll definitely digg
    it and personally recommend to my friends. I am confident they’ll be benefited from this
    web site.

  27. Undeniably consider that that you said. Your favorite reason appeared to be at the web the simplest thing to have in mind of.
    I say to you, I definitely get annoyed at the same time as
    folks think about worries that they plainly don’t understand about.

    You controlled to hit the nail upon the top and also defined out
    the entire thing with no need side effect , folks can take a signal.
    Will likely be again to get more. Thank you

  28. low carb says:

    Hello I am so delighted I found your webpage, I really found you by mistake, while I was searching on Yahoo for something else,
    Regardless I am here now and would just like to say
    kudos for a remarkable post and a all round enjoyable blog (I also love the theme/design), I don’t have time
    to browse it all at the moment but I have bookmarked it and also added in your RSS feeds, so when I have time I will be back to read much more, Please do keep up the great jo.

  29. What i don’t understood is in reality how you are not really much more well-favored than you may be now.
    You’re so intelligent. You understand therefore significantly relating to this matter, produced me for my part consider it from numerous various angles.
    Its like women and men don’t seem to be involved except it is one thing
    to do with Lady gaga! Your personal stuffs great.
    At all times maintain it up!

  30. Hey, I think your website might be having browser compatibility issues.
    When I look att your blog site in Chrome, it looks
    fin but when opening in Internet Explorer, iit has some overlapping.
    I just wanted to give you a quick heaads up! Other then that, very good blog!

  31. I used to be suggested this blog by way of my cousin. I’m now not
    sure whether or not this submit is written via him as
    no one else know such distinct approximately my trouble.

    You are incredible! Thank you!

  32. Hello everyone, it’s my first pay a quick visit at this web page, and
    piece of writing is really fruitful in support of me, keep up posting these
    types of posts.

  33. Online Paraphrase Service

    Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes | All Wrong

  34. Hello, i believe that i noticed you visited my site so i came to return the desire?.I’m trying
    to to find things to enhance my web site!I suppose its adequate to use some of
    your ideas!!

  35. This site certainly has all of the information I needed about this
    subject and didn’t know who to ask.

  36. Thanks for some other informative website. The place else may just I get that
    kind of info written in such a perfect method? I have a venture that I’m just
    now running on, and I’ve been on the look out for
    such info.

  37. daniel says:

    You actually make it seem really easy together with your presentation however I in
    finding this topic to be really one thing which I feel I would never understand.
    It kind of feels too complex and extremely huge for me. I’m
    having a look forward on your next put up, I
    will attempt to get the cling of it!

  38. commercial carpet cleaning minneapolis

    Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes | All Wrong

  39. Hey there I am so delighted I found your website,
    I really found you by error, while I was researching on Bing for something else,
    Anyways I am here now and would just like to say kudos for a marvelous post and
    a all round exciting blog (I also love the theme/design), I don’t have time to go through
    it all at the moment but I have saved it and also added your RSS feeds, so when I have time I will be back to read a lot more, Please
    do keep up the great b.

  40. I am really thankful to the holder of this web site who has
    shared this impressive article at at this place.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: