先講一個好消息,不讓ASP.NET MVC專美於前,謙卑式檢核(Unobtrusive Validation)在ASP.NET 4.5 WebForm將成為內建功能囉! 聽到這消息讓我格外振奮,它意味著: 1) 現在已投入的研究心得及開發出來的擴充模組,將來肯定能更廣泛應用,2) 未來寫ASP.NET 4.5 WebForm時,不用額外手工加掛檢核,更加省事 3) 我又押對寶了 ^__^。

這篇要談一個常見問題: 謙卑式檢核的寫法主要透過在特定欄位元素(INPUT、SELECT)加上data-val-* Attributes實現檢核規則設定,直覺上所有檢核都針對該欄位,但在實務上,某些檢核邏輯涉及的欄位元素常不只一個。舉例來說,某個申辦門號註冊網頁,使用者可透過Checkbox複選免費加值服務,而可選取的服務數目有上限,或具有"選A服務就不能再挑B服務"之類的特殊限制。這類的檢核要求,看起來無法單靠在Checkbox加註data-val-*搞定,多半需要額外加工,以下便是我自己慣用處理方式的分享。

針對這種情境,我多半會在網頁另外偷藏一個<input type="hidden">隱藏欄位,當使用者點選Checkbox時,會觸發程式邏輯將多個Checkbox的勾選結果合併成單一字串(如"選項1|選項5|選項8")寫入隱藏欄位;而實際送至伺服器端的其實是隱藏欄位中的合併字串,而非各個Checkbox的值。加入這番設計,檢核對象便可縮小到單一隱藏欄位,不用管使用者實際勾選的那些Checkbox。不過因為我在專案中大量使用內嵌式的檢核訊息顯示,使用<input type="hidden" />無法決定訊息顯示位置,得改用<input type="text" style="visibility: hidden; width: 0px" />,才能可以滿足"隱藏不可見,但在網頁上有其顯示位置座標"的要求。

下一步要將Checkbox的勾選結果即時反應到隱藏欄位上,決定還是採Unobtrusive方式謙卑地完成才夠酷。在隱藏欄位上定義data-multi-sel-cbx-selector Attribute,指定"input.boo-checkes"、"#check-group input:checkbox"之類jQuery選擇器,定義其關聯的Checkbox。程式會對這些Checkbox加上Click事件,一旦任何一個被勾選或解除勾選,就重新找出這群Checkbox中被勾選者,取其值以特定字元(如"|")合併成單一字串,將其設為隱藏欄位的內容。合併時用來分隔的字元如果不要用預設的"|",也可以透過data-multi-sel-sep-char Attribute另行指定。此外,某些時候會透過程式變動隱藏欄位的值,希望也要能反應為Checkbox的勾選狀態,做法是宣告"mapValue"事件加入邏輯,在修改隱藏欄位內容後觸發即可,例如: $("#hidden_input").val("option1|option2").trigger("mapValue");

以上工作完成後,剩下的就跟一般掛檢核邏輯的做法沒什麼兩樣,透過data-val-required, data-val-custRule對隱藏欄位加上必選或自訂檢核規則就OK了。

來看個實例! 假想市調問卷有個題目,要求使用者複選自己最常用的電子小裝置,至少一樣,最多三樣。我們可將隱藏欄位放在所有Checkbox的最前方,讓內嵌式檢核訊息出現在左上角(或者你也可以任意調整隱藏欄位的擺放位置,高興就好)。其他部分如先前的介紹,透過data-multi-sel-cbx-selector="#ulGadgets :checkbox"指定隱藏欄位要跟哪些Checkbox關聯,然後靠data-val-required 限定至少要選一個,再加上自訂檢核條件(data-val-multiSelect)限制不可選取超過3個。

以下是程式碼,另有線上展示可供把玩,歡迎大家參考回饋。

<!DOCTYPE html>
 
