[ASP.NET & jQuery]BlockUI in ASP.NET & AJAX.NET
前言
相信大家為了避免user連點submit的情況發生,或是為了讓user知道現在資料正在處理中,最popular的選擇是就是jQuery的BlockUI plugin。然而,在ASP.NET中,當jQuery的plugin與postback,還有萬惡的UpdatePanel糾纏在一起時,就會痛苦萬分。
這一篇文章除了透過jQuery的selector幫我們加上BlockUI的效果,還要做到當觸發JavaScript的alert與confirm點選取消時,不能有BlockUI的效果。而且要順便解決掉,不論在UpdatePanel中,或UpdatePanel外,我們的JavaScript都要能夠work,並且避免在server端,為了UpdatePanel的Partial Render,搞得我們得每次都註冊醜醜 hard-code的大串JavaScript。
Tips
-
我們針對所有的button, submit且其class不為UnBlock,以及所有class為blockUI的DOM,當click的時候,要加上blockUI的效果。
- 如有其他AutoPostBack為True的控制項需要,可以加載在change的事件
- 承接上面的前提,我們要加上例外,也就是當button觸發JavaScript的alert()時,(通常為Validation失敗),不能呼叫$.blockUI()。當button觸發JavaScript的confirm()時,則按確定才能呼叫BlockUI,按取消則不能呼叫BlockUI。
- 不管有沒有在UpdatePanel裡面,觸發的是非同步的postback還是正常的postback,都不該影響到我們的需求。且非同步的postback之後,運作應該維持正常。而且我們不希望因為UpdatePanel,而要將aspx上的JavaScript,搬到server端再透過ScriptManager去註冊。
這篇文章用到的外部js有:
- jQuery.min.js (1.4.2) : jQuery framework
- jQuery.blockUI.js : blockUI的plugin
- fauxconsole.js : for IE console.log
- jQuery.updatepanel.js : 方便用來處理UpdatePanel非同步postback後,JavaScript仍能正常運作
另外,這篇文章也會提到,如何override JavaScript原生的function,例如alert與confirm。
Solution
- 定義一個alertFlag為false,當呼叫alert()或confirm()點選取消時,將alertFlag設定為true。呼叫$.blockUI()前,要先檢查alertFlag為false才呼叫。
- 我們的selector: $(':button:not(.UnBlock), :submit:not(.UnBlock), .blockUI')
- 透過變數,先暫存原生的window.alert與window.confirm,接著override window.alert以及window.confirm。當需要用到原生的alert()或confirm()時,再使用變數即可。(類似繼承+overrides+ base.function()的意思)
- 每次呼叫完$.blockUI()後,要將flag設定回false。
- updatepanel的plugin,panelReady(fn)代表當updatepanel第一次被create或update的時候,要callback的function
接著我們來看我們的程式碼:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="JavaScript/jquery.blockUI.js" type="text/javascript"></script>
<link href="JavaScript/fauxconsole.css" rel="stylesheet" type="text/css" />
<script src="JavaScript/fauxconsole.js" type="text/javascript"></script>
<script src="JavaScript/jquery.updatepanel.js" type="text/javascript"></script>
<script type="text/javascript">
var alertFlag = false;
$(document).ready(function () {
$('#UpdatePanel1').panelReady(function () {
$('#BlockWithConfirm').bind('click', function () {
if (!confirm('確定要執行嗎?確定會block,取消則無block')) {
return false;
}
});
$('#UnBlockWithAlert').bind('click', function () {
//通常alert是用來做JavaScript的Validation,所以alert完通常會接return false來避免postback。
alert('只是alert,就不會出現block');
return false;
});
$(':button:not(.UnBlock), :submit:not(.UnBlock), .blockUI').bind('click', myBlockUI);
$.unblockUI();
});
});
var oldAlert = window.alert;
window.alert = function (msg) {
console.log('in overrides alert');
oldAlert(msg);
alertFlag = true;
};
var oldConfirmFlag = window.confirm;
window.confirm = function (msg) {
if (!oldConfirmFlag(msg)) {
alertFlag = true;
return false;
}
else {
return true;
};
};
function myBlockUI() {
if (!alertFlag) {
$.blockUI({
message: '<img src=PageBlock.gif /> Loading...'
});
};
alertFlag = false;
};
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdateProgress ID="UpdateProgress1" runat="server">
<ProgressTemplate>
<img src="PageBlock.gif" />
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
<ContentTemplate>
<asp:Label ID="Label1" runat="server" Text="test"></asp:Label>
<asp:Button ID="Button1" runat="server" Text="Block" OnClick="Button1_Click" CommandName="Block" />
<asp:Button ID="Button2" runat="server" Text="UnBlock" OnClick="Button1_Click" CssClass="UnBlock"
CommandName="UnBlock" />
<asp:Button ID="BlockWithConfirm" runat="server" Text="BlockWithConfirm" OnClick="Button1_Click"
CommandName="BlockWithConfirm" />
<asp:Button ID="UnBlockWithAlert" runat="server" Text="UnBlockWithAlert" OnClick="Button1_Click"
CommandName="UnBlockWithAlert" />
<asp:Button ID="ServerExecuteAlertJavaScript" runat="server" Text="ServerExecuteAlertJavaScript"
OnClick="Button2_Click" CommandName="ServerExecuteAlertJavaScript" />
</ContentTemplate>
</asp:UpdatePanel><br />
<asp:Button ID="Button3" runat="server" Text="Block out of UpdatePanel" OnClick="Button1_Click" CommandName="BlockOutOfUpdatePanel" />
</div>
</form>
</body>
</html>
一般的情況,這樣就夠了。
但是,當我們在server端註冊JavaScript的alert時,例如要呈現『存檔成功』、『存檔失敗』時,則alert會失效,且可能會影響到我們alertFlag的初始值。所以我們這邊在透過一個小技巧,通常我會將註冊JavaScript alert訊息的程式封裝在BasePage裡,讓一般繼承BasePage的頁面可以更加focus在他們自己要處理的邏輯上。所以我們每次呼叫alert()的時候,都加上setTimeout,延遲0秒,來強迫server端註冊的alert可以按照我們要的順序執行。
Server端的程式如下:
protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(2000);
Button o = sender as Button;
this.Label1.Text =string.Format("trigger by {0}, in {1}",o.CommandName, DateTime.Now.ToString());
}
protected void Button2_Click(object sender, EventArgs e)
{
string message = "存檔失敗或是成功";
System.Threading.Thread.Sleep(2000);
this.AlertMessage(message);
}
/// <summary>
/// alert訊息
/// </summary>
/// <param name="message">要alert的訊息</param>
public void AlertMessage(string message)
{
string js = "setTimeout(function() { alert('" + EscapeStringForJS(message) + "'); alertFlag = false;},0);";
RegisterStartupJS(message, js);
}
public void RegisterStartupJS(string RegisterName, string myJavascript)
{
//wholeJS=EscapeStringForJS(wholeJS);
if (ExistSM())
{ ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), RegisterName, myJavascript, true); }
else
{ Page.ClientScript.RegisterStartupScript(Page.GetType(), RegisterName, myJavascript,true); }
}
private bool ExistSM()
{
return (ScriptManager.GetCurrent(this.Page) != null);
}
public static string EscapeStringForJS(string s)
{
//REF: http://www.javascriptkit.com/jsref/escapesequence.shtml
return s.Replace(@"\", @"\\")
.Replace("\b", @"\b")
.Replace("\f", @"\f")
.Replace("\n", @"\n")
.Replace("\0", @"\0")
.Replace("\r", @"\r")
.Replace("\t", @"\t")
.Replace("\v", @"\v")
.Replace("'", @"\'")
.Replace(@"""", @"\""");
}
畫面
-
我們測試一般的按鈕,class設定為UnBlock的按鈕,有註冊confirm的按鈕,有註冊alert的按鈕,以及server端註冊呼叫alert()的情況。最後我們還擺了一個在UpdatePanel外面的按鈕來確認功能無誤。
-
點了Alert的按鈕之後,只有Alert,按完確定,沒有BlockUI的效果
-
點了ServerExecuteJavaScript後,會出現blockUI的效果,並且跳出alert訊息
-
點了Confirm之後,當點選『確定』,則出現blockUI效果,點選『取消』則恢復初始狀態
-
點選UnBlock的按鈕,則沒有blockUI的效果,只有非同步的postback
-
不論點選在UpdatePanel內的按鈕或UpdatePanel外的按鈕,都會成功呼叫blockUI
結論
當大家知道上面提到的幾個issue與Tips之後,就可以將對應的code封裝到獨立的js檔,或MasterPage中囉。讓其他開發人員只要遵守selector的原則,以及使用底層的方法,即可讓系統有一致的Block效果。
[註1]請將註冊BlockUI效果的方式,由bind改成live,以避免多次註冊影響confirm等override 原有js function的效果。例如:
$(':button:not(.UnBlock), :submit:not(.UnBlock), .blockUI').live('click', myBlockUI);
Sample Code: CommonFunctionWithBlockUI.zip
Reference:
blog 與課程更新內容,請前往新站位置:http://tdd.best/