• Home
  • New Entries
  • Popular Entries
  • Submit a Story
  • About

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:
          o Any other controls on the page
          o Back button
          o Refresh button
          o Typing another address in the address bar
          o Picking another page from history
          o Closing the web browser
          o 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:

001 <script type="text/javascript">
002 <!--
003 
004     // Function to maintain original default states for all fields.
005     //  In order to test for dirtiness, we will be checking if the default
006     //  value for each control matches its current value. However, this
007     //  default is not normally preserved across partial postbacks. We need to
008     //  preserve these values ourselves.
009     function keepDefaults(form) {
010 
011         // Get a reference to the form (in ASP.Net there should only be the one).
012         var form = document.forms[0];
013 
014         // If no original values are yet preserved...
015         if (typeof (document.originalValues) == "undefined") {
016 
017             // Create somewhere to store the values.
018             document.originalValues = new Array();
019 
020         }
021 
022         // For each of the fields on the page...
023         for (var i = 0; i < form.elements.length; i++) {
024 
025             // Get a ref to the field.
026             var field = form.elements[i];
027 
028             // Depending on the type of the field...
029             switch (field.type) {
030 
031                 // For simple value elements...    
032                 case "text":
033                 case "file":
034                 case "password":
035                 case "textarea":
036 
037                     // If we don yet know the original value...
038                     if (typeof (document.originalValues[field.id]) == "undefined") {
039 
040                         // Save it for later.
041                         document.originalValues[field.id] = field.value;
042 
043                     }
044                     break;
045 
046                 // For checkable elements...    
047                 case "checkbox":
048                 case "radio":
049 
050                     // If we don yet know the original check state...
051                     if (typeof (document.originalValues[field.id]) == "undefined") {
052 
053                         // Save it for later.
054                         document.originalValues[field.id] = field.checked;
055 
056                     }
057                     break;
058 
059                 // For selection elements...    
060                 case "select-multiple":
061                 case "select-one":
062 
063                     // The form is dirty if the selection has changed.
064 
065                     // For each of the options...
066                     var options = field.options;
067                     for (var j = 0; j < options.length; j++) {
068 
069                         var optId = field.id + "_" + j;
070 
071                         // If we don yet know the original selection state...
072                         if (typeof (document.originalValues[optId]) == "undefined") {
073 
074                             // Save it for later.
075                             document.originalValues[optId] = options[j].selected;
076 
077                         }
078                     }
079                     break;
080             }
081 
082         }
083     }
084 
085     // Call function to preserve defaults every time the page is loaded (or is
086     // posted back).
087     Sys.Application.add_load(keepDefaults);
088 
089     // Assume that a check for dirtiness is required.
090     //  If this value is still true, we will check for dirtiness when the page
091     //  unloads.
092     var dirtyCheckNeeded = true;
093 
094     // Function to flag that a check for dirtiness is not required.
095     //  Called by Save and Cancel buttons to indicate that a dirty check is
096     //  not actually required.
097     function ignoreDirty() {
098         dirtyCheckNeeded = false;
099     }
100 
101     // Function to check if the page is dirty.
102     //  The function compares the default value for the control (the one it
103     //  was given when the page loaded) with its current value.
104     function isDirty(form) {
105 
106         // For each of the fields on the page...
107         for (var i = 0; i < form.elements.length; i++) {
108             var field = form.elements[i];
109 
110             // Depending on the type of the field...
111             switch (field.type) {
112 
113                 // For simple value elements...    
114                 case "text":
115                 case "file":
116                 case "password":
117                 case "textarea":
118 
119                     // The form is dirty if the value has changed.
120                     if (field.value != document.originalValues[field.id]) {
121                         // Uncomment the next line for debugging.
122                         //alert(field.type + + field.id + + field.value + + document.originalValues[field.id]);
123                         return true;
124                     }
125                     break;
126 
127                 // For checkable elements...    
128                 case "checkbox":
129                 case "radio":
130 
131                     // The form is dirty if the check has changed.
132                     if (field.checked != document.originalValues[field.id]) {
133                         // Uncomment the next line for debugging.
134                         //alert(field.type + + field.id + + field.checked + + document.originalValues[field.id]);
135                         return true;
136                     }
137                     break;
138 
139                 // For selection elements...   
140                 case "select-multiple":
141                 case "select-one":
142 
143                     // The form is dirty if the selection has changed.
144                     var options = field.options;
145                     for (var j = 0; j < options.length; j++) {
146                         var optId = field.id + "_" + j;
147                         if (options[j].selected != document.originalValues[optId]) {
148                        
149                             // Uncomment the next line for debugging.
150                             //alert(field.type + + field.id + + options[j].text + + options[j].selected + + document.originalValues[optId]);
151                             return true;
152                         }
153                     }
154                     break;
155             }
156         }
157 
158         // The form is not dirty.
159         return false;
160     }
161 
162     // Clicking on some controls in (at least) IE6 caused the onbeforeunload
163     // to fire *twice*. We use this flag to check for this condition.
164     var onBeforeUnloadFired = false;
165 
166     // Function to reset the above flag.
167     function resetOnBeforeUnloadFired() {
168         onBeforeUnloadFired = false;
169     }
170 
171     // Handle the beforeunload event of the page.
172     //  This will be called when the user navigates away from the page using
173     //  controls on the page or browser navigation (back, refresh, history,
174     //  close etc.). It is not called for partial post-backs.
175     function doBeforeUnload() {
176 
177         // If this function has not been run before...
178         if (!onBeforeUnloadFired) {
179        
180             // Prevent this function from being run twice in succession.
181             onBeforeUnloadFired = true;
182 
183             // If the dirty check is required...
184             if (dirtyCheckNeeded) {
185 
186                 // If the form is dirty...
187                 if (isDirty(document.forms[0])) {
188 
189                     // Ask the user if she is sure she wants to continue.
190                     event.returnValue = "If you continue you will lose any changes that you have made to this record.";
191 
192                 }
193             }
194         }
195        
196         // If the user clicks cancel, allow the onbeforeunload function to run again.
197         window.setTimeout("resetOnBeforeUnloadFired()", 1000);
198     }
199 
200     // Hook the beforeunload event of the page.
201     //  Call the dirty check when the page unloads.
202     if (window.body) {
203         // IE
204         window.body.onbeforeunload = doBeforeUnload;
205     }
206     else
207         // FX
208         window.onbeforeunload = doBeforeUnload;
209 
210     // -->
211 </script>

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

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

 Original Source:
