ASP.NET MVC 3 comes with jQuery unobtrusive validation (if you are not familiar with jQuery unobtrusive validation, Brad Wilson has a good article), an elegant way to implement client-side validation.  After learning it, I start to use it widely in my projects, both MVC and WebForm, and I also use inline-style vaidation message plugin to make the webpage layout neat and tidy.  These projects work fine and we even got good feedbacks from users.
(PS: In ASP.NET 3.5/4 WebForm project, you have to add unobtrusive client validation by yourself, but there is good news, unobtrusive validation will be built-in feature in ASP.NET 4.5!)

The jQuery unobtrusive validation is targeting on single element, so we add data-val-* attributes on specific element to set validation rules and messages.  But sometimes the validation logic involves more than one element, using checkboxes for multiple-selection input is a typical scenario.  For example, user can choose optional services while registration, the max number of selected services is limited or some of the services are exclusive, so customized validation is required.

Here is my method to meet this kind of requirement.

First of all, a hidden input is required for collecting checked checkboxes' value as a single string for postbak.  Inline-style validation message need a visible element to get the position on the page for popup-style message display, so <input type="hidden" /> is not suitable here and I use <input type="text" style='visibility: hidden; width: 0px' /> as a solution. ('visibility: hidden' element is invisible but has valid position on webpage, 0px-width can avoid layout change when invisible element inserted.)

The next thing to do is to collect checkboxes' value to the hidden text input.  I decide to use "unobtrusive way" to implement it.  Use data-multi-sel-cbx-selector attribute to set a jQuery selector to find related checkboxes of this input, then add click event on them for value collecting.  When any of the checkboxes is clicked, we try to get values from all checked checkboxes and concatenate these values as a single string, then set it as hidden text input's value.  By default, the separate char is "|" for checkbox values concatenation, but you can assign it by setting data-multi-sel-sep-char attribute, in unobtrusive way again.  When hidden text input's value changed, a "mapValue" event is needed to parse the concatenated string and set mapped checkboxes' checked property.  The usage of mapValue event is also easy: $("#hidden_input").val("option1|option2").trigger("mapValue");

After binding between hidden text input and checkboxes is done, now we can use normal unobtrusive validation on the hidden text input, like data-val-required, or data-val-custRule for customized validation logic, then everything is done.

Now let's try it in a simple scenario.  It's a question in marketing survey to ask users to choose at least one, but not more than 3 options as their frequently used gadgets.  The hidden text input is put in front of all checkboxes to make sure the inline invalidation message will display at the upper-left corner of the <td>, or you can put it on any position you want.  The rest steps have been explained as above, use data-multi-sel-cbx-selector="#ulGadgets :checkbox" to bind following checkboxes to the hidden text input, and add data-val-required to make sure at least one option is selected and use customized validation rule to check the count of selected options is not more than three.

Here is the source and you can try the online demo.

<!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 () {
            //Binding checkboxes to multiple seletion field in unobtrusive way. 
            //This script section can be saved as separate js file for reuse
 
            //Use data-multi-sel-cbx-selector attribute to set binding
            $("input[data-multi-sel-cbx-selector]").each(function () {
                var $input = $(this);
                //Get separate char from data-multi-sel-sep-char attribute,
                //if not set, use "|" as default
                var sepChar = $input.data("multiSelSepChar") || "|";
                //Use data-multi-sel-cbx-selector as jQuery selector 
                //to find checkboxes for two-way binding
                var $cbxes = $($input.data("multiSelCbxSelector"));
                //Attach a click event to each checkbox
                $cbxes.click(function () {
                    //Join all checked values as a string
                    var ary = [];
                    $cbxes.filter(":checked").each(function () {
                        ary.push(this.value);
                    });
                    //Set the result string to the target input
                    //and fire "focusout" event to trigger validation
                    $input.val(ary.join(sepChar)).focusout();
                });
                //A new event to map $input's value to checkboxes
                $input.bind("mapValue", function () {
                    var ary = this.value.split(sepChar);
                    $cbxes.each(function () {
                        //If the checkbox's value is in array, check it
                        this.checked = $.inArray(this.value, ary) != -1;
                    });
                });
            });
        });
    </script>
    <script>
        //Custom validation rule, parse value as | separated value
        //and make sure the array elements count not more than than 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