Franc Stratton's .NET (TM) Web Application, OOP, and SOA Architecture & Programming Site

A site devoted to ASP.NET (TM), SilverLight (TM) and Browser-Based WPF (TM) Applications, IIS Services, and OOP Architectures

Home     Architecture Overview     WF/WCF/WPF     Data Store     Standards     .NET Security     Resources     jQuery     Silverlight     Developer Tips     Blog     Site Map      
jQuery Basics for ASP.NET
jQuery Selectors
jQueryEvents
Event Handler Methods
jQuery Special Effects
jQuery Callbacks
jQuery Page Method
jQuery Data Method
Simple Dirty Flag
Advanced Dirty Flag
AJAX Call to Web Service
Simple Page Styling
jQuery Selectors to an ASP.NET Page to Produce an Advanced Dirty Form Flag (many thanks to Kenneth Weems)

 

The jQuery dirty flag below is a much better implementation of the need for a dirty flag that takes advantage of jQuery selectors to set a drity flag for the page to 0 or 1 for false and true respectively.

 

//debugger; //uncomment debugger to debug with VS 2008/2010

$.SampleJQuery = {

dirtyFlag: "0",

allowPostBack: "0",

dirtyFlagMessageDefault: "If you click OK, the changes you have made will be lost. Click Cancel to remain on this page in order to save your changes.",

SaveClicked: "0",

IsCancelClicked: false

}

$(document).ready(function() {

$(":input:not([id*='FilterControl'][name*='PageSizeSelector'])").keydown(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

}).select(function() {

if (this.value != this.defaultValue) {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

}

}).bind('cut copy paste', function(e) {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

}).blur(function() {

if (this.value != this.defaultValue) {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

}

});

$("select[onchange][onchange*='__doPostBack']").bind('change keydown', function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 1;

});

$("select:not([onchange*='__doPostBack'])").change(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

});

$("input:checkbox[onclick][onclick*='__doPostBack']").click(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 1;

});

$("input:checkbox:not([onclick*='__doPostBack'])").click(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

});

$("input:radio:[onclick][onclick*='__doPostBack']").click(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 1;

});

$("input:radio:not([onclick*='__doPostBack'])").click(function() {

$.SampleJQuery.dirtyFlag = (!this.bypassdirtyflag) ? 1 : 0;

$.SampleJQuery.allowPostBack = 0;

});

$("input:submit:not([value*='Cancel'])").click(function() {

$.SampleJQuery.allowPostBack = 1;

});

$("input:submit:[value*='Cancel']").click(function(e) {

$.SampleJQuery.IsCancelClicked = true;

return confirmOnLeave(e);

});

$("input:submit:[value*='Save']").click(function() {

$.SampleJQuery.SaveClicked = 1;

$.SampleJQuery.allowPostBack = 1;

});

$("input:button:[value*='Cancel']").click(function(e) {

$.SampleJQuery.IsCancelClicked = true;

return confirmOnLeave(e);

});

$("input:button:[value*='Save']").click(function() {

$.SampleJQuery.SaveClicked = 1;

$.SampleJQuery.allowPostBack = 1;

});

$("a[href][href*='__doPostBack']").click(function() {

$.SampleJQuery.allowPostBack = 1;

});

$("td[id*='GridNRC']").click(function() {

$.SampleJQuery.dirtyFlag = 0;

$.SampleJQuery.allowPostBack = 0;

});

$("select:[name*='PageSizeSelector']").change(function() {

$.SampleJQuery.allowPostBack = 1;

$.SampleJQuery.dirtyFlag = 0;

this.defaultValue = this.value;

});

});

function confirmOnLeave(e) {

var confirmResponse = false;

if ($.SampleJQuery.dirtyFlag == 1) {

confirmResponse = confirm($.SampleJQuery.dirtyFlagMessageDefault);

if (confirmResponse == true) {

$.SampleJQuery.SaveClicked = 1;

$.SampleJQuery.allowPostBack = 1;

} else {

$.SampleJQuery.SaveClicked = 0;

$.SampleJQuery.allowPostBack = 0;

if (e.stopPropagation) { e.stopPropagation(); }

}

return confirmResponse;

}

}