http://allwrong.wordpress.com/2009/12/11/preventing-users-navigating-away-from-an-ajax-net-page-without-saving-their-changes/

AddThis Social Bookmark Button

Posted at 01:11:09 pm | Permalink | Posted in .Net  

Related Stuff

  • MooV: Using cutting edge Video phones and Software Video Phones - coupling all that with VoIP and empowering the disabled.

  • Moo Telecom: VoIP communications made easy - Ring anyway with the fun and ease of using a normal phone

  • TagR:Mobile Social Network with Real Time Locations Based services, and Ambience Intelligence, VoiP, IM, Skype, Googletalk, Mapping, Flickr, Events, Calendaring, Scheduling, SecondLife Support

  • ClearSMS : ClearSMS is a Web-based application that lets you send bulk SMS messages to your customers, contacts, or just about anyone.

  • Jajah:jah is a VoIP (Voice over IP) provider, founded by Austrians Roman Scharf and Daniel Mattes in 2005[1]. The Jajah headquarters are located in Mountain View, CA, USA, and Luxembourg. Jajah maintains a development centre in Israel.

  • Skype: It’s free to download and free to call other people on Skype. Skype the number one voice over ip software

  • PrivatePhone: a free local phone number with voicemail and messages you can check online or from any phone.

Top Stuff

e-messenger

MSN Web Messenger

eBuddy

ASP.NET Ajax CalendarExtender and Validation

AIM Express

Ajax Tools for ASP.NET Developers



About Ajaxlines

Ajaxlines is a project focused on providing its audience with a database of most of Ajax related articles, resources, tutorials and services from around the world.

Its purpose is to showcase the power of Ajax and to act as a portal to the Ajax development community.


Search


Topics

  • .Net (176)
  • Ajax (112)
  • Ajax Games (10)
  • Articles (95)
  • Bookmarking (35)
  • Calendar (21)
  • Chat (45)
  • ColdFusion (3)
  • CSS (84)
  • Email (23)
  • Facebook (84)
  • Flash (20)
  • Google (54)
  • Html (29)
  • Image (12)
  • International Calls & VOIP (7)
  • Java (58)
  • Javascript (280)
  • jQuery (200)
  • JSON (75)
  • Perl (2)
  • PHP (172)
  • Presentation (19)
  • Python (3)
  • Resources (2)
  • RSS (8)
  • Ruby (32)
  • Storage (4)
  • Toolkits (103)
  • Tutorials (227)
  • UI (11)
  • Utilities (174)
  • Web2.0 (18)
  • XmlHttpRequest (29)
  • YUI (13)

© 2006 www.ajaxlines.com. All Rights Reserved. Powered by IRange