Jake Jake - 7 months ago 21
Javascript Question

jQuery Unobtrusive client-side validation of custom attribute

I have created a custom validation attribute which works server-side (after form is posted) but I cannot get the validation to work client-side.

The custom attribute is:

public class ReasonableAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return Approval.IsNumberReasonable(value.ToString());
}

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

}


The
IsNumberReasonable()
function just does some checks on the fields input to make sure what they entered could reasonably be an Approval number (e.g. not empty, or "Unknown" or something like that) returning a
bool true/false
.

My Model then contains:

[Display(Name = "Approval Number"]
[Reasonable(ErrorMessage = "Please enter a reasonable {0}")]
public String ApprovalNumber { get; set; }


and the View contains:

@Html.LabelFor(model => model.ApprovalNumber, new { @class = "control-label col-md-3" })
<div class="col-md-9 append field">
@Html.TextBoxFor(model => model.ApprovalNumber, new { @class = "input text" })
@Html.ValidationMessageFor(model => model.ApprovalNumber)
</div>


in the JavaScript I have tried adding:

$.validator.unobtrusive.adapters.addBool("reasonable", "required");
// OR
$.validator.unobtrusive.adapters.add("reasonable");


and: (outside of document.ready)

(function ($) {
$.validator.addMethod('reasonable', function (value, element, params) {
return value != '';
}, 'Clientside Should Not Postback');
$.validator.unobtrusive.adapters.addBool('reasonable');
})(jQuery);


in various combinations but have not been able to get it to work.

I have got

<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>


included in the View.

When the form is submitted, client-side validation works for other fields (required ones etc) but the validation message next to the ApprovalNumber never appears/does not perform my custom validation.

Any pointers in the right direction are appreciated.
thanks.

I also have

<configuration>
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
</configuration>


Already in the Web.config file.

Edit 2:
as per chenZ's post:
The html for the input field is:

<input class="input text valid" data-val="true" data-val-reasonable="Please enter a reasonable Approval Number" data-val-required="Approval Number is Required." id="ApprovalNumber" name="ApprovalNumber" type="text" value="">


Tried updating the bottom of my view with:

<script src="~/Scripts/jquery.validate.js"></script>
<script type="text/javascript">
(function ($) {
$.validator.addMethod('reasonable', function (value, element, params) {
return value != '';
}, 'Clientside Should Not Postback');
})(jQuery);
</script>

<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/Create.js"></script>


where the end of
jquery.validate.unobtrusive.js
now contains:

$(function () {
$.validator.unobtrusive.adapters.addBool('reasonable');
$("#formID").removeData("unobtrusiveValidation").removeData("validator");
$jQval.unobtrusive.parse(document);
});
}(jQuery));


and
create.js
contains:

$.validator.unobtrusive.adapters.addBool("mandatory", "required"); // another custom attribute for checkbox validation
var v = $("#formID").validate({
errorClass: "warning",
onkeyup: false,
onblur: false,
});


the
v
variable is used further down for the submit button:

$("#SubmitButton").click(function () {
if (v.form()) {
// The form passed validation so continue..
} else {
// Validation failed, do something else..
}
});


I did some further testing ans it appears to be something to do with the:

(function ($) {
$.validator.addMethod('reasonable', function (value, element, params) {
return value != '';
}, 'Clientside Should Not Postback');
})(jQuery);


when I change it to:

(function ($) {
$.validator.addMethod('reasonable', function (value, element, params) {
return value != 'unknown';
}, 'Clientside Should Not Postback');
})(jQuery);


it works, but only if I type 'unknown' into the input box, anything else passes.. similarly if I put
return value != 'jhdsfkhsdkfj';
it only validates when I input jhdsfkhsdkfj.

so it appears that it is using that as the rule? instead of my server-side
IsNumberReasonable()
function.

hope that helps in troubleshooting.

Answer

I haven't tried it, so I'm not sure, but i think there are 2 places where you may be wrong.

1.rule.ValidationType = "reasonable";
Check your html, find the input and is there an attr data-val-reasonalbe or reasonale

$.each(this.adapters, function () {
            var prefix = "data-val-" + this.name,

in jquery.validate.unobtrusive.js, line 173, you can find this, so it must be a data-val-xxxx

2.in jquery.validate.unobtrusive.js end of file you can find

$(function () {
    $jQval.unobtrusive.parse(document);
});

so, when the page is loaded, all form set validate

This is a difficult thing. If your code is like

<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
.....
(function ($) {
        $.validator.addMethod('reasonable', function (value, element, params) {
            return value != '';
        }, 'Clientside Should Not Postback');
        $.validator.unobtrusive.adapters.addBool('reasonable');
    })(jQuery);

When you do unobtrusive.parse(document); your valid method reasonable doesn't exist, and adapter doesn't exist exist either, so your form valid doesn't have a rule reasonable

If you change the code to like this

<script src="~/Scripts/jquery.validate.js"></script>
(function ($) {
        $.validator.addMethod('reasonable', function (value, element, params) {
            return value != '';
        }, 'Clientside Should Not Postback');
        $.validator.unobtrusive.adapters.addBool('reasonable');
    })(jQuery);
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>

the valid method can add, but the $.validator.unobtrusive is undefined

I think you should do more

<script src="~/Scripts/jquery.validate.js"></script>
(function ($) {
        $.validator.addMethod('reasonable', function (value, element, params) {
            return value != '';
        }, 'Clientside Should Not Postback');
    })(jQuery);
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>

and put the

$.validator.unobtrusive.adapters.addBool('reasonable');

in to jquery.validate.unobtrusive.js, and make sure it runs before

$(function () {
    $jQval.unobtrusive.parse(document);
});

I'm not sure if it will work, maybe I'll try later. Hope this can help you.

update I tried it. In point 2, you can use code

$(function () {
    $("#formid").removeData("unobtrusiveValidation").removeData("validator");
    $.validator.unobtrusive.parse(document);
});

Put this code after your add valid method, this will clear valid setting and parse again with your custom rules

update 2

$("#a").validate({});//first time call is useful
$("#a").validate({});//second time call is useless

also to unobtrusive.parse(...)

jquery.validate.js line 41

var validator = $.data( this[0], "validator" );
if ( validator ) {
    return validator;
}

and jquery.validate.unobtrusive.js line 99

result = $form.data(data_validation),
    ...
    if (!result) { ....

when <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
end of this file, called unobtrusive.parse(document), it will append a data to form. If it called before you setup code, the validate setting is without your setup. you can removeData(...), and run unobtrusive.parse(document) by your code again like

<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/setting.js"></script>
<script>
  $(function(){ $("#formid").removeData(...).removeData(...); 
  ....unobtrusive.parse(document)});
</script>

validate plugin use data name "validator" and unobtrusive use data name "unobtrusiveValidation".

I'm really sorry for my poor English, hope you can understand.