MVC unobtrusive validation on checkbox not working
Asked Answered
R

6

24

I'm trying to implement the code as mentioned in this post. In other words I'm trying to implement unobtrusive validation on a terms and conditions checkbox. If the user hasn't selected the checkbox, then the input should be marked as invalid.

This is the server side Validator code, I've added:

/// <summary>
/// Validation attribute that demands that a boolean value must be true.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return value != null && value is bool && (bool)value;
    }
}

This is the model

[MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
[DisplayName("Accept terms and conditions")]
public bool AcceptsTerms { get; set; }

This is my view:

@Html.EditorFor(x => x.AcceptTermsAndConditions)
@Html.LabelFor(x => x.AcceptTermsAndConditions)
@Html.ValidationMessageFor(x => x.AcceptTermsAndConditions)

and this is the jQuery I've used to attach the validator client side:

$.validator.unobtrusive.adapters.addBool("mustbetrue", "required");

The client side script doesn't appear to be kicking in, however. Whenever I press the submit button, validation on the other fields kicks in fine, but the validation for the Terms & conditions doesn't seem to kick in. This is how the code looks in Firebug after I've clicked the submit button.

<input type="checkbox" value="true" name="AcceptTermsAndConditions" id="AcceptTermsAndConditions" data-val-required="The I confirm that I am authorised to join this website and I accept the terms and conditions field is required." data-val="true" class="check-box">
<input type="hidden" value="false" name="AcceptTermsAndConditions">
<label for="AcceptTermsAndConditions">I confirm that I am authorised to join this website and I accept the terms and conditions</label>
<span data-valmsg-replace="true" data-valmsg-for="AcceptTermsAndConditions" class="field-validation-valid"></span>

Any ideas? Have I missed out a step? This is driving me potty!

Thanks in advance S

Realism answered 3/8, 2011 at 8:9 Comment(1)
Couldn't you just use the [Requred] attribute instead of creating your own MustBeTrueAttribute?Antihelix
C
26

Sniffer,

In addition to implementing Darin's solution, you also need to modify the file jquery.validate.unobtrusive.js. In this file, you must add a "mustbetrue" validation method, as follows:

$jQval.addMethod("mustbetrue", function (value, element, param) {
    // check if dependency is met
    if (!this.depend(param, element))
        return "dependency-mismatch";
    return element.checked;
});

Then (I forgot to add this at first), you must also add the following to jquery.validate.unobtrusive.js:

adapters.add("mustbetrue", function (options) {
    setValidationValues(options, "mustbetrue", true);
});

counsellorben

Confined answered 3/8, 2011 at 18:38 Comment(6)
This is awesome and what I was missing! Brilliant. Wish I could mark both you and Darin as the answer, but you get it as you got me over the finishing line! One final question to tidy this up a bit - is there a way of adding these 2 pieces of code without having to change the core unobtrusive.js script? I tried changing $jQval.addMehod to $.validator.addMethod and adapters.add to $.validator.unobtrusive.adapters.add and calling them from an external script file, but this didn't seem to work. Any ideas?Realism
Ahh, apologies - I was a bit quick off the mark there. It's not quite working. The validation now shows up as an error if the checkbox is not ticked (great!), but is also still showing as invalid if the user then goes and checks the checkbox (not great!). this is now stopping the page from posting back as it thinks there is still an error. Any ideas?Realism
@Sniffer, sorry, but this is what happens when I think I'm slick, and don't doublecheck. The line "return this.checked;" was incorrect, and I have changed it. I tried several methods to insert the script into the page, instead of modifying jquery.validate.unobtrusive.js, but none was successful.Confined
I could kiss you (but I obviously won't)! element.checked worked a treat! It's weird that you can't add the script without changing the jquery.validate.unobtrusive.js directly. I'll play about with it some more - maybe it's worth separate SO question. Anyway I'll post an update here if I find a way. Thanks again!Realism
The setValidationValues() function is internal to jquery.validate.unobtrusive.js, you cannot use it from another js file. Just copy it, the code is trivial. See also https://mcmap.net/q/367483/-mvc3-make-checkbox-required-via-jquery-validate.Alrzc
I'm using $.validator.addMethod() and $.validator.unobtrusive.adapters.add() to add custom validators externally.Brace
B
37

You need to implement IClientValidatable on your custom attribute in order to tie the mustbetrue adapter name that you are registering on the client side with this attribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        return value != null && value is bool && (bool)value;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "mustbetrue"
        };
    }
}

UPDATE:

Full working example.

Model:

public class MyViewModel
{
    [MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
    [DisplayName("Accept terms and conditions")]
    public bool AcceptsTerms { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel();
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

@model MyViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    $.validator.unobtrusive.adapters.addBool("mustbetrue", "required");
</script>

@using (Html.BeginForm())
{
    @Html.CheckBoxFor(x => x.AcceptsTerms)
    @Html.LabelFor(x => x.AcceptsTerms)
    @Html.ValidationMessageFor(x => x.AcceptsTerms)
    <input type="submit" value="OK" />
}
Bearden answered 3/8, 2011 at 10:15 Comment(6)
I though you had it there Dann (you've certainly got me further along the path), but it's still not working on Client side. Is the javascript correct? $.validator.unobtrusive.adapters.addBool("mustbetrue", "required");Realism
Dann - I've done all this and I still can't get my example to work. The problem is that the HTML for the input always stays the same no matter what (see html in original question although data-val-mustbetrue is now appearing!). The data-val is always set to true no matter what - even on page load. Is this what you would expect? I've even changed my javascript to just $.validator.unobtrusive.adapters.addBool("mustbetrue"); and I'm still getting the data-val-required attribute in the HTML, which I wasn't expecting... Any ideas?Realism
@Sniffer, you are getting the data-val-required attribute because your AcceptsTerms property is of non-nullable boolean property, so ASP.NET MVC automatically appends it. This is the expected behavior.Bearden
Works great! In order to use a localized error message (with ErrorMessageResourceType and ErrorMessageResourceName) instead of the default required message on the client side, I had to change one line in the Attribute definition to ErrorMessage = FormatErrorMessage(metadata.DisplayName),Wildon
Just a useful information to add here. This line $.validator.unobtrusive.adapters.addBool("mustbetrue", "required"); must be added inside the <script type="text/javascript"> just like Dimitrov have. If you add it for example inside the $(document).ready(function () { it won't work. And you don't need to add anything on the jquery.validate.unobtrusive file.Foldboat
I have notice a bug in the DOM, probably because of unobtrusive script? if checkbox is not selected, every time I hit submit button, the aria-describedby attribute of the checkbox is added again and againSkepful
C
26

Sniffer,

In addition to implementing Darin's solution, you also need to modify the file jquery.validate.unobtrusive.js. In this file, you must add a "mustbetrue" validation method, as follows:

$jQval.addMethod("mustbetrue", function (value, element, param) {
    // check if dependency is met
    if (!this.depend(param, element))
        return "dependency-mismatch";
    return element.checked;
});

Then (I forgot to add this at first), you must also add the following to jquery.validate.unobtrusive.js:

adapters.add("mustbetrue", function (options) {
    setValidationValues(options, "mustbetrue", true);
});

counsellorben

Confined answered 3/8, 2011 at 18:38 Comment(6)
This is awesome and what I was missing! Brilliant. Wish I could mark both you and Darin as the answer, but you get it as you got me over the finishing line! One final question to tidy this up a bit - is there a way of adding these 2 pieces of code without having to change the core unobtrusive.js script? I tried changing $jQval.addMehod to $.validator.addMethod and adapters.add to $.validator.unobtrusive.adapters.add and calling them from an external script file, but this didn't seem to work. Any ideas?Realism
Ahh, apologies - I was a bit quick off the mark there. It's not quite working. The validation now shows up as an error if the checkbox is not ticked (great!), but is also still showing as invalid if the user then goes and checks the checkbox (not great!). this is now stopping the page from posting back as it thinks there is still an error. Any ideas?Realism
@Sniffer, sorry, but this is what happens when I think I'm slick, and don't doublecheck. The line "return this.checked;" was incorrect, and I have changed it. I tried several methods to insert the script into the page, instead of modifying jquery.validate.unobtrusive.js, but none was successful.Confined
I could kiss you (but I obviously won't)! element.checked worked a treat! It's weird that you can't add the script without changing the jquery.validate.unobtrusive.js directly. I'll play about with it some more - maybe it's worth separate SO question. Anyway I'll post an update here if I find a way. Thanks again!Realism
The setValidationValues() function is internal to jquery.validate.unobtrusive.js, you cannot use it from another js file. Just copy it, the code is trivial. See also https://mcmap.net/q/367483/-mvc3-make-checkbox-required-via-jquery-validate.Alrzc
I'm using $.validator.addMethod() and $.validator.unobtrusive.adapters.add() to add custom validators externally.Brace
B
5

I'm unsure why this didn't work for me, but I opted to use your code and do something slightly different.

On my JavaScript load I add the following, this makes the checkbox fire the unabtrusive validation if, either you select the checkbox and uncheck it. Also, if you submit the form.

$(function () {
        $(".checkboxonblurenabled").change(function () {
            $('form').validate().element(this);
        });
});

You also need to add the CSS class to your checkbox, like so.

@Html.CheckBoxFor(model => model.AgreeToPrivacyPolicy, new { @class = "checkboxonblurenabled"})

So, we now need to hook up the model and put in out class to handle the server side validation (which i'm re-using from above) but changing the unobtrusiveness slightly.

Here's the customer attribute that extends IClientValidate as in the above example...

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        return value != null && value is bool && (bool)value;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "mustbetrue"
        };
    }
}

In your model, object property set the desired attribute notations

 [MustBeTrue(ErrorMessage = "Confirm you have read and agree to our privacy policy")]
    [Display(Name = "Privacy policy")]
    public bool AgreeToPrivacyPolicy { get; set; }

Okay, we're ready to put in the JavaScript.

(function ($) {
    /*
    START CHECK MUST BE TRUE - UNOBTRUSIVE JAVASCRIPT
    START CHECK MUST BE TRUE - UNOBTRUSIVE JAVASCRIPT
    START CHECK MUST BE TRUE - UNOBTRUSIVE JAVASCRIPT
    */
    jQuery.validator.unobtrusive.adapters.add("mustbetrue", ['maxint'], function (options) {
        options.rules["mustbetrue"] = options.params;
        options.messages["mustbetrue"] = options.message;
    });

    jQuery.validator.addMethod("mustbetrue", function (value, element, params) {

        if ($(element).is(':checked')) {
            return true;
        }
        else {
            return false;
        }
    });
    /*
    START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
    START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
    START CHECK MAX INT - UNOBTRUSIVE JAVASCRIPT
    */



} (jQuery));

What makes this work, is... well. After looking at the HTML markup after trying to do the suggested answer above, my values were all set to true, however my checkbox checked was false. So, I decided to let jQuery work it out using IsChecked

Buddybuderus answered 24/4, 2012 at 15:33 Comment(1)
>> i'm unsure why this didn't work for me me neither :D But your solution is complete and perfect. Thank you really. Great idea. +1 Cheers.Xeniaxeno
S
2

For those that none of these solutions are working:

I'm working with Razor MVC 4 using .Net Framework 4, and the latest jquery validate script files.

After implementing the custom attribute validation in client and server side, it still doesn't work. My form is being post anyway.

So here is the catch: JQuery validate script has a default setting of ignore hidden tags where hidden is http://api.jquery.com/hidden-selector/, that won't be a problem normally but the @Html.CheckBoxFor style that I'm using is customized with a CSS3 style that changes the display to none and a custom image of the checkbox is displayed, so it will never execute the validation rule on the checkbox.

My workaround was adding this line before the custom client validation rule declaration:

$.validator.defaults.ignore = "";

what it does is override the ignore setting for all validations in the current page, notice that now it could execute validations on hidden field too (a side effect).

Serapis answered 17/12, 2013 at 18:3 Comment(1)
This was killing me, until I remembered the front-end was using a bunch of custom UI elements, hiding my input. The hacky solution was to position the input offscreen rather than hide it.Folks
F
1
<script>
    $(function () {
        $('#btnconfirm').click(function () {
            if ($("#chk").attr('checked') !== undefined ){
                return true;
            }
            else {

                alert("Please Select Checkbox ");
                return false;
            }
        });

    });
</script>
<div style="float: left">
                    <input type="checkbox" name="chk" id="chk"  />
                    I read and accept the terms and Conditions of registration
                </div>
  <input type="submit" value="Confirm"  id="btnconfirm" />
Freedafreedman answered 26/2, 2014 at 11:19 Comment(0)
C
0
/// <summary> 
///  Summary : -CheckBox for or input type check required validation is not working the root cause and solution as follows
///
///  Problem :
///  The key to this problem lies in interpretation of jQuery validation 'required' rule. I digged a little and find a specific code inside a jquery.validate.unobtrusive.js file:
///  adapters.add("required", function (options) {
///  if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
///    setValidationValues(options, "required", true);
///    }
///   });
///   
///  Fix: (Jquery script fix at page level added in to check box required area)
///  jQuery.validator.unobtrusive.adapters.add("brequired", function (options) {
///   if (options.element.tagName.toUpperCase() == "INPUT" && options.element.type.toUpperCase() == "CHECKBOX") {
///              options.rules["required"] = true;
///   if (options.message) {
///                   options.messages["required"] = options.message;
///                       }
///  Fix : (C# Code for MVC validation)
///  You can see it inherits from common RequiredAttribute. Moreover it implements IClientValidateable. This is to make assure that rule will be propagated to client side (jQuery validation) as well.
///  
///  Annotation example :
///   [BooleanRequired]
///   public bool iAgree { get; set' }
///    

/// </summary>


public class BooleanRequired : RequiredAttribute, IClientValidatable
{

    public BooleanRequired()
    {
    }

    public override bool IsValid(object value)
    {
        return value != null && (bool)value == true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "brequired", ErrorMessage = this.ErrorMessage } };
    }
}
Cullis answered 31/12, 2014 at 9:33 Comment(1)
<script type="text/jscript"> //Custom jQuery validation unobtrusive script and adapters jQuery.validator.unobtrusive.adapters.add("brequired", function (options) { //b-required for checkboxes if (options.element.tagName.toUpperCase() == "INPUT" && options.element.type.toUpperCase() == "CHECKBOX") { //setValidationValues(options, "required", true); options.rules["required"] = true; if (options.message) { options.messages["required"] = options.message; } } }); </script>Cullis

© 2022 - 2024 — McMap. All rights reserved.