http://www.codeproject.com/aspnet/PleaseWaitButton.asp
Introduction
It is often useful upon a form submission in a web application to display a "please wait" message, or animated .gif image, particularly if the submission process lasts a few seconds or more. I recently developed a survey submission application in which internal users upload excel spreadsheets through a web page. The application inserts the uploaded data from the spreadsheets into a database. The upload/insert process may only take a few seconds, but even a few seconds on the web is a noticeable wait. When testing, some users clicked the upload button repeatedly; it was useful to provide a visual clue that the upload was underway. For that matter, it was useful to hide the upload button altogether, preventing multiple clicks. The control presented here, a subclass of the Button
control, demonstrates how client-side JavaScript code encapsulated in an ASP.NET server control can provide such functionality conveniently.
Though there are lots of JavaScript examples out there to accomplish this type of thing, I came across some issues when attempting to encapsulate this functionality in an ASP.NET control. My initial attempts involved disabling the button with a JavaScript onClick
handler and substituting different text, but I found this would interfere with the functioning of the ASP.NET server-side Click
event. What ultimately worked, providing better cross-browser support as well, was to have the button rendered within a <div>
tag. This <div>
can then be hidden without interfering with the ASP.NET Click
event.
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 thesrc
attribute for the resulting<
img
>
tag.
PleaseWaitType
One of thePleaseWaitTypeEnum
values –TextOnly
,ImageOnly
,TextThenImage
, orImageThenText
– 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)
{
// Server-side Click event handler;
// simulate something that could take a long time,
// like a file upload or time-consuming server processing
DateTime dt = DateTime.Now.AddSeconds(5);
while (DateTime.Now < dt)
{
// do nothing; simulate a 5-second pause
}
// at the end of the loop display a success message
// and hide the submit form
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 Button
within a <div>
tag. It also renders an initially empty <div>
tag for the message/image.
protected override void Render(HtmlTextWriter output)
{
// before rendering the button, output an empty <div>
// that will be populated client-side via javascript
// with a "please wait" message"
output.Write(string.Format("<div id='pleaseWaitButtonDiv2_{0}'>",
this.ClientID));
output.Write("</div>");
// render the button in an encapsulating <div> tag of its own
output.Write(string.Format("<div id='pleaseWaitButtonDiv_{0}'>",
this.ClientID));
base.Render(output);
output.Write("</div>");
}
These <div>
tags are then manipulated through a client-side JavaScript onclick
handler. The layout (text and/or image) and onclick
assignment are made in the overridden OnPreRender
method:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// get the current onclick handler if any, and
// set up the text message and/or image string
// depending on the values of PleaseWaitText and
// PleaseWaitImage
string sCurrent = this.Attributes["onclick"];
string sMessage = "";
string sText = _pleaseWaitText;
string sImage = (_pleaseWaitImage != String.Empty
? string.Format(
"<img src=/"{0}/" align=/"absmiddle/" alt=/"{1}/"/>"
, _pleaseWaitImage, _pleaseWaitText )
: String.Empty);
// If we're using an image, register some javascript
// for client-side image preloading
if (sImage != String.Empty
&& _pleaseWaitType != PleaseWaitTypeEnum.TextOnly)
RegisterJavascriptPreloadImage(_pleaseWaitImage);
// establish the layout based on PleaseWaitType
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;
}
// Add the final code chunk as the javascript onclick handler
string sCode
= string.Format(
"PleaseWait('pleaseWaitButtonDiv_{0}', 'pleaseWaitButtonDiv2_{1}', '{2}');"
, this.ClientID, this.ClientID, sMessage);
this.Attributes["onclick"] = sCode + sCurrent;
}
If a PleaseWaitImage
is specified, the private method RegisterJavascriptPreloadImage
(
)
is executed. This outputs client-side code to preload the image. 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.
private void RegisterJavascriptPreloadImage(string sImage)
{
// javascript variable name for the image
string sImgName = "img_"
+ sImage.Replace(".","_").Replace("+", "_").Replace(" ", "_");
// construct the client-side script to preload
// the image
StringBuilder sb = new StringBuilder();
sb.Append("<script language='JavaScript'>");
sb.AppendFormat("{0} = new Image();", sImgName);
sb.AppendFormat("{0}.src = /"{1}/";", sImgName, sImage);
sb.Append("</script>");
// register the script with the page
this.Page.RegisterClientScriptBlock(
sImgName + "_PreloadScript", sb.ToString());
}
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);
// the client-side javascript code is kept
// in an embedded resource; load the script
// and register it with the page.
RegisterJavascriptFromResource();
}
private void RegisterJavascriptFromResource()
{
// load the embedded text file "javascript.txt"
// and register its contents as client-side script
string sScript = GetEmbeddedTextFile("javascript.txt");
this.Page.RegisterClientScriptBlock("PleaseWaitButtonScript", sScript);
}
private string GetEmbeddedTextFile(string sTextFile)
{
// generic function for retrieving the contents
// of an embedded text file resource as a string
// we'll get the executing assembly, and derive
// the namespace using the first type in the assembly
Assembly a = Assembly.GetExecutingAssembly();
String sNamespace = a.GetTypes()[0].Namespace;
// with the assembly and namespace, we'll get the
// embedded resource as a stream
Stream s = a.GetManifestResourceStream(
string.Format("{0}.{1}", sNamespace, sTextFile)
);
// read the contents of the stream into a string
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)
{
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 and companion client-side JavaScript to present users with a "please wait" message or image. Such a message can provide users with a useful visual cue for time-consuming form processing and prevent accidental multiple clicks.
About Mike Ellison
I work for the University of Nevada, Las Vegas in the Office of Institutional Analysis and Planning. Our office is charged with the mission of deriving useful information from institutional data. Within the context of that mission, my office mates and I apply technology in the form of custom data processing applications, data extraction and analysis tools, reporting tools, relational databases, OLAP solutions, data warehousing, and data mining. Click here to view Mike Ellison's online profile. |
Other popular ASP.NET articles:
|