function setDirtyFlagAttribute(arry) {

if (arry) {

var i;

for (i = 0; i < arry.length; i++) {

$("*[id='" + arry[i] + "']").attr("bypassdirtyflag", 'true');

}

}

}

//used by the oBout grid

function bindSaveLink() {

$.SampleJQuery.dirtyFlag = 0;

}

//used by the oBout grid

function bindCancelLink() {

if ($.SampleJQuery.dirtyFlag != 0) {

var bool = confirm($.SampleJQuery.dirtyFlagMessageDefault)

if (bool == true) {

$.SampleJQuery.dirtyFlag = 0;

return true;

} else {

return false;

}

}

return true;

}

function checkDirtyForm() {

if (!unloadOnLeaveOnly()) {

window.onbeforeunload = null;

}

else {

event.returnValue = $.SampleJQuery.dirtyFlagMessageDefault;

}

}

function unloadOnLeaveOnly() {

if ($.SampleJQuery.allowPostBack == 0) {

if ($.SampleJQuery.dirtyFlag == 1) { return true; }

} else {

return false;

}

}

addListener(window, 'beforeunload', checkDirtyForm)

if (typeof unloadCheckIn != 'function') {

addListener(window, 'unload', unloadJqueryDirtyFlag);

}

function unloadJqueryDirtyFlag() {

// if (typeof (isModalPage) != 'undefined') {

// var Mode = getQueryStringValue("Mode");

// if (($.SampleJQuery.SaveClicked == 1 && Mode == 2) || $.SampleJQuery.IsCancelClicked)

// window.close();

// }

}

function ResetDirtyFlag() {

if ($.SampleJQuery.dirtyFlag != 'undefined' && $.SampleJQuery.dirtyFlag == '0') {

$.SampleJQuery.dirtyFlag = '1';

}

}

function SetDirtyFlagToFalse() {

if ($.SampleJQuery.dirtyFlag != 'undefined' && $.SampleJQuery.dirtyFlag == '1') {

$.SampleJQuery.dirtyFlag = '0';

}

}

 

Developer Control of the Dirty Flag for ASP.NET Web Forms

                                                                      By Kenneth Weems and Franc Stratton

 

The dirty flag for web forms allows the developer to implement complex user interface or business logic and validation in ASP.NET code-behind, JavaScript files, and business layers and have programmatic control over when the dirty flag is reset to true or set back to false. Two new dirty flag methods called “ResetDirtyFlag” and “SetDirtyFlagToFalse” reset the jQuery “$.SampleJQuery.dirtyFlag” to true or set it back to false respectively. Furthermore, new jQuery code added to the ASP.NET page JavaScript file controls when and how the dirty flag works with a modal form’s “SAVE” and “CANCEL” button events to save or close the form (shown below):

$(document).ready(function() {

    $("input:button:[id*='Save_Phone']").click(function() {

        SavePhoneNumber();

    });

    $("input:button:[id*='Cancel_Phone']").click(function(e) {

        if (!e.isPropagationStopped())

            CancelDialog();

    });

});

As an example, the modal form’s save button above is named “btnSave_Phone” and the cancel button is named “btnCancel_Phone”. If a programmer’s buttons were named “btnSave_Address” and “btnCancel_Address”, then these button identifiers would replace “btnSave_Phone” and “btnCancel_Phone” respectively in the jQuery code above. The “SavePhoneNumber()” and “CancelDialog()” methods are in the pages JavaScript file for saving form data or cancelling the edit and closing the modal form.

The current example for how to control the dirty flag for an ASPX web page with an oBout modal window is the DepartmentAttorneysEdit.aspx page in the UI Appeals folder.

 

The figure above shows the “DepartmentAttorneysEdit.aspx” page highlighted in blue, and the “DepartmentAttorneysEdit.js” JavaScript file is at the top of the picture.

