|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Table of contentsIntroductionThis is the second in a series of articles on a class library for ASP.NET applications that I have developed. It contains a set of common, reusable page classes that can be utilized in web applications as-is to provide a consistent look, feel, and set of features. New classes can also be derived from them to extend their capabilities. The features are all fairly modular, and may be extracted and placed into your own classes too. For a complete list of articles in the series along with a demonstration application and the code for the classes, see Part 1 [^]. This article describes the data change checking features of the Data change checkingIn a rich-client application, it is fairly easy to detect changes in data entry controls and warn the user that they are about to lose their changes when they attempt to leave the form. You also have full control over how they can leave the form or the application. In a web-based application, it is more difficult. You have some chance of catching the attempt to leave if they click a link or button on your page, but they may also leave by entering a new URL in the browser's address text box, they may navigate away by using the back, forward, or history links, or they may just close the browser. It can be quite irritating for the end-user to navigate away from the page only to realize that they did not save their changes first. Telling them "Then don't do that" just makes them cranky. The
How it worksThe
Change checking is usually enabled in the private void Page_Load(object sender, System.EventArgs e)
{
if(Page.IsPostBack == false)
{
// Set up form for data change checking when
// first loaded.
this.CheckForDataChanges = true;
this.BypassPromptIds =
new string[] { "btnSave", "btnCancel",
"chkLimitToTeam" }
}
}
If specifying the IDs of such controls as the sort links in data grid headers, you will need to run the page, view the source, and get the names of the link controls from the rendered HTML. Be sure to change the '$' characters in the names to ':' when specifying them in the bypass list, as the Changes in the data controls can only be detected from the point at which the user starts interacting with the page up to the point at which a postback occurs. If you have controls on the page that cause a postback, such as a button that alters the state of some controls but it does not actually save changes made up to that point, you may need to make use of the Generating the client-side codeThe rendering of the client-side variables and script occurs in the overridden protected override void OnPreRender(EventArgs e)
{
StringBuilder sb;
string[] idList;
base.OnPreRender(e);
// If data change checking has been requested, output the
// dirty flag, exclusion arrays, confirm message, and the
// script.
if(this.CheckForDataChanges == true)
{
// Register a hidden field so that the client can pass
// back changes to the Dirty flag.
this.RegisterHiddenField("BP_bIsDirty",
this.Dirty.ToString(
CultureInfo.InvariantCulture).ToLower(
CultureInfo.InvariantCulture));
// Register an OnSubmit function call so that we can
// get the state of the Dirty flag and put it in the
// hidden field. It can't occur in the OnBeforeUnload
// event as everything has been packaged up ready for
// sending to the server and changes made in that event
// don't get sent to the server.
this.RegisterOnSubmitStatement("BP_DirtyCheck",
"BP_funCheckForChanges(true);");
// Create a script block containing the array
// declarations, the data loss message variable, the
// dirty flag, and the change checking script.
sb = new StringBuilder(
"<script type='text/javascript'>\n<!--\n" +
"var BP_arrBypassList = new Array(", 4096);
idList = this.BypassPromptIds;
if(idList != null)
{
sb.Append('\"');
sb.Append(String.Join("\",\"", idList));
sb.Append('\"');
}
sb.Append(");\nvar BP_arrSkipList = new Array(");
idList = this.SkipDataCheckIds;
if(idList != null)
{
sb.Append('\"');
sb.Append(String.Join("\",\"", idList));
sb.Append('\"');
}
sb.Append(");\nvar BP_strDataLossMsg = \"");
sb.Append(this.ConfirmLeaveMessage);
sb.Append("\";\nvar BP_strFormName = \"");
// BP_strFormName tells the script what form to use
// for the change checking.
sb.Append(this.PageForm.UniqueID);
sb.Append("\";\n//-->\n</script>\n");
// Add the reference to retrieve the script from the
// resource server handler.
sb.Append("<script type='text/javascript' src='");
sb.Append(ResSrvHandler.ResSrvHandlerPageName);
sb.Append("?Res=DataChange.js'></script>");
this.RegisterClientScriptBlock("BP_DCCJS",
sb.ToString());
}
...
}
The protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// Retrieve the state of the Dirty flag (if used)
this.Dirty = Convert.ToBoolean(Request.Form["BP_bIsDirty"],
CultureInfo.InvariantCulture);
...
}
The JavaScript codeThe JavaScript code is what makes it all work on the client side. When rendered, it consists of a hidden field that is set to the current value of the The change checking function is also registered as part of the form's // Replace the OnSubmit event. This is so that we can always
// update the state of the Dirty flag even when controls with
// AutoPostBack cause the submit.
document.forms
When the user tries to leave the page, the change checking function is called and the following steps occur: function BP_funCheckForChanges(bInOnSubmit)
{
var strID, nIdx, nNumOpts, nSkipCnt, nPos, nOptIdx;
var oElem, oOptions, oOpt, nDefSelIdx, nSelIdx;
var oForm = document.forms
The first part initializes several variables for the function. The // IE Only: The event target is most likely the item that
// caused the request to leave the page. If it's in the list
// of controls that can bypass the check, don't prompt. The
// control ID must be an exact match or must end with the name
// (i.e. it's in a DataGrid).
strID = "";
oElem = document.getElementById("__EVENTTARGET");
if(oElem == null || typeof(oElem) == "undefined" ||
oElem.value == "")
{
// Check the active element if there is no event target
if(typeof(document.activeElement) != "undefined")
{
oElem = document.activeElement
strID = oElem.id;
}
}
else
strID = oElem.value;
// Some elements may not have an ID but their parent element
// might so grab that if possible (i.e. AREA elements in a MAP
// element).
if(strID == "" && oElem != null &&
typeof(oElem) != "undefined")
{
// Link buttons in DataGrids don't have IDs but do use
// __doPostBack(). If we see a link with that in its href,
// assume __doPostBack() is running and skip the check.
// The submission will call us again.
if(oElem.tagName == "A" &&
oElem.href.indexOf("__doPostBack") != -1)
return;
if(typeof(oElem.parentElement) != "undefined")
strID = oElem.parentElement.id;
}
if(strID != "")
{
nSkipCnt = BP_arrBypassList.length;
for(nIdx = 0; nIdx < nSkipCnt; nIdx++)
if(strID == BP_arrBypassList[nIdx])
bPrompt = false;
else
{
nPos = strID.length - BP_arrBypassList[nIdx].length;
if(nPos >= 0)
if(strID.substr(nPos) == BP_arrBypassList[nIdx])
bPrompt = false;
}
}
For Internet Explorer only, the event target element (the one that caused the postback) is checked to see if its control ID is in the bypass list. If so, the // Now we'll figure out if something changed
nSkipCnt = BP_arrSkipList.length;
for(nIdx = 0; !bChanged && nIdx < nElemCnt; nIdx++)
{
oElem = oForm.elements[nIdx];
// If the control is in the list of ones to ignore,
// carry on.
for(nOptIdx = 0; nOptIdx < nSkipCnt; nOptIdx++)
{
if(oElem.id == BP_arrSkipList[nOptIdx])
break;
nPos = oElem.id.length - BP_arrSkipList[nOptIdx].length;
if(nPos >= 0)
if(oElem.id.substr(nPos) == BP_arrSkipList[nOptIdx])
break;
}
if(nOptIdx < nSkipCnt)
continue;
// Check for changes based on the control type
if(oElem.type == "text" || oElem.tagName == "TEXTAREA")
{
if(oElem.value != oElem.defaultValue)
bChanged = true;
}
else
if(oElem.type == "checkbox" || oElem.type == "radio")
{
if(oElem.checked != oElem.defaultChecked)
bChanged = true;
}
else
if(oElem.tagName == "SELECT")
{
oOptions = oElem.options;
nNumOpts = oOptions.length;
nDefSelIdx = nSelIdx = 0;
// Search for a change in the default. If
// nothing is explicitly marked as the default,
// element zero is assumed to have been the
// default.
for(nOptIdx = 0; nOptIdx < nNumOpts; nOptIdx++)
{
oOpt = oOptions[nOptIdx];
if(oOpt.defaultSelected)
nDefSelIdx = nOptIdx;
if(oOpt.selected)
nSelIdx = nOptIdx;
}
if(nDefSelIdx != nSelIdx)
bChanged = true;
}
}
Next, each control is checked to see if the data it contains has been changed. If the control ID is in the list of elements to skip, it will be ignored. This allows you to have controls on the page that can be modified without causing it to prompt to save changes (i.e. message text areas etc.). As with the bypass list, the control ID can be an exact match or one ending in the specified ID. Changes are detected based on the control type. For text boxes and text areas, the if(bChanged)
{
// Pass the dirty state back to the server
ctlDirty.value = "true";
// If prompting, set the message
if(bPrompt)
{
event.returnValue = BP_strDataLossMsg;
BP_bOnBeforeUnloadFired = true;
window.setTimeout("BP_funClearIfCancelled()", 1000);
}
}
If no changes were found and the For Internet Explorer, if a change was detected, or the page class' dirty flag was set to In addition to setting the message, we also set a flag variable ( ConclusionI have used the Revision history
| ||||||||||||||||||||||||||||||||