MVC3: make checkbox required via jQuery validate?
Asked Answered
C

3

45

I want my "Agree To Terms" checkbox to be mandatory using jQuery validate, in an MVC3 project. I currently get server/client DRY/SPOT validation from "MS data annotation attributes" + "MS MVC3 unobtrusive jQuery validation".

Here's a stand-alone test (plain HTML generated by MVC3). Why doesn't it work, please? When run, validation ensures the "Contact Name" field is filled, but doesn't care about the state of the checkbox.

<!DOCTYPE html>

<html>
<head>
    <title>RequiredCheckbox</title>

    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
    <script type="text/javascript" src="//ajax.microsoft.com/ajax/jQuery.Validate/1.7/jQuery.Validate.js"></script>
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.js"></script>
    <script type="text/javascript" language="javascript">
        $(function () {
            // http://itmeze.com/2010/12/checkbox-has-to-be-checked-with-unobtrusive-jquery-validation-and-asp-net-mvc-3/
            $.validator.unobtrusive.adapters.add("mandatory", function (options) {
                    options.rules["required"] = true;
                    if (options.message) {
                        options.messages["required"] = options.message;
                    }
                }
            });
            $.validator.unobtrusive.parse(document);
        });
    </script>

</head>

<body>
    <div>
        <form>
            <input data-val="true" data-val-mandatory="The field Terms Are Accepted is invalid." id="isTermsAccepted" name="isTermsAccepted" type="checkbox" value="true" />
            <input name="isTermsAccepted" type="hidden" value="false" />
            <span class="field-validation-valid" data-valmsg-for="isTermsAccepted" data-valmsg-replace="true"></span>

            <input data-val="true" data-val-required="The Contact Name field is required." id="contactName" name="contactName" type="text" value="" />
            <span class="field-validation-valid" data-valmsg-for="contactName" data-valmsg-replace="true"></span>
            <button type="submit">Submit</button>
        </form>
    </div>
</body>
</html>





The rest of this post is just my research notes.

Setting data annotation attribute [required] doesn't help:
http://forums.89.biz/forums/MVC+3+Unobtrusive+validation+does+not+work+with+checkboxes+(jquery+validation)+and+the+fix+for+it.

That's fine. What "required" means for a checkbox is obviously a holy war I don't want to wade into, where MS thought they knew better than the jquery team. Coercing it locally should be a simple matter of:
$("form").validate({ rules: { cbAgreeToTerms: "required" } });

...right? no, because of:
http://blog.waynebrantley.com/2011/01/mvc3-breaks-any-manual-use-of-jquery.html
http://pinoytech.org/question/4824071/microsofts-jquery-validate-unobtrusive-makes-other-validators-skip-validation

WHAT? That's pretty stinking cheezy! (IMHO, of course.)

Now, I've tried this solution:
http://itmeze.com/2010/12/checkbox-has-to-be-checked-with-unobtrusive-jquery-validation-and-asp-net-mvc-3/
and it didn't work for me. This author's dangling comments and somewhat cargo-cult use of the inverted CHECKBOX test from earlier in his/her article make me wonder if it actually works for him/her, then what other voodoo was involved?

Note I think that the last snippet of JS is equivalent to the cleaner:
$.validator.unobtrusive.adapters.addBool("brequired", "required"); That was suggested by the last post in:
http://forums.asp.net/p/1648319/4281140.aspx#4281140
But notice that author comments that he hadn't debugged it yet. It didn't work for me, and reading between the lines, I think he means it didn't work for him?

The unobtrusive.js calls parse on docready, so I tried calling that, but it didn't help me.
$.validator.unobtrusive.parse(document); I've also found a few similar articles and none talk about requiring initialization of any sort. Maybe they are all locally editing the original/public unobtrusive.js? I'd rather not if I can help it, isn't that what the adapters are for?

I found stack-overflow articles, much the same, as well as more complex examples:
ASP .Net MVC 3 unobtrusive custom client validation
Perform client side validation for custom attribute
http://xhalent.wordpress.com/2011/01/27/custom-unobstrusive-jquery-validation-in-asp-net-mvc-3/
But I don't see anything there that is different than what I've already tried.

Is this really working for people? Why can't I get it to work for me?

Convolvulus answered 8/2, 2011 at 14:7 Comment(1)
After seeing all the responses, why can't you simply use an Equals(true) attribute?Gautama
I
32

Just change your javascript to this:

(function ($) {
    // http://itmeze.com/2010/12/checkbox-has-to-be-checked-with-unobtrusive-jquery-validation-and-asp-net-mvc-3/
    $.validator.unobtrusive.adapters.add("mandatory", function (options) {
        options.rules["required"] = true;
        if (options.message) {
            options.messages["required"] = options.message;
        }                
    });            
} (jQuery));