The “DepartmentAttorneysEdit.aspx” page contains an oBout grid that displays a list of phone numbers for the attorneys.

 

When the user highlights a row in the grid and clicks the “Edit” button or just clicks on the “Add” button, a JavaScript method opens an oBout dialog window in either edit or add mode respectively. These two buttons use the JavaScript code:

 

function DisplayDialog(mode) {

if ($.SamplejQuery.dirtyFlag != 'undefined' && $.SamplejQuery.dirtyFlag == '1') {

    alert('You have unsaved data on this page.  You must click SAVE or CANCEL before making changes to items in the grid.'); //Customer Service Message

}

else {

if (mode == 1) {                document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_lblOwTitle').innerHTML = "Add Phone Number";

        document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtPhoneNumber').value = "";

            document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtExt').value = "";

            document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtBestTimeToCall').value = "";

           

        if ($.SamplejQuery.dirtyFlag == '1') {

                SetDirtyFlagToFalse();

        }

            owPhoneNumber.Open();

        }

        else {

            if (gvwPhoneNumber.SelectedRecords.length == 0) {

                //if no records are selected when the Edit button is clicked, display alert message

                alert('No record selected. You must select a record from the grid in order to perform the requested action.');

                return false;

            }

            else {

                document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_lblOwTitle').innerHTML = "Edit Phone Number";

                var phoneNumber = gvwPhoneNumber.SelectedRecords[0];

                var phoneType = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_ddlPhoneType');

 

                for (var i = 0; i < phoneType.options.length; i++) {

                    if (phoneType.options[i].text == phoneNumber.PhoneType) {

                        phoneType.options[i].selected = true;

                    }

                }

                document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtPhoneNumber').value = phoneNumber.PhoneNumber;

                document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtExt').value = phoneNumber.Extension;

                document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtBestTimeToCall').value = phoneNumber.BestTimeToCall;

                if ($.SamplejQuery.dirtyFlag == '1') {

                    SetDirtyFlagToFalse();

                }

                owPhoneNumber.Open();

            }

        }

    }

}

Note: Be sure that the modal buttons do not have any in-line JavaScript in their “onclick” events that would interfere with the jQuery dirty flag code. For instance, notice that there are no “onclick” events for the buttons below utilized in the examples depicted above in this document.

<input id="btnSave_Phone" type="button" value="Save" class="arts_ButtonStandard" />

<input id="btnCancel_Phone" type="button" value="Cancel" class="arts_ButtonStandard" />

The programmer does not want the dialog window to open if the parent form controls have data in them changed by the user. The programmer should then add the JavaScript conditional if statement to check the dirty flag, and if it is true, then an alert message informs the user “You have unsaved data on this page.  You must click SAVE or CANCEL before making changes to items in the grid.” The JavaScript for this condition is:

if ($.SamplejQuery.dirtyFlag != 'undefined' && $.SamplejQuery.dirtyFlag == '1') {

    alert('You have unsaved data on this page.  You must click SAVE or CANCEL before making changes to items in the grid.'); //CSM.0036

}

