Click here to Skip to main content
14,936,800 members
Articles / Web Development / HTML
Posted 10 Aug 2007


94 bookmarked

Dirty Panel Extender (ASP.NET AJAX)

Rate me:
Please Sign up or sign in to vote.
4.61/5 (30 votes)
5 Sep 2007CPOL4 min read
A dirty panel extender implementation with ASP.NET AJAX control toolkit.
ASP.NET Ajax Dirty Panel Extender in Action


My website is a rich social network that offers users many web forms to fill. For example, users can post articles and edit lengthy profiles. Often they click on a link that takes them away from the page or press the wrong key (e.g. backspace that navigates to the previous page). In both cases their changes get lost. And it is always frustrating to have to re-enter the same text twice. Wouldn't it be nice to warn the user that he has unsaved data and give him an opportunity to cancel, then save his data?

This is a Panel Extender for ASP.NET AJAX 1.0 that automatically detects if any input control inside it was changed and shows an alert if the user tries to leave the page before saving the data. The extender supports most HTML input controls and can detect whether either data, selection or both have changed.


This article uses the same techniques as described in this prior AJAX DirtyPanel article, but is implemented as a panel extender for Microsoft ASP.NET AJAX 1.0. The extender model offers a very clean and straightforward solution described in the implementation section below.

Using the Code

Standard Pages

Assuming you have an ASP.NET AJAX enabled site that uses the Ajax Control Toolkit, simply add the DirtyPanelExtender project to your solution, register the extender on the .aspx page and add an extender to a panel.

<%@ register assembly="DirtyPanelExtender"
           namespace="DirtyPanelExtender" tagprefix="dp" %>
<dp:DirtyPaneleEtender id="demoPanelExtender" runat="server"
 OnLeaveMessage="There's still unsaved data on the page!" />
<asp:UpdatePanel id="demoPanel" runat="server">

Master Pages

The master page scenario enables all website pages to enable the dirty panel feature automatically. You must wrap the ContentPlaceHolder in a panel and extend the panel with the DirtyPanelExtender.

<form id="form1" runat="server">
 <asp:ScriptManager ID="ScriptManager1" runat="server" />
 <dp:DirtyPanelExtender ID="demoPanelExtender" runat="server" 
  OnLeaveMessage="There's still unsaved data on the page!" />
 <asp:UpdatePanel ID="masterPanel" runat="server">
  <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">


Creating a Basic Extender

Creating an extender skeleton is described in this walkthrough. The basics include:

  • DirtyPanelExtenderBehavior.js: all client-side script logic
  • DirtyPanelExtender.cs: server-side control implementation
  • DirtyPanelExtenderDesigner.cs: design-time functionality

Hooking window.onbeforeunload

The window.onbeforeunload callback is the essential hook that will trap closing of the window. It is possible to prompt the user before the window is unloaded.

window.onbeforeunload = function (eventargs)
  if(! eventargs) eventargs = window.event;
  eventargs.returnValue = "You have unsaved data. 
                Are you sure you want to close this window?"

See MSDN for detailed information about the window.onbeforeunload handler.

Multiple DirtyPanels

The implementation supports multiple dirty panels by creating an array of panels.

var DirtyPanelExtender_dirtypanels = new Array()

The panel initialization code that will add itself to this array.

initialize : function() 
                            (this, 'initialize');
 DirtyPanelExtender_dirtypanels[DirtyPanelExtender_dirtypanels.length] = this;

It is now possible to iterate through the array in JavaScript.

for (i in DirtyPanelExtender_dirtypanels)
 var panel = DirtyPanelExtender_dirtypanels[i];

Hooking window.onbeforeunload for Dirty Panels

Every panel will expose a panel.isDirty that will return true if any of the existing form fields has changed (making the panel "dirty"), plus an OnLeaveMessage property to store the message to show. The hooking will only need to happen for a dirty panel.

window.onbeforeunload = function (eventargs)
 for (i in DirtyPanelExtender_dirtypanels)
  var panel = DirtyPanelExtender_dirtypanels[i];
  if (panel.isDirty())
   if(! eventargs) eventargs = window.event;
   eventargs.returnValue = panel.get_OnLeaveMessage();

Suppressing Dirty Check for Postbacks

The dirty panel only needs to trap navigating away from the page and not regular AJAX interaction built into the page. This notably enables upload controls without an UpdatePanel.

function __newDoPostBack(eventTarget, eventArgument)
// suppress prompting on postback
window.onbeforeunload = null;
return __savedDoPostBack (eventTarget, eventArgument);

var __savedDoPostBack = __doPostBack;
__doPostBack = __newDoPostBack; 

