My viewmodel
Public Class ViewModel <SelectOne()> Public Property Collection As List(Of Item) End Class
My model
Public Class Item <SelectOneProperty(TargetValue:=True, ErrorMessage:="Select at least one.")> Public Property Selected As Boolean Public Property Value As String End Class
In my opinion, I am drawing ViewModel.Collection with an editor template
@Html.CheckBoxFor(Function(item) item.Selected) @Html.HiddenFor(Function(item) item.Value)
Now, what I want is to make sure that at least one checkbox is selected using client-side validation.
I can achieve this by setting my own validation attribute in the Item.Selected property and registering a new adapter through $.validator.unobtrusive.adapters.add()
But I believe that the attribute should be more likely in ViewModel.Collection , as on the server side. I already check if one of the elements in the collection has Selected = True using this selective check:
<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)> Public Class SelectOneAttribute Inherits ValidationAttribute Protected Overrides Function IsValid(value As Object, validationContext As ValidationContext) As ValidationResult Dim list As IList If value Is Nothing Then Return Nothing End If If TypeOf value Is IEnumerable Then list = CType(value, IList) Else list = New Object() {value} End If Dim count As Integer = (From item In list From prop In item.GetType().GetProperties() Let attributes = prop.GetCustomAttributes(GetType(RequireOneOrMoreIndicatorAttribute), False) Where attributes.Count > 0 From attribute In attributes Where attribute.TargetValue = prop.GetValue(item, Nothing)).Count() If count > 0 Then Return Nothing End If Return New ValidationResult(FormatErrorMessage(validationContext.DisplayName)) End Function End Class
It uses reflection on SelectOnePropertyAttribute to find which property is being checked:
<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)> Public Class SelectOnePropertyAttribute Inherits ValidationAttribute Implements IClientValidatable Public Property TargetValue As Object Public Sub New(targetValue As Object) Me.TargetValue = targetValue End Sub Public Overrides Function IsValid(value As Object) As Boolean Return True End Function Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) _ As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) _ Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules Dim rule As New ModelClientValidationRule With { .ValidationType = "selectone", .ErrorMessage = Me.ErrorMessage } Return New ModelClientValidationRule() {rule} End Function End Class
And this is a client-side check
$.validator.unobtrusive.adapters.add("selectone", function (options) { options.rules["selectone"] = {}; options.messages["selectone"] = options.message; }); $.validator.addMethod("selectone", function (value, element, parameters) { var $el = $(element), name = $el.attr("name"), field = name.replace(/\[.*$/, "").replace(".", "_"), attr = name.replace(/^.*\./, ""), test = new RegExp(field + "\\[\\d\\]\." + attr); var inputs = $("input[id^=" + field + "]:not([disabled]):not([type=hidden])").filter("input[name$=" + attr + "]"); for(var i = 0; i < this.errorList.length; i++) { var name = $(this.errorList[i].element).attr("name");