You do not actually need to write your own adapter though and can just use:

(function ($) {
    $.validator.unobtrusive.adapters.addBool("mandatory", "required");
} (jQuery));
Interbedded answered 8/2, 2011 at 16:40 Comment(8)
Awesome, worked like a charm! I don't understand it though... while I was debugging (before your advice) addBool had already added the adapter to the existing array (count of 14 or something). Doesn't that imply I was already in the correct context?Convolvulus
Not sure if it is what you mean, but your previous code did not execute until the document was loaded and internally, jquery.validate.unobtrusive had already called parse. I know you called parse again, but this does not work.Interbedded
Ok, I get that it wasn't a matter of context, but order. I'm still deer-in-headlights on the syntax you used. What are the ($) and (jQuery) called, so I can look them up?Convolvulus
Or a link or description of their relevance or anything :)Convolvulus
nevermind, it is obvious after much-needed sleep. it's just direct invocation of an anonymous function. Any particular reason for that, rather than just directly calling the contained method?Convolvulus
Generally that's done to prevent a clash of the $ variable shortcut.Showing
Personally I just find it easier to use jQuery directly. It's less ambiguous that way. :)Sorites
I also glanced through this code too quickly when I initially implemented it. Adding the adapter should happen OUTSIDE of a document ready.Biscuit
C
61

I've summarized here the correctly-working source code, which resulted from applying the accepted answer. Hope you find it useful.

RequiredCheckbox.aspx

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<RegistrationViewModel>" %>

<!DOCTYPE html>
<html>
<head runat="server">
    <title>RequiredCheckbox</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
    <script src="//ajax.microsoft.com/ajax/jQuery.Validate/1.7/jQuery.Validate.js" type="text/javascript"></script>
    <script src="//ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.js" type="text/javascript"></script>
    <script type="text/javascript" language="javascript">
        $.validator.unobtrusive.adapters.addBool("mandatory", "required");
    </script>
</head>
<body>
    <div>
        <% 
        // These directives can occur in web.config instead
        Html.EnableUnobtrusiveJavaScript();
        Html.EnableClientValidation();
        using (Html.BeginForm())
        { %>
            <%: Html.CheckBoxFor(model => model.IsTermsAccepted)%>
            <%: Html.ValidationMessageFor(model => model.IsTermsAccepted)%>

            <%: Html.TextBoxFor(model => model.ContactName)%>
            <%: Html.ValidationMessageFor(model => model.ContactName)%>
            <button type="submit">Submit</button>
        <% } %>
    </div>
</body>
</html>

RegistrationViewModel.cs

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public class RegistrationViewModel {
    [Mandatory (ErrorMessage="You must agree to the Terms to register.")]
    [DisplayName("Terms Accepted")]
    public bool isTermsAccepted { get; set; }

    [Required]
    [DisplayName("Contact Name")]
    public string contactName { get; set; }
}

MandatoryAttribute.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

public class MandatoryAttribute : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        return (!(value is bool) || (bool)value);
    }
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        ModelClientValidationRule rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
        rule.ValidationType = "mandatory";
        yield return rule;
    }
}
Convolvulus answered 22/3, 2011 at 0:40 Comment(6)
Legend thanks for spending the time to work this out! It still amazes me the amount of plumbing code like this I have to add on every single asp.net mvc project!Bream
This seems to be the cleanest/simplest solution. Thank you.Bryner
+1 Excellent solution for an unrelated checkbox problem I had: using a jquery wizard and separate show/hide jquery for a dropdown (which then shows other inputs if "Yes" is selected). The dropdown had a binder attached and modified MS js code but it wouldn't fire the checkbox validation within. Oddly, the controllers modelValid code got the checkbox validation to fire, but not on the wizard step - only after hitting "submit".Secor
FYI, I just had to do this again, and I still don't see a more "correct" solution. I tried to hack the client adapter with a regex, equalsTo, and min/max, but the jquery.validate call to optional() makes all of those ineffective.Convolvulus
I've spent more than 2 hours on this problem. Very clean and correct solution.Cherycherye
thanks for this! Just adding $.validator.unobtrusive.adapters.addBool("mandatory", "required"); and <input type="checkbox" data-val="true" data-val-mandatory="true" .... worked for meCostanzo
I
32

Just change your javascript to this:

(function ($) {
    // http://itmeze.com/2010/12/checkbox-has-to-be-checked-with-unobtrusive-jquery-validation-and-asp-net-mvc-3/
    $.validator.unobtrusive.adapters.add("mandatory", function (options) {
        options.rules["required"] = true;
        if (options.message) {
            options.messages["required"] = options.message;
        }                
    });            
} (jQuery));

You do not actually need to write your own adapter though and can just use:

(function ($) {
    $.validator.unobtrusive.adapters.addBool("mandatory", "required");
} (jQuery));
Interbedded answered 8/2, 2011 at 16:40 Comment(8)
Awesome, worked like a charm! I don't understand it though... while I was debugging (before your advice) addBool had already added the adapter to the existing array (count of 14 or something). Doesn't that imply I was already in the correct context?Convolvulus
Not sure if it is what you mean, but your previous code did not execute until the document was loaded and internally, jquery.validate.unobtrusive had already called parse. I know you called parse again, but this does not work.Interbedded
Ok, I get that it wasn't a matter of context, but order. I'm still deer-in-headlights on the syntax you used. What are the ($) and (jQuery) called, so I can look them up?Convolvulus
Or a link or description of their relevance or anything :)Convolvulus
nevermind, it is obvious after much-needed sleep. it's just direct invocation of an anonymous function. Any particular reason for that, rather than just directly calling the contained method?Convolvulus
Generally that's done to prevent a clash of the $ variable shortcut.Showing
Personally I just find it easier to use jQuery directly. It's less ambiguous that way. :)Sorites
I also glanced through this code too quickly when I initially implemented it. Adding the adapter should happen OUTSIDE of a document ready.Biscuit
L
0
<asp:Content ID="Content1" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
    <h2>
        Save New Contact</h2>
    <%using (Html.BeginForm("SaveContact", "Contact", FormMethod.Post, new { id = "UserImportTypeForm", @autocomplete = "off" })) %>
    <%{ %>
    <table style="height: 100px;">
        <tr>
            <td>
                Import Type :
            </td>

        </tr>
        <tr>
            <td>
                Is Verified
            </td>
            <td>
                <%-- <%=Html.TextBox("UserContactModel.IsVerified", new SelectList(Model.IsVerified, "IsVerified"), new { })%>>--%>
                <%-- <input type="text" name="txtIsVerified" id="txtIsVerified" />--%>
                <%-- <%= Html.TextBox("txtIsVerified")%>--%>
                <%=Html.CheckBox("SelectedUserContact.IsVerified", Convert.ToBoolean(Model.SelectedUserContact.IsVerified) )%>
                <%=Html.ValidationSummary("txtIsVerified", "*")%>
            </td>
        </tr>
        <tr>
            <td>
                First Name
            </td>
            <td>
                <%--<input type="text" name="txtFirstName" id="txtFirstName" />--%>
                <%=Html.TextBox ("SelectedUserContact.FirstName", Model.SelectedUserContact.FirstName )%>
                <%-- <%=Html.ValidationSummary("FirstName", "*")%>--%>
            </td>
        </tr>
        <tr>
            <td>
                Last Name
            </td>
            <td>
                <%--<input type="text" name="txtLastName" id="txtLastName" />--%>
                <%=Html.TextBox("SelectedUserContact.LastName", Model.SelectedUserContact.LastName)%>
                <%=Html.ValidationSummary("LastName", "*")%>
            </td>
        </tr>
        <tr>
            <td>
                Contact ID
            </td>
            <td>
                <%=Html.TextBox("SelectedUserContact.ContactID",Model.SelectedUserContact.ContactID) %>
                <%=Html.ValidationSummary("ContactID","*") %>
            </td>
        </tr>
        <tr>
            <td align="right">
                <input type="submit" value="Save" name="btnSave" id="btnSave" />
            </td>
            <td>
                <input type="button" value="Cancel" name="btnCancel" id="btnCancel" />
            </td>
        </tr>
    </table>
    <%} %>
    <script src="../../Scripts/jquery.validate.js" type="text/javascript"></script>
    <script language="javascript" type="text/javascript">

        $("#UserImportTypeForm").validate({
            rules:
    {

        "SelectedUserContact.FirstName": { required: true },
        "SelectedUserContact.LastName": { required: true },
        "SelectedUserContact.ContactID": {required:true}
    },
            messages:
    {

        "SelectedUserContact.FirstName": { required: "*" },
        "SelectedUserContact.LastName": { required: "*" },
        "SelectedUserContact.ContactID": { required: "*" },

    }
        });



    </script>
</asp:Content>
Lowering answered 18/2, 2011 at 12:16 Comment(1)
Sorry, it's a little off-target with my goals. I'll edit my initial post to clarify, but when I stipulated "MVC3 + jQuery", I meant to imply using automatically-generated validation rules rather than hand-writing rules. With data annotation and the "unobtrusive validator", you lose access to simultaneously use the "rules" option in the way you show. If there are other better (but still simple) "DRY/SPOT" methods for server+client validation in MVC3 where the approach you show would work, I'm not aware of them.Convolvulus

© 2022 - 2024 — McMap. All rights reserved.