else {

The “if” conditional statement prevents the modal form from opening by displaying a modal JavaScript alert with the message and only an “OK” button:

 

 

If the dirty flag is false, then the JavaScript code runs that opens and populates the form with data to edit or a blank form for adding a new phone number. Notice in the code added below that the programmer has set the dirty flag to false after the form is populated:

if ($.SamplejQuery.dirtyFlag == '1') {

                    SetDirtyFlagToFalse();

If the user enters or changes data on the modal form and saves the data, the programmer set the dirty flag to false before going back to the parent ASPX form. If the user cancels the form input, and hits the cancel button, the programmer must also set the dirty flag to false before returning to the parent form. Why? The oBout window is on the parent form, and they must share the same form dirty flag. There is only one dirty flag per form, and if a parent form opens an oBout dialog window, then the dirty flag must be set to false for new user input. The code below handles user saves and cancels, and this must be added to the form JavaScript file:

 

$(document).ready(function() {

    $("input:button:[id*='Save_Phone']").click(function() {

        SavePhoneNumber();

    });

    $("input:button:[id*='Cancel_Phone']").click(function(e) {

        if (!e.isPropagationStopped())

            CancelDialog();

    });

});

If the user clicks the “btnSave_Phone” button, then the “SavePhoneNumber()” JavaScript method runs. If the user clicks the “btnCancel_Phone” form button, then the JavaScript “CancelDialog()” method will run if the flag is not dirty. If the flag is dirty, then the user will be presented with the JavaScript confirm message box that informs them that the form is dirty, and they have choice of leaving the page by hitting “OK” or remain on the page by hitting “Cancel”:

 

If the user saves the data or cancels the changes on the modal form, the programmer must add the code to set the dirty flag to false in the appropriate form JavaScript file methods. For example:

function SavePhoneNumber() {

    var phoneNumber = new Object();

 

    var validation = ValidatePhoneType();

 

    if (validation == true) {

        var title = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_lblOwTitle').innerHTML;

        var mode = title.substring(0, 3);

 

        phoneNumber.PhoneType = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_ddlPhoneType').value;

        phoneNumber.PhoneNumber = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtPhoneNumber').value;

        phoneNumber.PhoneExt = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtExt').value;

        phoneNumber.BestTimeToCall = document.getElementById('ctl00_ContentPlaceHolder1_owPhoneNumber_txtBestTimeToCall').value;

        if ($.SamplejQuery.dirtyFlag == '1') {

            SetDirtyFlagToFalse();

        }

The jQuery code method “SetDirtyFlagToFalse()” sets the dirty flag back to false before returning to the parent page.

The programmer may add the dirty flag “SetDirtyFlagToFalse()” and ““ResetDirtyFlag()” methods to code-behind form logic as well by using the below methodology:

ClientScript.RegisterStartupScript(this.GetType(), "ResetDirtyFlagScript", "<script language=JavaScript>ResetDirtyFlag();</script>");

 

The developer should remember that the following code is in the BasePage base class that the page code-behind is derived from. If the page is not derived from the BasePage base class, then the following code must be added to the page code-behind aspx.cs file.

 

//---------------------------------------------------------------------------

/// <summary>

/// Method Name: AddDirtyFlag

/// Description: This method adds dirty flag functionality to any page that derives

/// from this class.

/// </summary>

//---------------------------------------------------------------------------

private void AddDirtyFlag()

{

var sb = new StringBuilder();

// Check to see if the include script exists already.

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "jQueryLink"))

{

sb.Append(" <script src=\"");

sb.Append(ResolveClientUrl(JQUERYURL));

sb.Append("\" type=\"text/javascript\"></script>");

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "jQueryLink", sb.ToString());

}

sb.Length = 0;

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "DirtyFlagLink"))

{

sb.Append(" <script src=\"");

sb.Append(ResolveClientUrl(DIRTYFLAGJSCODEURL));

sb.Append("\" type=\"text/javascript\"></script>");

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "DirtyFlagLink", sb.ToString());

}

sb.Length = 0;

if (BypassDirtyControls != null && BypassDirtyControls.Count > 0)

{

sb.Append("<script type=\"text/javascript\" language=\"javascript\">");

sb.Append("$(window).load(function() {");

sb.Append("setDirtyFlagAttribute([\"");

sb.Append(String.Join("\",\"", BypassDirtyControls.ToArray()));

sb.Append("\"])");

sb.Append("} );");

sb.Append("</script>");

}

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "ByPassDirtyFlagList"))

{

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ByPassDirtyFlagList", sb.ToString());

}

//Reset the dirty flag on postback

if (IsPostBack)

