I need to validate my input in MVC4 razor. The problem is, I want to use twitter bootstrap and then set the error message class for the top div
as seen here: http://getbootstrap.com/css/#forms-control-states
How can I change the parent div
's class?
Have you tried this script?
http://blog.ijasoneverett.com/2013/05/asp-net-mvc-validation-with-twitters-bootstrap/
I use something similar for getting bootstrap and mvc validation to play nice
The accepted answer needed modifications to work with Bootstrap 3. I have used the following with success with MVC 4 and Bootstrap 3.
$(function () {
// any validation summary items should be encapsulated by a class alert and alert-danger
$('.validation-summary-errors').each(function () {
$(this).addClass('alert');
$(this).addClass('alert-danger');
});
// update validation fields on submission of form
$('form').submit(function () {
if ($(this).valid()) {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length == 0) {
$(this).removeClass('has-error');
$(this).addClass('has-success');
}
});
}
else {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).removeClass('has-success');
$(this).addClass('has-error');
}
});
$('.validation-summary-errors').each(function () {
if ($(this).hasClass('alert-danger') == false) {
$(this).addClass('alert');
$(this).addClass('alert-danger');
}
});
}
});
// check each form-group for errors on ready
$('form').each(function () {
$(this).find('div.form-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('has-error');
}
});
});
});
var page = function () {
//Update the validator
$.validator.setDefaults({
highlight: function (element) {
$(element).closest(".form-group").addClass("has-error");
$(element).closest(".form-group").removeClass("has-success");
},
unhighlight: function (element) {
$(element).closest(".form-group").removeClass("has-error");
$(element).closest(".form-group").addClass("has-success");
}
});
}();
Modify validator defaults:
// Override jquery validate plugin defaults in order to utilize Twitter Bootstrap 3 has-error, has-success, etc. styling.
$.validator.setDefaults({
highlight: function(element) {
$(element).closest('.form-group').addClass('has-error').removeClass('has-success');
},
unhighlight: function(element) {
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
},
errorElement: 'span',
errorClass: 'help-block',
errorPlacement: function (error, element) {
if (element.parent('.input-group').length || element.prop('type') === 'checkbox' || element.prop('type') === 'radio') {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
}
});
Create a new validation summary partial (so you have complete control over markup):
@model ModelStateDictionary
<div class="@(Html.ViewData.ModelState.IsValid ? "validation-summary-valid" : "validation-summary-errors") panel panel-danger" data-valmsg-summary="true">
<div class="panel-heading">
Please, correct the following errors:
</div>
<div class="panel-body">
<ul>
@foreach (var modelError in Model.SelectMany(keyValuePair => keyValuePair.Value.Errors))
{
<li>@modelError.ErrorMessage</li>
}
</ul>
</div>
</div>
Add one style to your CSS (to hide the validation summary on initial load without needing to depend on client-side scripting to hide it after the fact):
.validation-summary-valid {
display: none;
}
And render your alternative validation summary in your view instead of @Html.ValidationSummary():
@{Html.RenderPartial("_HtmlValidationSummary", ViewData.ModelState);}
And you're good to go. Oh, and to get the nice styling for checkbox and radio, depending on your setup the above script will catch it, but the easiest is to simply modify your i.e. Boolean.cshtml partial to be like the following (where Html.FormGroupFor is just a slightly updated version of https://github.com/erichexter/twitter.bootstrap.mvc/blob/master/src/Bootstrap/BootstrapSupport/ControlGroupExtensions.cs - just change "control-group" references to "form-group" and add " controlGroupWrapper.AddCssClass("has-error");" on line 58'ish):
@using Bctf.Web.HtmlHelpers
@model bool?
@using (Html.FormGroupFor(x => Model))
{
<label class="checkbox">
@Html.CheckBox("", Model.HasValue && Model.Value, new { @class = "tickbox-single-line" }) @ViewData.ModelMetadata.DisplayName
</label>
@Html.ValidationMessageFor(x => Model, string.Empty, new { @class = "help-block" })
}
All of the above is compilation of various solutions I've found from many different locations that have made the right mix. Ben's solution above works, but it does a fair bit client-side which causes a jitter whenever some method in it is called (i.e. post, page load, validation error). The above does all that you need without having all that DOM looping.
The various answers found at Bootstrap 3 with jQuery Validation Plugin cover much of the above (and credit for a significant chunk of the above goes to the various posters there, although no one answer covers it all).
Solution
Include last:
$.validator.unobtrusive.options = {
errorPlacement: function (error, element) {
$(element).closest(".form-group").addClass("has-error");
}
, success: function (label, element) {
$(element).closest(".form-group").removeClass("has-error");
}
}
Currently in jquery.validate.unobtrusive.js the following code shows how MS allows for extending itself:
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
}
Allows you to basically override/extend the following options:
- errorClass
- errorElement
- errrorPlacement
- invalidHandler
- success
It does this by looking at the global variable $.validator.unobtrusive.options
(which doesn't exist unless you make it). defaultOptions
is set to $.validator.unobtrusive.options
and execInContext
is a method that finds the applicable method in $.validator.unobtrusive.options
and executes it with the original arguments (so that the original MS method doesn't get clobbered).
I like to add class on server side, and not client side, so, my solution has no javascript (in my case, it's a simple system with only 2 or 3 fields on the form).
This is to use the same structure from Form -> Validation States from Bootstrap documentation, available in http://getbootstrap.com/css/#forms-control-validation
First, I created a Html Helper
to check if the ModelState is valid, and if has a error on the field, return a string (class name). I could have done this directly on the view, but I like a cleaner view. So, my class:
public static class ErrorHelper
{
private static string GetPropertyName<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
{
// Get field name
// Code from: https://stackoverflow.com/a/2916344/4794469
MemberExpression body = expression.Body as MemberExpression;
if (body != null) return body.Member.Name;
UnaryExpression ubody = expression.Body as UnaryExpression;
if (ubody != null) body = ubody.Operand as MemberExpression;
return body?.Member.Name;
}
public static MvcHtmlString ReturnOnError<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, string @class)
{
var propertyName = GetPropertyName(expression);
if (propertyName == null)
return MvcHtmlString.Empty;
if (htmlHelper.ViewData.ModelState.IsValid || !htmlHelper.ViewData.ModelState.ContainsKey(propertyName))
return MvcHtmlString.Empty;
return htmlHelper.ViewData.ModelState[propertyName].Errors.Any()
? MvcHtmlString.Create(@class)
: MvcHtmlString.Empty;
}
}
Imports:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
Now, in my View, I just need to use the class, like:
<div class="form-group @Html.ReturnOnError(m => m.Name, "has-error")">
@Html.LabelFor(m => m.Name, new { @class = "control-label" })
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Name, null, new { @class = "help-block" })
</div>