Implement null check logic in Mvc View with a model - asp.net-mvc

<div class="form-gridcontrol">
<label>Notes</label>
#Html.CustomTextArea(m => m.Notes)
</div>
In ASP.NET MVC , I have created a custom textarea and inputing/displaying data from the database using a Model.Above is the code where you can see the Notes are getting assigned to #Html.CustomTextArea.
I have a situation where , I need to display a text "Not Applicable" if there is no value in "m.Notes"
How I should right the logic in the above code? Please guide.

There are multiple possible ways for this. One of the way is that you can populate in the controller action from where it is loaded like:
public ActionResult YourActionMethod()
{
............
............
if(String.IsNullOrEmpty(model.Notes))
model.Notes = "Not Applicable";
return View(model);
}
Another way can be to introdcue the backing field on your property and write in it's getter:
private String _notes;
public String Notes
{
get
{
return String.IsNullOrEmpty(_notes) ? "Not Applicable" : _notes;
}
set
{
_notes = value;
}
}

You can try this:
#if (Model.Notes != null)
{
#Html.CustomTextArea(m => m.Notes)
}
else
{
#Html.CustomTextArea( m => m.Notes, new { #Value = "Not Applicable"})
}
edit:this is not working with textarea
else
{
#Html.CustomTextArea(m => m.Notes, new {id="mytextarea"})
<script>
$("#mytextarea").text("Not Applicable")
</script>
}
I got a trick for you :)

Related

MVC EditorFor to Title Case

How do you convert input value to title case in EditorFor? I know doing
#Html.EditorFor(model, new { htmlAttributes = new { #style = "text-transform:uppercase" } })
will only change the client side so I need to change it manually on server side.
I tried adding the class text-capitalize but seems no luck.
Thanks in advance.
Here are explanations to use either title case or sentence case for viewmodel's string properties which bound to EditorFor:
1) If you want to use title case, you can set it inside getter part with ToTitleCase method (change CurrentCulture to InvariantCulture depending on your requirements), as in example below:
private string _titleCase;
private System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
public string TitleCaseProperty
{
get
{
if (string.IsNullOrEmpty(_titleCase))
{
return _value;
}
else
{
return culture.TextInfo.ToTitleCase(_titleCase.ToLower());
}
}
set
{
_titleCase = value;
}
}
View usage
#Html.EditorFor(model => model.TitleCaseProperty, ...)
2) If you want sentence case instead, use regular expression to find out sequences (referenced from this similar issue) and do similar way to getter part like above:
private string _sentenceCase;
private Regex rgx = new Regex(#"(^[a-z])|[?!.:,;]\s+(.)", RegexOptions.ExplicitCapture);
public string SentenceCaseProperty
{
get
{
if (string.IsNullOrEmpty(_sentenceCase))
{
return _value;
}
else
{
return rgx.Replace(_sentenceCase.ToLower(), s => s.Value.ToUpper());
}
}
set
{
_sentenceCase = value;
}
}
View usage
#Html.EditorFor(model => model.SentenceCaseProperty, ...)
Live example: .NET Fiddle Demo
I would recommend performing this conversion at the getter of this property using .ToUpper()
get {
if (string.IsNullOrEmpty(_value))
{
return _value;
}
return _value.ToUpper();
}
easier method
#Html.TextBoxFor(model.FieldName, new { #class = "uppercase" })
css:
.uppercase { text-transform:uppercase }

An item with the same key has already been added when loading Kendo MVC Grid

I am trying to populate a Kendo mvcGrid and I keep getting the error An item with the same key has already been added.
I have seen elsewhere that duplicate model properties is the likeliest source of the problem but I have no duplicate properties in my model. I've even simplified everything to just a basic mock up to eliminate as many possible variables as possible..and I cant get the grid to load data.
I've tried it as a Html.Partial where its not loaded initially because of a empty model and then loading it through jquery...Ive tried with passing in am empty model so that the grid will load initially without data and then doing a datasource refresh...no luck there either. Basically as soon as I try to add content to the grid with a populated model I get the error and no data is loaded.
Been fighting this issue for 2 days now and had no luck. Ill post my code here maybe someone else can see what I'm missing.
One variable that cant change is the grid resides on a partial view with a model different from its parent view.
in the parent view I have tried both
<div id ="columnkgrid">#{Html.RenderPartial("Columns", new TestFieldIssue.Models.FieldListModel());}</div>
and
<div id="columnkgrid">#Html.Partial("Columns", new TestFieldIssue.Models.FieldListModel())</div>
either way will succeed in posting a grid with no data..it has no data yet because its populated by the selection of a value in a dropdown list.
so as to not over complicate the sample I've just set a hard coded value in the jquery function I have been using to refresh the grid.
function loadgrid() {
var grid = $("#grid").data("kendoGrid");
var val = 1;
grid.dataSource.read({ table_pk: val });
grid.refresh();
}
the grid code in the partial once again kept it simple without any bells and whistles just to test.
#(Html.Kendo().Grid(Model.fields)
.DataSource(data => data
.Ajax()
.Model(model => model.Id(m => m.field_pk))
.Read(r => r.Action("Columns","Home"))
)
.Name("grid")
.Columns(c =>
{
c.Bound(m => m.field_name).Title("Field Name").Width(100);
})
)
and the controller methods loading some mock data
public ActionResult Columns(int? table_pk)
{
FieldListModel model;
if (table_pk == null)
{
model = GetEmptyColumns();
}
else
{
model = GetColumns();
}
return PartialView("Columns", model);
}
public FieldListModel GetColumns()
{
FieldListModel model = new FieldListModel();
model.fields = new List<FieldModel>();
model.fields.Add(new FieldModel { field_name = "field1", field_pk = 1 });
model.fields.Add(new FieldModel { field_name = "field2", field_pk = 2 });
model.fields.Add(new FieldModel { field_name = "field3", field_pk = 3 });
return model;
}
public FieldListModel GetEmptyColumns()
{
FieldListModel model = new FieldListModel();
model.fields = new List<FieldModel>();
model.fields.Add(new FieldModel { field_name = "", field_pk = 0 });
return model;
}
and a very simple Model
public class FieldListModel
{
public List<FieldModel> fields { get; set; }
}
public class FieldModel
{
public int field_pk { get; set; }
public string field_name { get; set; }
}
I made few changes to run your code (correct version of Kendo and JQuery). May be those related to setup at my machine. I was able to reproduce the problem.
Then I changed the action code and was able to see the values populated in Grid:
public ActionResult Columns(int? table_pk, [DataSourceRequest] DataSourceRequest request)
{
FieldListModel model;
if (table_pk == null)
{
model = GetEmptyColumns();
}
else
{
model = GetColumns();
}
return Json(model.fields.ToDataSourceResult(request));
}
The change is accepting an additional parameter in action method of type DataSourceRequest. The Kendo grid wraps request in this object to specify sorting and paging information. The grid itself gets updated with data wrapped under DataSourceRequest object (note in return statement). More information here.