{

Control postbackControl = DHS.AppBase.Global.GetPostBackControl(this.Page);

if (postbackControl == null || postbackControl.ID == null)

{

return;

}

else

{

if (postbackControl.GetType().ToString() != "System.Web.UI.WebControls.Button" &&

postbackControl.GetType().ToString() != "System.Web.UI.WebControls.LinkButton" &&

!IsControlInDirtyBypassList(postbackControl.ClientID.ToString()))

{

if (!this.Page.ClientScript.IsStartupScriptRegistered("ResetSetDirtyFlag"))

{

string jScript = string.Empty;

jScript = @"<script language='javascript' type='text/javascript'>

if (typeof ($.DHSJQuery.dirtyFlag) != 'undefined')

{

$.DHSJQuery.dirtyFlag = 1;

}

</script>";

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ResetSetDirtyFlag", jScript);

}

}

if (postbackControl.ID.ToUpper().Contains("SAVE"))

{

if (!this.Page.ClientScript.IsStartupScriptRegistered("SaveButtonClicked"))

{

string jScript = string.Empty;

jScript = @"<script language='javascript' type='text/javascript'>

if (typeof ($.DHSJQuery.SaveClicked) != 'undefined')

{

$.DHSJQuery.SaveClicked = 1;

}

</script>";

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "SaveButtonClicked", jScript);

}

 

}

}

}

}

//---------------------------------------------------------------------------

/// <summary>

/// Method Name: IsControlInDirtyBypassList

/// Description: Method that checks to see if the control that caused

/// a postback exists in the bypass dirty flag list.

/// </summary>

///

/// <param name="ctrlName">The name of the control that caused the postback</param>

/// <returns>boolean</returns>

//---------------------------------------------------------------------------

private bool IsControlInDirtyBypassList(string ctrlName)

{

bool retVal = false;

if (BypassDirtyControls != null)

{

if (BypassDirtyControls.Contains(ctrlName))

{

retVal = true;

}

}

return retVal;

}

 

//---------------------------------------------------------------------------

/// <summary>

/// Method Name: AddDefaultJavascript

/// Description: This method adds JavaScript code and includes to any page that derives

/// from this class. Also Adds a client postback variable to determine if

/// a postback has occurred.

/// </summary>

//---------------------------------------------------------------------------

private void AddDefaultJavascript()

{

var sb = new StringBuilder();

sb.Append("<script type=\"text/javascript\" language=\"javascript\">");

sb.Append("function addListener(obj, evt, handler) {");

sb.Append("if (obj.addEventListener) {");

sb.Append("obj.addEventListener(evt, handler, false);");

sb.Append("}else if (obj.attachEvent) {");

sb.Append("obj.attachEvent('on' + evt, handler);}}");

sb.Append("</script>");

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "AddListener"))

{

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "AddListener", sb.ToString());

}

sb.Length = 0;

if (!IsPostBack)

{

sb.Append("<script type=\"text/javascript\" language=\"javascript\">");

sb.Append("var isPostBack = false;");

sb.Append("</script>");

}

else

{

sb.Append("<script type=\"text/javascript\" language=\"javascript\">");

sb.Append("var isPostBack = true;");

sb.Append("</script>");

}

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "ClientPostBack"))

{

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ClientPostBack", sb.ToString());

}

sb.Length = 0;

// Check to see if the include script exists already.

if (!this.Page.ClientScript.IsStartupScriptRegistered(this.GetType(), "jQueryLink"))

{

sb.Append(" <script src=\"");

sb.Append(ResolveClientUrl(JQUERYURL));

sb.Append("\" type=\"text/javascript\"></script>");

this.Page.ClientScript.RegisterStartupScript(this.GetType(), "jQueryLink", sb.ToString());

}

 

 

Also, if the page code-behind aspx.cs file is not derived from the base class BasePage, then the following code must be added to the page load event:

 

//---------------------------------------------------------------------------

/// <summary>

/// Method Name: Page_Load

/// Description: During load, if the current request is a postback, control

/// properties are loaded with information recovered from view state and control state.

/// Use the OnLoad event method to set properties in controls and establish database connections.

/// </summary>

///

/// <param name="sender">sender object</param>

/// <param name="e">EventArgs</param>

//---------------------------------------------------------------------------

protected void Page_Load(object sender, EventArgs e)

{

AddDefaultJavascript();

SetModalPageFlag();

if (AllowDirtyFlag)

{

AddDirtyFlag();

}

}