Determining Whether a Panel is Dirty

Determining whether the panel is dirty is the hardest part. First, there's no native support for whether an input box or other editable control has changed. Old values must be tracked and compared. In addition, hidden values should not be updated on a regular postback. Original values are saved in a hidden field in OnPreRender.

protected override void OnPreRender(EventArgs e)
 string values_id = string.Format("{0}_Values", TargetControl.ClientID);
 string values = (Page.IsPostBack ? 
    Page.Request.Form[values_id] : String.Join(",", GetValuesArray()));
 ScriptManager.RegisterHiddenField(this, values_id, values);

The implementation of GetValuesArray simply iterates through child controls and saves those that are editable. Special care is taken for various types of controls.

  • ListControl types, including DropDownList and ListBox: save both data and initial selections
  • RadioButtonList: save an entry for each radio button with its selected state; radio button contents don't work
  • IEditableTextControl: save any .Text value of an editable control
  • ICheckBoxControl: save checkbox state

Note that it now looks trivial to implement a way to reset the dirty flag, for example when the user presses the Save button. It is only necessary to reset the saved values. Unfortunately things are not that simple, especially if the extender is used with an UpdatePanel. You must emit JavaScript within that panel that will reset the value of the hidden field.

public void ResetDirtyFlag()
                (TargetControl, TargetControl.GetType(),
   string.Format("{0}_Values_Update", TargetControl.ClientID), 
        string.Format("document.getElementById('{0}').value = '{1}';",
   string.Format("{0}_Values", TargetControl.ClientID), 
                String.Join(",", GetValuesArray())), true);

The isDirty function deconstructs the hidden field value and compares the current form values one-by-one, for each type of input control.