Multiple IEnumerable implement in mvc3 on VIew

#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No").Filterable(true);
})
But i want to do something like that :
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
But its not working can anybody have some idea about it.
Now
if i do something like this it works
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
}
My problem is that APPLICATION_NO property present in both Model class so i don`t want to use
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
Twice in my code.
Your problem is that you are not using when of the most important concepts in MVC: view models.
As for your last comment, what you want to use is a view model, i.e. a class created specifically to send data to the view to show it.
To do so:
create a public class which has the APPLICATION_NO (needed for the view model class)
create another public class which will be your view model. That's a class that shapes the data as you need it on the razor template (in this case, it will hold a list of the class defined in 1)
in your controller, return the view passing the model as second parameter. I.e. don't use the ViewBag/ViewData, but a view model instead, like this return View("ViewName", model)
use the model in your view: declare the model type using #model and use it inside the Razor templae with the provided Model variable
In this way, you shape the data on the server, and avoid including a lot of code in you Razor template (which is not advisable). And, of course, you have intellisense, and your templates become typed.
Code for 1:
public class ApplicationModel
{
public int APPLICATION_NO {get; set;}
}
Code for 2:
public class ApplicationsViewModel
{
public List<ApplicationModel> Applications { get; set; }
}
Code for 3 (inside the controller)
var model = new ApplicationsViewModel();
if (...) // include the condition to choose the kind of data inside the list
{
model.Applications = list.Select(item =>
new ApplicationModel { APPLICATION_NO = item.APPLICATION_NO } ).ToList()
}
else
{
// same code here, for the other kind of data in the list
}
// return the view with this model
return View("ApplicationView", model);
Code for 4:
// decalre the model type at the beginning
#model YourNamespace.ApplicationViewModel;
// access the model using the Model variable
#Html.Grid(Model.Applications).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
This allows you to build MVC applications with several advantages, for example testability, code reusing, readability or availability of a ModelState. Belive me, many of theses things are really,really important, specially the ModelState.
Besides you can use code annotations (attributes that give extra info to the HTML helpers) in your view model, that attributes can provide labels, validation and some other automatic functionality.
Don't doubt to include all the needed properties inside your view model.
Using a view model allows you to create an ad-hoc class without the need to change you domain or business layer classes, i.e. you don't need to use interfaces, code annotations and so on. However many times it's interesting to add the code annotations in the business classes and nest them inside the view models.
Remember tha sometimes you can use the same view model to post the data back to the server, specifying it as the paramter type of your POST action.
By the way, the interface solution is a good one, but this solution doesn't required the interface. (This solution would have a better implementationusing that interface, but that's your choice).
#if(some conditon)
{
#{IEnumerable<BC.Models.APPLICATION> data = ViewBag.list;}
}
else
{
#{IEnumerable<BC.Models.RIGHTS> data = ViewBag.list;}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
The code can't work because data is declared into if blocks.
If the grid has to work only on shared fields of the two classes you can think about using an Interface that APPLICATION and RIGHTS will implement and change the code like this:
#{IEnumerable<BC.Models.IAPPLICATION_NO> data = (IEnumerable<BC.Models.IAPPLICATION_NO>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
where IAPPLICATION_NO is an interface like:
public interface IAPPLICATION_NO
{
string APPLICATION_NO { get; }
}
I don't know what APPLICATION_NO is, so I used string and the interface can define only get for grid.
Otherwise, if you need to display different data for those two types you should consider using two views or different grid declaration in the if blocks.
I worked on a sample of my answer on VS:
I attach you the code:
public interface AInterface
{
string AProperty { get; }
}
public class AList : AInterface
{
public string AProperty { get; set; }
}
public class BList : AInterface
{
public string AProperty { get; set; }
}
these are the classes
now the controller:
public class TestController : Controller
{
public ActionResult Index()
{
var random = new Random((int)DateTime.Now.Ticks);
if (random.Next() % 2 == 0)
ViewBag.List = new List<AList> { new AList { AProperty = "Atestvalue" } };
else
ViewBag.List = new List<BList> { new BList { AProperty = "Atestvalue" } };
return View();
}
}
and the view:
#{
ViewBag.Title = "Index";
IEnumerable<TestMvcApplication.Interfaces.AInterface> test = ViewBag.List;
}
<h2>Index</h2>
#foreach (var item in test)
{
<div>
#item.AProperty
</div>
}
this solves your problem as you can see
Without using interfaces:
#{IEnumerable<dynamic> data = (IEnumerable<dynamic>)ViewBag.list;}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled("Application No");
})
but you lose the IntelliSense completion and if the member is missing I think you receive a runtime error.
I tried your Grid assembly but it uses c# Expression and it's incompatible with dynamic.
Another solution could be casting one list to another using LINQ in the controller:
IEnumerable<BC.Models.Application> data;
if (some condition)
{
data = applicationList; //applicationList's type is IEnumerable<BC.Models.Application>
}
else
{
data = rightsList.Select(t => new Application { APPLICATION_NO = t.APPLICATION_NO }); //rightsList's type is IEnumerable<BC.Models.RIGHTS>
}
ViewBag.list = data;
In the view you can keep the working code you posted at the top of the question. You have not multitype IEnumerable support because you use only one type but without using a common interface between these classes I think we must go to reflection but I think it's hard to write that code.
Why not
#{string columnName = "default column name";
if(some_condition)
{
// I'm not sure what's going on with the data variable
columnName = "alternate column name";
}
else
{
// Again, do your stuff with the data variable
}
}
#Html.Grid(data).Columns(columns =>
{
columns.Add(c => c.APPLICATION_NO).Titled(columnName);
})
I think that you're going to end up with (at best) confusing code if you try to be too clever in your views. A grid for rendering IEnumerable<A> isn't adaptable to rendering IEnumerable<B> unless A:ISomeInterface and B:ISomeInterface and the grid renders ISomeInterface. Alternatively just pass the column name as a property of the view model.

ASP.NET MVC Post list becomes null under very strange circumstances

So I have a controller like this:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
return View("Test");
}
public ActionResult Post(IList<Test> LanguageStrings, IList<Test> LanguageStringsGroup, IList<string> Deleted, IList<string> DeletedGroup)
{
if (LanguageStrings == null)
{
throw new ApplicationException("NULL");
}
return View("Test");
}
}
public class Test
{
public string Val { get; set; }
public string Another { get; set; }
}
And a view like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
When I post the form my controller throws the exception because LanguageStrings is null. The strange part I mentioned in the title is that if I add one more record to the list everything works.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStrings[2].Val", "test3")
#Html.Hidden("LanguageStrings[2].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
It also works when I remove the "Deleted" list.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
This has something to do with the naming I am using. I have already solved the problem with renaming LanguageStrings to something else. But I would like to understand what is happening here because probably I could learn something from it how MVC maps request body and will be able to avoid similar time consuming problems.
Please help me and explain the cause of this.
You found a bug in the PrefixContainer of MVC 4 which has already been fixed in MVC 5.
Here is the fixed version with comments about the bug:
internal bool ContainsPrefix(string prefix)
{
if (prefix == null)
{
throw new ArgumentNullException("prefix");
}
if (prefix.Length == 0)
{
return _sortedValues.Length > 0; // only match empty string when we have some value
}
PrefixComparer prefixComparer = new PrefixComparer(prefix);
bool containsPrefix = Array.BinarySearch(_sortedValues, prefix, prefixComparer) > -1;
if (!containsPrefix)
{
// If there's something in the search boundary that starts with the same name
// as the collection prefix that we're trying to find, the binary search would actually fail.
// For example, let's say we have foo.a, foo.bE and foo.b[0]. Calling Array.BinarySearch
// will fail to find foo.b because it will land on foo.bE, then look at foo.a and finally
// failing to find the prefix which is actually present in the container (foo.b[0]).
// Here we're doing another pass looking specifically for collection prefix.
containsPrefix = Array.BinarySearch(_sortedValues, prefix + "[", prefixComparer) > -1;
}
return containsPrefix;
}
I have had much more success with #Html.HiddenFor() for posting back to the controller. Code would look something like this:
#for (int i = 0; i < #Model.LanguageStrings.Count; i++)
{
#Html.HiddenFor(model => model.LanguageStrings[i].Val, string.Format("test{0}", i))
#Html.HiddenFor(model => model.LanguageStrings[i].Another)
}
Most HTML helper methods have a "For" helper that is intended to be used for binding data to models. Here is another post on the site that explains the "For" methods well: What is the difference between Html.Hidden and Html.HiddenFor

MVC3 unobtrusive validation group of inputs

I need to validate 3 or more input fields (required at lest one). For example I have Email, Fax, Phone.
I require at least ONE to be filled in. I need both server and client 'unobtrusive validation'. please help. I looked into "Compare" method and tried modifying it but no luck. please help.
thanks
You could write a custom attribute:
public class AtLeastOneRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _properties;
public AtLeastOneRequiredAttribute(params string[] properties)
{
_properties = properties;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (_properties == null || _properties.Length < 1)
{
return null;
}
foreach (var property in _properties)
{
var propertyInfo = validationContext.ObjectType.GetProperty(property);
if (propertyInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", property));
}
var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
if (propertyValue is string && !string.IsNullOrEmpty(propertyValue as string))
{
return null;
}
if (propertyValue != null)
{
return null;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "atleastonerequired"
};
rule.ValidationParameters["properties"] = string.Join(",", _properties);
yield return rule;
}
}
which could be used to decorate one of your view model properties (the one you want to get highlighted if validation fails):
public class MyViewModel
{
[AtLeastOneRequired("Email", "Fax", "Phone", ErrorMessage = "At least Email, Fax or Phone is required")]
public string Email { get; set; }
public string Fax { get; set; }
public string Phone { get; set; }
}
and then a simple controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
Rendering the following view which will take care of defining the custom client side validator adapter:
#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">
jQuery.validator.unobtrusive.adapters.add(
'atleastonerequired', ['properties'], function (options) {
options.rules['atleastonerequired'] = options.params;
options.messages['atleastonerequired'] = options.message;
}
);
jQuery.validator.addMethod('atleastonerequired', function (value, element, params) {
var properties = params.properties.split(',');
var values = $.map(properties, function (property, index) {
var val = $('#' + property).val();
return val != '' ? val : null;
});
return values.length > 0;
}, '');
</script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
<div>
#Html.LabelFor(x => x.Email)
#Html.EditorFor(x => x.Email)
</div>
<div>
#Html.LabelFor(x => x.Fax)
#Html.EditorFor(x => x.Fax)
</div>
<div>
#Html.LabelFor(x => x.Phone)
#Html.EditorFor(x => x.Phone)
</div>
<input type="submit" value="OK" />
}
Of course the custom adapter and validator rule should be externalized into a separate javascript file to avoid mixing script with markup.
I spent more than 36 hours why the code did not work for me.. At the end , I found out that in my case , I was not supposed to use the property names in this line of code
[AtLeastOneRequired("Email", "Fax", "Phone", ErrorMessage = "At least Email, Fax or Phone is required")]
But I had to use the HTMl element Ids in place of the property names and it worked like magic.
Posting this here if it might help somebody.
Since you are using MVC 3, take a look at great video Brad Wilson had on mvcConf. There's everything you need to create client + server Unobtrusive Validation
#Darin Dimitrov 's solution is probably the standard of creating a custom validation attribute that works with unobtrusive validation. However, using custom validation attributes for unobtrusive validation have some disadvantages such as:
The custom validation attribute is only attached to one properties, so client validation will not work if there's a change event on the other two inputs.
The error message works fine with ValidationSummary, but if you want to display 1 error message for the whole group (which I think is the norm), it would be nearly impossible.
To avoid the first problem, we can add custom validation attribute to each element in the group, which will cause another problem: we have to validate all elements of the group, instead of stopping at the first invalid group element. And of course, the second problem - separate error messages for each element - still remains.
There's another way to handle client side validation of group of inputs, using groups setting in jquery validator (https://jqueryvalidation.org/validate/#groups). The only problem (and a big one) is that unobtrusive validation doesn't support jquery validation's groups by default, so we have to customize a little bit.
Although this answer is hardly "unobtrusive", in my opinion, it is worth trying to get rid of unnecessary complication of code, if your final goal is to validate a group of inputs while using Microsoft's unobtrusive validator library.
First, because groups settings of default jquery validator is not available in jquery unobtrusive validator, we have to override unobtrusive settings (ref. How can I customize the unobtrusive validation in ASP.NET MVC 3 to match my style?)
$("form").on('submit', function () {
var form = this;
var validator = $(this).data("validator");
if (validator.settings && !validator.settings.submitHandler) {
$.extend(true, validator.settings.rules, validationSettings.rules);
$.extend(true, validator.settings.groups, validationSettings.groups);
initGroups(validator);
var fnErrorReplacement = validator.settings.errorPlacement;
validator.settings.errorPlacement = function (error, element) {
validationSettings.errorPlacement(error, element, fnErrorReplacement, form);
}
validator.settings.submitHandler = formSubmitHandler;
}
});
function formSubmitHandler(form) {
form.submit();
}
After that, override unobtrusive validator's groups, rules and errorPlacement settings.
var validationSettings = {
groups: {
checkboxgroup: "Email Fax Phone"
},
rules: {
Email: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
},
Fax: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
},
Phone: {
required: function () {
return validateCheckboxGroup(["#Email", "#Fax", "#Phone"]);
}
}
}
,
errorPlacement: function (error, element, fnUnobtrusive, form) {
switch (element.attr("name")) {
case "Email":
case "Fax":
case "Phone":
onGroupError(error, "CheckBoxGroup", form);
break;
default:
fnUnobtrusive(error, element);
break;
}
}
}
function validateCheckboxGroup(names) {
var result = true;
$.each(names, function (index, value) {
if ($(value).is(":checked")) {
result = false;
}
});
return result;
}
Because unobtrusive validator does not implement groups setting of jquery validator, we need to reuse two functions from the two libraries to: (1).split group names (reusing code from jquery validator) and (2) append error element without remove 'input-validation-error' class (reusing function onError from unobtrusive library).
function initGroups(validators) {
validators.groups = {};
$.each(validators.settings.groups,
function (key, value) {
if (typeof value === "string") {
value = value.split(/\s/);
}
$.each(value,
function (index, name) {
validators.groups[name] = key;
});
});
}
function onGroupError(error, inputElementName, form) {
var container = $(form).find("[data-valmsg-for='" + inputElementName + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.appendTo(container);
}
else {
error.hide();
}
}
Finally, use HtmlExtensions.ValidationMessage to create error span of the checkbox group.
#Html.ValidationMessage("CheckBoxGroup", new { #class = "text-danger" })
The keeping of "input-validation-error" class is necessary, so that jquery validator will validate all 3 elements (Email, Phone, Fax) of checkbox group as a whole, instead of validating one by one. The unobtrusive validation library remove this class by default on function onError, so we have to customize this as shown in function onGroupError above.

Resources