|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIt 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 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 Using the controlAs its descendent, the
Here is an example .aspx file demonstrating a <%@ 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 worksThe Because 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 rendering the button to a string buffer and then manipulating its The modification of the 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 function 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 function 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 a 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 functionsThe embedded text file javascript.txt contains the client-side code to hide the button's 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 <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>
SummaryThe ASP.NET server control AcknowledgementsThanks to CodeProject member KJELLSJ (Kjell-Sverre Jerijaervi) for code ideas to allow the button to work with client-side validation, and for pointing out the issue in the first place. History
| ||||||||||||||||||||||