IntroductionIt
is often useful to display "please wait" messages or animated gif images during the form submission process of a web application, especially when the submission process takes a long time. I recently developed a survey submission program where internal users upload excel spreadsheets via a web page. The program inserts the uploaded spreadsheet data into the database. This process only takes a few seconds, but even if it is a few seconds, it is a very obvious waiting process from the perspective of the web page. During testing of the program, some users repeatedly clicked the upload button. Therefore, it is useful to provide a visual message to tell people that the upload is in progress. And hide the upload button at the same time to prevent multiple clicks. The control introduced here is a subclass of the Button control, which demonstrates how to encapsulate client-side JavaScript code in an ASP.NET server control to provide convenient functions.
Although there are many javascript examples out there to accomplish this, I found some problems when I tried to encapsulate these functions into ASP.NET controls. I first tried to invalidate the button through the javascript onclick handler and replace it with other text. But I found it very tricky, as this will hinder the function of the click event on the asp.net server side. What finally worked and had good support for different browsers was to render the button in a div tag. The div can be hidden and out of the way of asp.net's click event.
Using the control
As a derivative of the normal button control, the functions of PleaseWaitButton are basically the same. It uses three additional properties to manage the display of "please Wait" messages or images when the button is clicked.
PleaseWaitText
This is the client-side text message that is displayed and, if present, will replace the button when the button is clicked.
PleaseWaitImage
This is the image file (such as an animated gif image) that is displayed and, if present, will replace the button when it is clicked. This attribute will become the src attribute in the <img> tag.
PleaseWaitType
One of the PleaseWaitTypeEnum enumeration values: TextOnly, ImageOnly, TextThenImage, or ImageThenText. It controls the layout of messages and images.
Below is an example .aspx file that demonstrates a PleastWaitButton with 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 ASP.NET Button inside a <div> tag. It also renders an empty <div> tag for the information/image. When the button is clicked, the hiding of the button and the display of information are controlled by the Javascript function (see client function below). For convenience, the implementation of all necessary javascript client code is handled by the PleaseWaitButton server control.
Since PleaseWaitButton implements its own javascript onclick handler, we have to take some extra steps to preserve the original onclick handler and allow the control to cleanly run some client-side validation code. In order to achieve this goal, we first restore the Button base class to a string buffer, and then handle it skillfully to include the onclick code we defined.
protected override void Render(HtmlTextWriter output)
{
// Output the button's html (with attributes)
// to a dummy HtmlTextWriter
StringWriter sw = new StringWriter();
HtmlTextWriter wr = new HtmlTextWriter(sw);
base.Render(wr);
string sButtonHtml = sw.ToString();
wr.Close();
sw.Close();
// now modify the code to include an "onclick" handler
// with our PleaseWait() function called appropriately
// after any client-side validation.
sButtonHtml = ModifyJavaScriptOnClick(sButtonHtml);
// 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));
output.Write(sButtonHtml);
output.Write("</div>");
}
This technique of reducing the button to a string buffer and then processing its onclick content is certainly a hack. But it allows us to implement standard validation code in the parent button class and then Implement our PleaseWait() Javascript function call. Without doing this, we would have to implement our PleaseWait() function call in the onclick attribute before the validation code, unless we were willing to completely override the rendering of the parent Button class's attribute. In this way, even if there are input errors on the page, it will have the effect of hiding the button and displaying the "please wait" message that we do not want. Therefore, we must force our client PleaseWait() function in the onclick handler to appear after client page validation.
The modification of the onclick attribute occurs in the ModifyJavaScriptOnClick() function. This function gets the HTML string rendered by the button and checks to see if the onclick attribute is present. If so, this function checks whether client-side validation code is used. If this is the case, the PleaseWait() function we defined will be added at the end of the existing onclick code, immediately after the boolin variable Page_IsValid checked by the client. This variable represents whether the validation control is used. If the value of Page_IsValid is false, the "Please wait" message will not be displayed. Displayed if True.
private string ModifyJavaScriptOnClick(string sHtml)
{
// Thanks to CodeProject member KJELLSJ (Kjell-Sverre Jerijaervi)
// for code ideas to allow the button to work with client-side validation
string sReturn = "";
string sPleaseWaitCode = GeneratePleaseWaitJavascript();
// is there an existing onclick attribute?
Regex rOnclick = new Regex("onclick="(?<onclick>[^"]*)");
Match mOnclick = rOnclick.Match(sHtml);
if (mOnclick.Success)
{
// there is an existing onclick attribute;
// add our code to the end of it; if client-side
// validation has been rendered, make sure
// we check to see if the page is valid;
string sExisting = mOnclick.Groups["onclick"].Value;
string sReplace = sExisting
+ (sExisting.Trim().EndsWith(";") ? "" : "; ");
if (IsValidatorIncludeScript() && this.CausesValidation)
{
// include code to check if the page is valid
string sCode = "if (Page_IsValid) " + sPleaseWaitCode
+ " return Page_IsValid;";
// add our code to the end of the existing onclick code;
sReplace = sReplace + sCode;
}
else
{
// don't worry about the page being valid;
sReplace = sReplace + sPleaseWaitCode;
}
// now substitute our onclick code
sReplace = "onclick="" + sReplace;
sReturn = rOnclick.Replace(sHtml, sReplace);
}
else
{
// there isn't an existing onclick attribute;
// add ours
int i = sHtml.Trim().Length - 2;
string sInsert = " onclick="" + sPleaseWaitCode + "" ";
sReturn = sHtml.Insert(i, sInsert);
}
return sReturn;
}
The IsValidatorIncludeScript() uses the above check to see if there is a standard Javascript code block for the asp.net validation control registered with the page. The following uses a simple method to test whether there is validation code and variables like Page_IsValid.
private bool IsValidatorIncludeScript()
{
// return TRUE if this page has registered javascript
// for client-side validation; this code may not be registered
// if ASP.NET detects what it thinks (correctly or incorrectly)
// is a down-level browser.
return this.Page.IsStartupScriptRegistered("ValidatorIncludeScript");
} The following GeneratePleaseWaitJavascript() constructs the PleaseWait() Javascript function contained in the onclick attribute. We can determine the desired layout by inspecting the control's properties.
private string GeneratePleaseWaitJavascript()
{
// create a JavaScript "PleaseWait()" function call
// suitable for use in an onclick event handler
string sMessage = "";
string sText = _pleaseWaitText;
string sImage = (_pleaseWaitImage != String.Empty
? string.Format(
"<img src="{0}" align="absmiddle" alt="{1}"/>"
, _pleaseWaitImage, _pleaseWaitText )
: String.Empty);
// 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;
}
// return the final code chunk
string sCode = string.Format(
"PleaseWait('pleaseWaitButtonDiv_{0}',
'pleaseWaitButtonDiv2_{1}', '{2}');"
, this.ClientID, this.ClientID, sMessage);
sCode = sCode.Replace(""", """);
return sCode;
}
If you specify a PleaseWaitImage, you must include an additional piece of Javascript code to notify the client to preload the image. The registration of this script should appear in the overridden OnPreRender method. The registered key is the name of the image; if multiple buttons use the same image, the preload script only needs to be implemented once. A regular expression is used here to create a Javascript image variable to ensure that special characters (such as slashes in file paths) are converted into underscores.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender (e);
// If we're using an image, register some javascript
// for client-side image preloading
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.RegisterClientScriptBlock(sImgName + "_PreloadScript",
sb.ToString());
}
Client-side functions
The embedded text file javascript.txt contains the <div> that hides the button and the client-side code that displays the "please wait" message or image. These codes are loaded in the private method RegisterJavascriptFromResource() called in the overridden OnInit() method. This method calls the generic method GetEmbeddedTextFile(), which loads the file as a source and returns the content 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() that is executed in the Javascript onclick handler of the button. This code also calls a client-side method HideDiv() to hide the button's container <div>, and then assembles the information or image into the previously empty <div> tag by setting the innerHTML attribute. The auxiliary function GetDiv() returns a <div> object with id by checking document.getElementById, document.all, and document.layers, ensuring compatibility with different browsers. Below is the entire code of javascript.txt:
<script language="JavaScript">
function GetDiv(sDiv)
{
vardiv;
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>
Original link: http://www.codeproject.com/aspnet/PleaseWaitButton.asp
Download Source Project - 7 Kb
Download Demo Project - 30 Kb
http://www.cnblogs.com/jeffamy/archive/2006/08/20/481952.html