isDirty : function() {
 var values_control = document.getElementById(this.get_element().id + 
 var values = values_control["value"].split(",");
 for (i in values) {
  var namevalue = values[i];
  var namevaluepair = namevalue.split(":");
  var name = namevaluepair[0];
  var value = (namevaluepair.length > 1 ? namevaluepair[1] : "");
  var control = document.getElementById(name);
  if (control == null) continue;
  if (control.type == 'checkbox' || control.type == 'radio') {
   var boolvalue = (value == "true" ? true : false);
   if(control.checked != boolvalue) {
    return true;
  } else if (control.type == 'select-one') {
   if ( control.size > 0 ){
    // control is listbox
    if( encodeURIComponent(optionValues) != value ){
     return true;
   } else if(control.selectedIndex != value) {
     return true;
  } else {
   if(encodeURIComponent(control.value) != value) {
       return true;
 return false;

Dealing with Lists

The actual implementation of isDirty is a little more complex, especially for lists. These typically inherit from ListControl. It is necessary to support both selection and data changes in the list, and GetValuesArray creates two hidden variables, id:selection:value and id:data:value to represent the current state.

else if (control is ListControl)
   StringBuilder data = new StringBuilder();
   StringBuilder selection = new StringBuilder();
   foreach (ListItem item in ((ListControl) control).Items)
   values.Add(string.Format("{0}:data:{1}", control.ClientID, 
   values.Add(string.Format("{0}:selection:{1}", control.ClientID, 

isDirty will process both types of values.

} else if (control.type == 'select-one' || control.type == 'select-multiple') 
  if (namevaluepair.length > 2) {
  // composite control (has data and selection)
     if ( control.options.length > 0) {
         // the control has a list of values
         // there's data:value and selection:value
         var code = value;
         value = (namevaluepair.length > 2 ? namevaluepair[2] : "");
         var optionValues = "";
         // concat all listbox items
         for( var cnt = 0; cnt < control.options.length; cnt++) {
            if (code == 'data') {
                optionValues += control.options[cnt].text;
            } else if (code == 'selection') {
                optionValues += control.options[cnt].selected;
            optionValues += "\r\n";
         if( encodeURIComponent(optionValues) != value ) {
            // items in the listbox have changed
            return true;
 } else if(control.selectedIndex != value) {
     return true;


This is a simple and useful control. I also found the ASP.NET AJAX extender model very well structured and clean, adding useful functionality to existing controls in a straightforward manner, a significant improvement over the reference AJAX implementation for Anthem.

Known Issues

  • bug: doesn't work with Opera; tested with Opera 9.21
  • bug: doesn't work with Safari; tested with 3.0.2 WinXP
  • bug: partial support for RadioButtonList - selection changes only, no dynamic data changes


  • 08/10/2007: initial version
  • 08/11/2007: fixed bug - target control client ID wrong
  • 08/11/2007: fixed bug - fixed for upload controls and standard AJAX scenarios; suppressed prompting for all postbacks
  • 08/13/2007: added demo and documentation for using the extender with master pages
  • 08/28/2007: added RadioButtonList and ListBox support and demo for both data and selection (thanks to David Christensen)


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Team Leader Application Security Inc.,
United States United States
Daniel Doubrovkine has been in software engineering for twelve years and is currently development manager at Application Security Inc. in New York City. He has been involved in many software ventures, including Xo3 and Vestris Inc, was a development lead at Microsoft Corp. in Redmond, and director of Engineering at Visible Path Corp. in New York City. Daniel also builds and runs a foodie website,

Comments and Discussions

Question[My vote of 1] This doesn't work with web-kit browsers like Chrome Pin
msdevtech26-Jul-12 7:27
Membermsdevtech26-Jul-12 7:27 
QuestionUpgraded to .NET Framework 4.0 and AJAX 4.1.50927.0 Pin
datamaster-ca30-Sep-11 5:50
Memberdatamaster-ca30-Sep-11 5:50 
Generallost focus during tab [modified] Pin
datamaster-ca4-Mar-11 9:58
Memberdatamaster-ca4-Mar-11 9:58 
GeneralRe: lost focus during tab Pin
datamaster-ca7-Mar-11 4:12
Memberdatamaster-ca7-Mar-11 4:12 
GeneralMy vote of 5 Pin
Bharat Gambhir25-Nov-10 2:42
MemberBharat Gambhir25-Nov-10 2:42 
GeneralInherited 2008 web application that uses the DirtyPanelExtender. Pin
BenLynch6-Sep-10 8:36
MemberBenLynch6-Sep-10 8:36 
GeneralRe: Inherited 2008 web application that uses the DirtyPanelExtender. Pin
dB.6-Sep-10 16:09
MemberdB.6-Sep-10 16:09 
GeneralRe: Inherited 2008 web application that uses the DirtyPanelExtender. Pin
BenLynch6-Sep-10 21:00
MemberBenLynch6-Sep-10 21:00 
GeneralRe: Inherited 2008 web application that uses the DirtyPanelExtender. Pin
dB.7-Sep-10 1:16
MemberdB.7-Sep-10 1:16 
GeneralRe: Inherited 2008 web application that uses the DirtyPanelExtender. Pin
BenLynch7-Sep-10 4:34
MemberBenLynch7-Sep-10 4:34 
GeneralRe: Inherited 2008 web application that uses the DirtyPanelExtender. Pin
BenLynch7-Sep-10 10:48
MemberBenLynch7-Sep-10 10:48 
GeneralNot working. Pin
Mike78623-Aug-10 5:33
MemberMike78623-Aug-10 5:33 
GeneralRe: Not working. Pin
dB.6-Sep-10 16:09
MemberdB.6-Sep-10 16:09 
GeneralI couldn't get this to work Pin
toddmo3-Aug-10 15:14
Membertoddmo3-Aug-10 15:14 
GeneralRe: I couldn't get this to work Pin
dB.4-Aug-10 1:50
MemberdB.4-Aug-10 1:50 
GeneralRe: I couldn't get this to work [modified] Pin
toddmo4-Aug-10 2:26
Membertoddmo4-Aug-10 2:26 
GeneralRe: I couldn't get this to work Pin
dB.4-Aug-10 2:34
MemberdB.4-Aug-10 2:34 
GeneralRe: I couldn't get this to work Pin
toddmo4-Aug-10 2:48
Membertoddmo4-Aug-10 2:48 
GeneralGiving alert for nonsecure items on https Pin
dsunaria28-Jul-10 1:00
Memberdsunaria28-Jul-10 1:00 
GeneralRe: Giving alert for nonsecure items on https Pin
dB.30-Jul-10 6:38
MemberdB.30-Jul-10 6:38 
GeneralEasy way to reset the DirtyPanelExtender from JavaScript Pin
it-bergmann15-Dec-09 21:01
Memberit-bergmann15-Dec-09 21:01 
GeneralRe: Easy way to reset the DirtyPanelExtender from JavaScript Pin
dB.16-Dec-09 2:32
MemberdB.16-Dec-09 2:32 
GeneralRe: Easy way to reset the DirtyPanelExtender from JavaScript [modified] Pin
it-bergmann18-Dec-09 4:09
Memberit-bergmann18-Dec-09 4:09 
Generalthis works for me with JS update function and using NET 3.5 Pin
it-bergmann18-Dec-09 9:44
Memberit-bergmann18-Dec-09 9:44 
GeneralAlways dirty again [modified] Pin
it-bergmann24-Nov-09 0:47
Memberit-bergmann24-Nov-09 0:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.