<html>
<head>
    <title>Multiple-Selection Field Validation Example</title>
    <script src="Scripts/jquery-1.6.3.js"> </script>
    <script src="Scripts/jquery.validate.js"> </script>
    <script src="Scripts/jquery.validate.unobtrusive.js"> </script>
    <script src="Scripts/jquery.validate.inline.js"> </script>
    <link href="Content/validationEngine.jquery.css" rel="stylesheet" type="text/css" />
    <script>
        $(function () {
            //透過Unobtrusive方式將隱藏欄位能自動取得Checkbox的勾選結果 
            //實務上這段Script可提取成獨立JS檔,方便重複利用
 
            //找出有設data-multi-sel-cbx-selector的欄位
            $("input[data-multi-sel-cbx-selector]").each(function () {
                var $input = $(this);
                //若有data-multi-sel-sep-char,就使用自訂的分隔字元,
                //未指定定時,預設使用"|"分隔
                var sepChar = $input.data("multiSelSepChar") || "|";
                //由data-multi-sel-cbx-selector取出jQuery selector 
                //找到其關聯的Checkbox進行雙向繫結
                var $cbxes = $($input.data("multiSelCbxSelector"));
                //在每一個關聯Checkbox上加掛Click事件
                $cbxes.click(function () {
                    //將所有勾選的值串成單一字串
                    var ary = [];
                    $cbxes.filter(":checked").each(function () {
                        ary.push(this.value);
                    });
                    //將合併的單一字串設為隱藏欄位的值
                    //並呼叫"focusout"事件立即觸發檢核
                    $input.val(ary.join(sepChar)).focusout();
                });
                //另外宣告一個mapValue事件,用來將隱藏欄位內容反應成Checkbox勾選狀態
                $input.bind("mapValue", function () {
                    var ary = this.value.split(sepChar);
                    $cbxes.each(function () {
                        //由Checkbox value是否在其中決定checked
                        this.checked = $.inArray(this.value, ary) != -1;
                    });
                });
            });
        });
    </script>
    <script>
        //自訂檢核邏輯,以|分隔字元拆解出複選值陣列
        //由陣列數目限定複選數量不可超過3個
        jQuery.validator.addMethod("multiSelectChk",
            function (value, elem, params) {
                var count = value.split('|').length;
                if (count > 3) return false;
                return true;
            }, '');
            jQuery.validator.unobtrusive.adapters.add(
            "multiSelect", [],
            function (options) {
                options.rules["multiSelectChk"] = true;
                options.messages['multiSelectChk'] = options.message;
            });
            $(function () {
                $("#form1").makeValidationInline();
            });
    </script>
    <style>
        body,table,input { font-size: 9pt; }
        td { border: 1px solid gray; padding: 5px; }
        #ulGadgets 
        {
            list-style-type: none; margin: 0px; 
            padding: 0px;
        }
        #ulGadgets li { float: left; }
    </style>
</head>
<body>
<form id="form1" method="get" action="MultSelFieldValidation.htm">
    <table style="margin-top: 50px;">
        <tr>
        <td style="width: 150px; text-align: right; vertical-align: top;">
        Frequently Used Gadgets</td>
        <td style="width: 400px">
            <input type="text" id="txtGadgets" name="txtGadgets" 
             style="visibility: hidden; position: absolute; width: 0px;"
             data-val="true" data-val-required="Please choose at least one option"
             data-val-multiSelect="You cannot choose more than 3 options" 
             data-multi-sel-cbx-selector="#ulGadgets :checkbox"
             />
            <ul id="ulGadgets">
                <li><input type="checkbox" value="Watch" />Watch</li>
                <li><input type="checkbox" value="MP3Player" />MP3 Player</li>
                <li><input type="checkbox" value="FlashDrive" />USB Flash Drive</li>
                <li><input type="checkbox" value="MobiePhone" />Mobile Phone</li>
                <li><input type="checkbox" value="PDA" />PDA</li>
                <li><input type="checkbox" value="GPS" />GPS</li>
                <li><input type="checkbox" value="Tablet" />Tablet or Slate PC</li>
            </ul>
            <div style="clear: both; margin: 5px; color: Gray;">
            (Multiple selection, 
            please select at least one, but not more than three)</div>
        </td>
        </tr>
    </table>
    <input type="submit" value="Submit" />
</form>
</body>
</html>

Comments

Be the first to post a comment

Post a comment


56 + 42 =