Introduction
The original version of the PleaseWaitButton control was written by Mike Ellison for
ASP.NET 1.1 (thanks Mike): http://www.codeproject.com/KB/webforms/PleaseWaitButton.aspx.
I just addressed the JavaScript rendering in version 2.0.
It is often useful upon a form submission in a web application to display a "please wait" message, or an animated GIF image, particularly if the submission process lasts
a few seconds or more. The best way of preventing the user from clicking on the button twice is to make sure the state of the button is changed upon initial click.
Using the control
As its descendent, the PleaseWaitButton functions just like a regular Button control. It exposes three additional properties
to govern the display of the "Please Wait" message or image once the button is clicked.
PleaseWaitText: This is the client-side text message to display, if any, in place of the button when clicked.
PleaseWaitImage: This is the image file (typically an animated GIF) to display, if any, in place of the button when clicked. This property serves
as the src attribute for the resulting <img> tag.
PleaseWaitType: One of the PleaseWaitTypeEnum values – TextOnly, ImageOnly, TextThenImage,
or ImageThenText – which governs the layout of the message and/or image.
Here is an example .aspx file demonstrating a PleaseWaitButton with both PleaseWaitText and PleaseWaitImage set:
<%@ Page language="C#" %>
<%@ Register TagPrefix="cc1" Namespace="JavaScriptControls"
Assembly="PleaseWaitButton" %>
<script runat="server">
private void PleaseWaitButton1_Click(object sender, System.EventArgs e)
{
DateTime dt = DateTime.Now.AddSeconds(5);
while (DateTime.Now < dt)
{
}
panelSuccess.Visible = true;
PleaseWaitButton1.Visible = false;
}
</script>
<html>
<head>
<title>Testing PleaseWaitButton</title>
</head>
<body>
<form id="Form1" method="post" runat="server">
<P>Testing the PleaseWaitButton control.</p>
<cc1:PleaseWaitButton id="PleaseWaitButton1" runat="server"
Text="Click me to start a time-consuming process"
PleaseWaitText="Please Wait... "
PleaseWaitImage="pleaseWait.gif"
OnClick="PleaseWaitButton1_Click" />
<asp:Panel id="panelSuccess" runat="server"
visible="false">
Thank you for submitting this form. You are truly
the coolest user I've ever had the pleasure of serving.
No, really, I mean it. There have been others, sure,
but you are really in a class by yourself.
</asp:Panel>
</form>
</body>
</html>
How it works
The PleaseWaitButton control renders a standard ASP.NET Button within a <div> tag. It also renders an initially empty
<div> tag for the message/image. JavaScript functions (shown below under Client-side functions) control
the hiding of the button and display of a "please wait" message when clicked. For convenience, the PleaseWaitButton server control handles
the rendering of all necessary JavaScript client code.
Because PleaseWaitButton renders its own JavaScript onclick handler, additional measures are necessary to preserve existing onclick handlers
and to allow the control to work cleanly with client-side validation code. To accomplish this, the base Button is first rendered to a string buffer,
which is then manipulated to include our custom onclick code.
protected override void Render(HtmlTextWriter output)
{
StringWriter sw = new StringWriter();
HtmlTextWriter wr = new HtmlTextWriter(sw);
base.Render(wr);
string sButtonHtml = sw.ToString();
wr.Close();
sw.Close();
sButtonHtml = ModifyJavaScriptOnClick(sButtonHtml);
output.Write(string.Format("<div id='pleaseWaitButtonDiv2_{0}'>",
this.ClientID));
output.Write("</div>");
output.Write(string.Format("<div id='pleaseWaitButtonDiv_{0}'>",
this.ClientID));
output.Write(sButtonHtml);
output.Write("</div>");
}
This technique of rendering the button to a string buffer and then manipulating its onclick contents is certainly a hack. It does however allow us to render
our PleaseWait() JavaScript call after the standard validation code rendered by the parent Button class. Unless we were willing to completely
rewrite the rendering of attributes of the parent's Button class, the best we can do without this hack is to render our PleaseWait() function
call before the validation code in the onclick attribute. This creates the undesirable effect of hiding the button and displaying a "please wait" message
even if there are data entry errors on the page. The calling of our client-side PleaseWait() function must be forced to occur after client-side page validation
in the onclick handler.
The modification of the onclick attribute occurs in the ModifyJavaScriptOnClick() function. This takes the rendered HTML string for the button
and inspects it to see if there is an existing onclick attribute. If so, the function also checks to see if client-side validation code is in use. In this case,
our custom PleaseWait() JavaScript function call is added to the end of the existing onclick code, following a check of the client-side boolean
variable Page_IsValid. This variable is present when validation controls are in use. If Page_IsValid is false on the client side, the "please wait"
message is stalled. If Page_IsValid is true on the client side, the "please wait" message displays.
private string ModifyJavaScriptOnClick(string sHtml)
{
string sReturn = "";
string sPleaseWaitCode = GeneratePleaseWaitJavascript();
Regex rOnclick = new Regex("onclick=\"(?<onclick>[^\"]*)");
Match mOnclick = rOnclick.Match(sHtml);
if (mOnclick.Success)
{
string sExisting = mOnclick.Groups["onclick"].Value;
string sReplace = sExisting
+ (sExisting.Trim().EndsWith(";") ? "" : "; ");
if (IsValidatorIncludeScript() && this.CausesValidation)
{
string sCode = "if (Page_IsValid) " + sPleaseWaitCode
+ " return Page_IsValid;";
sReplace = sReplace + sCode;
}
else
{
sReplace = sReplace + sPleaseWaitCode;
}
sReplace = "onclick=\"" + sReplace;
sReturn = rOnclick.Replace(sHtml, sReplace);
}
else
{
int i = sHtml.Trim().Length - 2;
string sInsert = " onclick=\"" + sPleaseWaitCode + "\" ";
sReturn = sHtml.Insert(i, sInsert);
}
return sReturn;
}
The function IsValidatorIncludeScript() referenced above checks to see if a standard JavaScript block used by ASP.NET validation controls has been registered
with the page. This serves as a convenient way of testing whether or not validation code and variables like Page_IsValid will be available.
private bool IsValidatorIncludeScript()
{
return this.Page.ClientScript.IsStartupScriptRegistered("ValidatorIncludeScript");
}
The function GeneratePleaseWaitJavascript() constructs the PleaseWait() JavaScript function call that is included
in the onclick attribute. Control properties are inspected to determine the desired layout.
private string GeneratePleaseWaitJavascript()
{
string sMessage = "";
string sText = _pleaseWaitText;
string sImage = (_pleaseWaitImage != String.Empty
? string.Format(
"<img src=\"{0}\" align=\"absmiddle\" alt=\"{1}\"/>"
, _pleaseWaitImage, _pleaseWaitText )
: String.Empty);
switch (_pleaseWaitType)
{
case PleaseWaitTypeEnum.TextThenImage:
sMessage = sText + sImage;
break;
case PleaseWaitTypeEnum.ImageThenText:
sMessage = sImage + sText;
break;
case PleaseWaitTypeEnum.TextOnly:
sMessage = sText;
break;
case PleaseWaitTypeEnum.ImageOnly:
sMessage = sImage;
break;
}
string sCode = string.Format(
"PleaseWait('pleaseWaitButtonDiv_{0}',
'pleaseWaitButtonDiv2_{1}', '{2}');"
, this.ClientID, this.ClientID, sMessage);
sCode = sCode.Replace("\"", """);
return sCode;
}
If a PleaseWaitImage has been specified, an additional block of JavaScript is included, instructing the client to pre-load the image. The registration of this script
occurs in the overridden OnPreRender method. The registration key is based on the image name; if multiple buttons are used on a page with the same image, the preload
script is rendered only once for the image. A regular expression is used to create the JavaScript image variable, ensuring non-alphanumeric characters (such as a slash in a file path)
are converted to underscores.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender (e);
if (_pleaseWaitImage != String.Empty
&& _pleaseWaitType != PleaseWaitTypeEnum.TextOnly)
RegisterJavascriptPreloadImage(_pleaseWaitImage);
}
private void RegisterJavascriptPreloadImage(string sImage)
{
Regex rex = new Regex("[^a-zA-Z0-9]");
string sImgName = "img_" + rex.Replace(sImage, "_");
StringBuilder sb = new StringBuilder();
sb.Append("<script language="'JavaScript'">");
sb.Append("if (document.images) { ");
sb.AppendFormat("{0} = new Image();", sImgName);
sb.AppendFormat("{0}.src = \"{1}\";", sImgName, sImage);
sb.Append(" } ");
sb.Append("</script>");
this.Page.ClientScript.RegisterStartupScript(this.GetType(), sImgName +
"_PreloadScript",sb.ToString());
}
Client-side functions
The embedded text file javascript.txt contains the client-side code to hide the button's <div> and display the "please wait" message/image.
This code is loaded in the overridden OnInit() method with a call to the private method RegisterJavascriptFromResource(). This method calls the more generic
method GetEmbeddedTextFile() which loads a text file embedded as a resource and returns the contents as a string.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
RegisterJavascriptFromResource();
}
private void RegisterJavascriptFromResource()
{
string sScript = GetEmbeddedTextFile("javascript.txt");
this.Page.ClientScript.RegisterStartupScript(this.GetType(),
"PleaseWaitButtonScript", sScript);
}
private string GetEmbeddedTextFile(string sTextFile)
{
Assembly a = Assembly.GetExecutingAssembly();
String sNamespace = a.GetTypes()[0].Namespace;
Stream s = a.GetManifestResourceStream(
string.Format("{0}.{1}", sNamespace, sTextFile)
);
StreamReader sr = new StreamReader(s);
String sContents = sr.ReadToEnd();
sr.Close();
s.Close();
return sContents;
}
The javascript.txt embedded resource contains the client-side method PleaseWait() which is executed in the JavaScript onclick handler
for the button. This code calls the client method HideDiv() to hide the button's containing <div>, then populates the previously empty
<div> tag with the message/image by setting its innerHTML property. The helper function GetDiv(), attempting to maintain
cross-browser compatibility, inspects document.getElementById, document.all, and document.layers to return a <div>
object given its ID. The complete client-side code in javascript.txt follows:
<script language="JavaScript">
function GetDiv(sDiv)
{
var div;
if (document.getElementById)
div = document.getElementById(sDiv);
else if (document.all)
div = eval("window." + sDiv);
else if (document.layers)
div = document.layers[sDiv];
else
div = null;
return div;
}
function HideDiv(sDiv)
{
d = GetDiv(sDiv);
if (d)
{
if (document.layers) d.visibility = "hide";
else d.style.visibility = "hidden";
}
}
function PleaseWait(sDivButton, sDivMessage, sInnerHtml)
{
if (!ValidatorOnSubmit()) return;
HideDiv(sDivButton);
var d = GetDiv(sDivMessage);
if (d) d.innerHTML = sInnerHtml;
}
</script>
Summary
The ASP.NET server control PleaseWaitButton presented here renders a standard Button within <div> tags with companion client-side
JavaScript to present users with a "please wait" message or image when clicked. Such a message can provide users with a useful visual cue for time-consuming form processing
and prevent accidental multiple clicks. Though special considerations are necessary for the control to function cleanly with client-side validators, this complexity can
be encapsulated in a server control, maintaining convenience for the control user.
Acknowledgements
Thanks to CodeProject member Mike Ellison for the original code; I just copied and pasted more than 98% of his article and made a little modification to it.
Working as software engineer for almost 10 years and during these years have develop several type of projects including ASP, ASP.NET, Widows form, Windows services, Web service and speech enable applications.