[ASP.NET & jQuery]BlockUI in ASP.NET & AJAX.NET

  • 38007
  • 0
  • 2011-12-21

[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

  1. 我們針對所有的button, submit且其class不為UnBlock,以及所有class為blockUI的DOM,當click的時候,要加上blockUI的效果。
    • 如有其他AutoPostBack為True的控制項需要,可以加載在change的事件
  2. 承接上面的前提,我們要加上例外,也就是當button觸發JavaScript的alert()時,(通常為Validation失敗),不能呼叫$.blockUI()。當button觸發JavaScript的confirm()時,則按確定才能呼叫BlockUI,按取消則不能呼叫BlockUI。
  3. 不管有沒有在UpdatePanel裡面,觸發的是非同步的postback還是正常的postback,都不該影響到我們的需求。且非同步的postback之後,運作應該維持正常。而且我們不希望因為UpdatePanel,而要將aspx上的JavaScript,搬到server端再透過ScriptManager去註冊。


這篇文章用到的外部js有:

  1. jQuery.min.js (1.4.2) : jQuery framework
  2. jQuery.blockUI.js : blockUI的plugin
  3. fauxconsole.js : for IE console.log
  4. jQuery.updatepanel.js : 方便用來處理UpdatePanel非同步postback後,JavaScript仍能正常運作


另外,這篇文章也會提到,如何override JavaScript原生的function,例如alert與confirm。

Solution

  1. 定義一個alertFlag為false,當呼叫alert()或confirm()點選取消時,將alertFlag設定為true。呼叫$.blockUI()前,要先檢查alertFlag為false才呼叫。
  2. 我們的selector: $(':button:not(.UnBlock), :submit:not(.UnBlock), .blockUI')
  3. 透過變數,先暫存原生的window.alert與window.confirm,接著override window.alert以及window.confirm。當需要用到原生的alert()或confirm()時,再使用變數即可。(類似繼承+overrides+ base.function()的意思)
  4. 每次呼叫完$.blockUI()後,要將flag設定回false。
  5. 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 />&nbsp;&nbsp;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(@"""", @"\""");
    }


畫面

  1. 我們測試一般的按鈕,class設定為UnBlock的按鈕,有註冊confirm的按鈕,有註冊alert的按鈕,以及server端註冊呼叫alert()的情況。最後我們還擺了一個在UpdatePanel外面的按鈕來確認功能無誤。
    image
  2. 點了Alert的按鈕之後,只有Alert,按完確定,沒有BlockUI的效果
    image
  3. 點了ServerExecuteJavaScript後,會出現blockUI的效果,並且跳出alert訊息
    image
  4. 點了Confirm之後,當點選『確定』,則出現blockUI效果,點選『取消』則恢復初始狀態
    image

    image
  5. 點選UnBlock的按鈕,則沒有blockUI的效果,只有非同步的postback
    image
  6. 不論點選在UpdatePanel內的按鈕或UpdatePanel外的按鈕,都會成功呼叫blockUI
    image

 

結論
當大家知道上面提到的幾個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: 

  1. UpdatePanel plug-in
  2. jQuery BlockUI plug-in
  3. fauxconsole plug-in
     

blog 與課程更新內容,請前往新站位置:http://tdd.best/