Introduction
In the web app world, it is quite important for an application to be sure about the integrity of the data. In the article's context, what this means is when the user modifies form data and leaves the form without saving it, the application prompts the user: 'Form is modified, do you want to save?' This is a quite common requirement for web developers. But the form might have different controls on it, and handling each control for each type of control will result in a code that is tightly coupled to the application. What if the form changes tomorrow, adding some fields or removing existing fields? It will result in additional maintenance work every time.
Background
In one of our projects, I was asked to create a user prompt when the user modifies the web form and leaves the web form without saving it, in order to save the integrity of the form data. Now, the question is, do we write code on the server side? Or on the client side? If we write code on the server side, every time the user leaves the web form, it should post back data to the server and verify whether the form data was modified or not, and based on that, it will have to create the user prompt.
The idea here is, we need to first capture the initial values when the form is loaded, also capture the final values' snapshot before the user leaves the form, and compare both the initial and final values; if there is any difference, prompt the user saying, "The form was modified, do you want to save?', else let the user go to the next page.
If we carefully observe the ASP.NET page life cycle, ViewState can do this for us. We can compare postback data with ViewState values. If there is any difference between the two, we can prompt the user on the client side saying, 'Form modified. Do you want to Save?' But this approach will have its side affects. If the web form contains AJAX UpdatePanel
s, they do partial postbacks for the actions they are responsible for. In this case, the ViewState will not be the same as the ViewState that is loaded the first time. The ViewState will get updated incrementally every time, and we will not have the option to compare the initial and final values. Because the ViewState is always updated with the current values on the form.
Now the other option would be client side scripting, using JavaScript. If we write code using JavaScript for each control on the form to compare the initial and final values, eventually it will become too much code, which will result in maintenance headaches in cases where the form fields are dynamic.
We can always browse through each form control using DOM traversing, and load them up into an initial and final dictionary, and compare the differences, which would always be a working option irrespective of whether the form has UpdatePanel
s or has partial post backs etc. But here, we need to verify, for each control, the type of the control in order to get the value of the control. So a better approach for the above is to use jQuery. jQuery selectors are good at selecting controls based on the type of controls given.
Using the Code
This sample uses Visual Studio 2008 SP1. Using the same version of Visual Studio is recommended; if you don't have the same version, download the source code and create a web application using the version you have and add the files to the project manually. The expected minimum version is Visual Studio 2005.
To explain it very simply, I took a simple employee site which contains two pages, default.aspx and Roles.aspx, and a site1.master page which holds the navigation for these two pages.
The picture below shows the code structure:
Below is the code in ItemValueDictionary.js:
function LoadCurrentValues(dictionary) {
$("input:text").each(function() {
AddElementToDictionary(dictionary,this.id, this.value);
});
$("input:radio").each(function() {
AddElementToDictionary(dictionary,this.id, this.checked);
});
$("input:checkbox").each(function() {
AddElementToDictionary(dictionary,this.id, this.checked);
});
$("select").each(function() {
AddElementToDictionary(dictionary,this.id, $("#" + this.id).val());
}
)
}
function CompareDictionaries(InitialDictionary) {
CurrentDictionary = new Array();
LoadCurrentValues();
if (InitialDictionary.length != CurrentDictionary.length) {
return false;
}
else {
for(i=0;i<InitialDictionary.length;i++)
{
if((InitialDictionary[i].name==CurrentDictionary[i].name) &&
(InitialDictionary[i].value != CurrentDictionary[i].value))
{
return false;
}
}
return true;
}
}
In the above code, LoadCurrentValues(dictionary)
loads the snapshot of each control when the method is called, and the dictionary will have the loaded values. After the page DOM is rendered, the LoadCurrentValues(dictionary)
method will be called to load the initial values of the controls. It will prepare a dictionary of IDs and values, with the initial values.
Similarly, LoadCurrentValues(InitialDictionary)
will load the current values when the user leaves the form page, and prepares a dictionary of IDs and values, with the current values. The CompareDictionaries()
method compares the initial dictionary and the current dictionary. If they are different, then it will return false
, else it returns true
.
The code below is the place where the LoadInitialValues()
method will be called. This is the master page for both of the pages.
$().ready(function() {
InitialDictionary = new Array();
LoadCurrentValues(InitialDictionary);
$("a[id*=hl]").click(function() {
if (!CompareDictionaries(InitialDictionary)) {
return confirm('Form is modified, Do you want to continue');
}
}
)
}
)
Points of Interest
Using jQuery to solve the above problem is good in terms of the amount of code that we have written. Also, if we change the form in future, like add a few textboxes or checkboxes, the preset code should automatically pick up those controls with no code changes required.
Disclaimer
The sample provided in this article is just to give the reader an idea of how to approach the above said problem. It might be necessary to tweak the solution to your needs. This sample can not be used for direct production deployment.