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?

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74

3 Answers3

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;
    }
}
shannon
  • 8,664
  • 5
  • 44
  • 74
  • 4
    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! – superlogical May 10 '11 at 16:12
  • This seems to be the cleanest/simplest solution. Thank you. – Kon Feb 14 '12 at 21:27
  • +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". – REMESQ Mar 02 '12 at 00:44
  • 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. – shannon Apr 25 '13 at 10:13
  • I've spent more than 2 hours on this problem. Very clean and correct solution. – POIR Apr 17 '14 at 11:01
  • thanks for this! Just adding $.validator.unobtrusive.adapters.addBool("mandatory", "required"); and – kiev May 09 '18 at 15:52
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));
Tushar
  • 85,780
  • 21
  • 159
  • 179
Paul Hiles
  • 9,558
  • 7
  • 51
  • 76
  • 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? – shannon Feb 08 '11 at 17:03
  • 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. – Paul Hiles Feb 08 '11 at 17:26
  • 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? – shannon Feb 08 '11 at 17:51
  • Or a link or description of their relevance or anything :) – shannon Feb 08 '11 at 17:53
  • 2
    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? – shannon Feb 09 '11 at 07:14
  • Generally that's done to prevent a clash of the $ variable shortcut. – Kzqai Oct 14 '11 at 23:41
  • Personally I just find it easier to use `jQuery` directly. It's less ambiguous that way. :) – bambams Nov 22 '12 at 21:11
  • I also glanced through this code too quickly when I initially implemented it. Adding the adapter should happen OUTSIDE of a document ready. – Erik W Sep 05 '13 at 16:51
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>
shannon
  • 8,664
  • 5
  • 44
  • 74
vikram
  • 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. – shannon Feb 19 '11 at 21:37