Introduction
在web application的表單提交過程中顯示“please wait”信息或者是gif動畫圖片通常是很有用的,特別是提交過程比較久的情況。我最近開發了一個調查提交程序,在程序裡內部用戶通過一個網頁上傳Excel電子表格。程序將上傳的電子表格數據插入到數據庫中。這個過程只需要幾秒鐘,但即便是幾秒鐘,在網頁是看來卻是非常明顯的等待過程。在程序測試的時候,一些用戶重復地點擊上傳按鈕。因此,提供一個視覺的信息來告訴人們上傳正在進行中是很有用的。並同時把上傳按鈕一起隱藏掉,以防止多次點擊。這裡介紹的控件是Button控件的子類,它演示了如何把客戶端Javascript代碼封裝在ASP.Net服務器控件中來提供便利的功能。
雖然外面已經有很多Javascript的例子來完成這件事情,但當我試圖把這些功能封裝到ASP.net控件中時我發現了一些問題。我最開始嘗試通過Javascript的onclick句柄來使button無效,並用另外的文本取代。但我發現很棘手,這樣會妨礙到asp.net服務器端的click事件的功能。而最終行得通的,並且對不同浏覽器也有很好支持的方法是,讓button在div標記中呈現。div可以隱藏並且不妨礙ASP.Net的click事件。
Using the control
作為正常的button控件的派生,PleaseWaitButton的功能與它基本一樣。它通過三個附加的屬性來管理當按鈕被點擊後"please Wait"信息或圖片的顯示。
PleaseWaitText
這是顯示的客戶端文本信息,如果存在,當按鈕被點擊它將取代按鈕。
PleaseWaitImage
這是顯示的圖像文件(比如gif動畫圖像),如果存在,當按鈕被點擊它將取代按鈕。這個屬性將變成<img>標記中的src屬性。
PleaseWaitType
PleaseWaitTypeEnum枚舉值之一:TextOnly,ImageOnly,TextThenImage,或者ImageThenText。它控制消息和圖片的布局。
下面是一個.ASPx文件示例,它演示了一個設置了PleaseWaitText和PleaseWaitImage的PleastWaitButton。
<%@ 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>
&
nbsp; <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
PleaseWaitButton控件在<div>標記中呈現了一個標准的ASP.Net Button。它也呈現了一個空的<div>標記給
信息/圖像。在點擊按鈕時,由Javascript函數(見下面的客戶端函數)控制按鈕的隱藏和信息的顯示。為了方便起見,由PleaseWaitButton服務器控件處理所有必需的Javascript客戶端代碼的實施。
由於PleaseWaitButton實施它自己的Javascript onclick句柄,所以我們必須采取一些額外的措施來保持原有的onclick句柄,並且允許控件清晰地運行一些客戶端驗證代碼。為了達到此目的,我們首先把Button基類還原為一個字符串緩沖,然後巧妙地處理它,把我們定義的onclick代碼包含進去。
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(sButto
nHtml);
// 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>");
}
這種把button還原成一個字符串緩沖然後處理它的onclick內容的技術是一件很危險的事情(is certainly a hack). 但它可以讓我們在父button類中實施標准的驗證代碼,然後再實現我們的PleaseWait() Javascript函數調用。如果不這樣做,我們只能在驗證代碼之前就在onclick屬性中實施我們的PleaseWait()函數調用,除非我們願意完全重寫父Button類的屬性的呈現。這樣就算頁面上有輸入錯誤也會產生我們並不希望的按鈕隱藏和顯示"please wait"信息的效果。因此,我們必須在onclick句柄中強行令我們的客戶端PleaseWait()函數出現在客戶端頁面驗證之後。
onclick屬性的修改發生在ModifyJavaScriptOnClick()函數中。這個函數獲取按鈕呈現的Html字符串,並檢查看是否存在onclick屬性。如果是,這個函數會檢查是否有使用客戶端驗證代碼。如果是這種情況的話,我們定義的PleaseWait()函數會加在已經存在的onclick代碼的最後面,緊跟在客戶端檢查的boolin變量Page_IsValid後面。這個變量代表是否使用了驗證控件。如果Page_IsValid的值是false,"Please wait"信息將不顯示。如果為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().EndsWit
h(";") ? "" : "; ");
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;
}
這個IsValidatorIncludeScript() 利用上面的檢查來查看是否有使用頁面注冊的ASP.Net驗證控件的標准Javascript代碼塊。下面則用一個簡單的方法測試了是否有驗證代碼和像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");
} 下面這個GeneratePleaseWaitJavascript()構建了包含在onclick屬性中的PleaseWait() Javascript函數。我們可以通過檢查控件的屬性來決定想要的布局。
private string GeneratePleaseWaitJavascript()
{
// create a JavaScript "Pl
easeWait()" 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;
}
如果指定了一個PleaseWaitImage,就必須包含額外的一段Javascript代碼來通知客戶端預載該圖像。這段腳本的注冊應該出現在重寫的OnPreRender方法中。注冊的鍵是圖像的名稱;如果多個按鈕都使用同一圖像,預載腳本只需要實施一次。這裡使用了一個正則表達式來創建Javascript圖像變量,以保證特殊字字符(比如文件路徑中的斜線)轉化成下劃線。
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)
RegisterJavascriptPreloa
dImage(_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
嵌入的文本文件Javascript.txt包含了隱藏按鈕的<div>和顯示"please wait"信息或圖像的客戶端代碼。這些代碼在重寫的OnInit()方法中調用的私有方法RegisterJavascriptFromResource()加載。這個方法調用泛型方法GetEmbeddedTextFile() ,在這個泛型方法中把文件做為源加載而把內容返回成字符串。
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 conten
ts of the stream into a string
StreamReader sr = new StreamReader(s);
String sContents = sr.ReadToEnd();
sr.Close();
s.Close();
return sContents;
}
javascript.txt嵌入資源包含了按鈕在Javascript的onclick句柄中執行的客戶端方法PleaseWait()。這段代碼也調用了一個客戶端方法HideDiv()以隱藏按鈕的容器<div>,然後通過設置innerHtml屬性把信息或圖像組裝進之前空的<div>標記中。輔助函數GetDiv()則是通過檢查document.getElementById, document.all, 和 document.layers用id返回一個<div>對象,保證了不同浏覽器的兼容性。下面是Javascript.txt的全部代碼:
<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>
原文鏈接: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