Note that there are some explanatory texts on larger screens.

plurals
  1. POMVC3 Client-side validation on collection, at least one element with value
    text
    copied!<p>My ViewModel</p> <pre><code>Public Class ViewModel &lt;SelectOne()&gt; Public Property Collection As List(Of Item) End Class </code></pre> <p>My Model</p> <pre><code>Public Class Item &lt;SelectOneProperty(TargetValue:=True, ErrorMessage:="Select at least one.")&gt; Public Property Selected As Boolean Public Property Value As String End Class </code></pre> <p>In my view I'm rendering <code>ViewModel.Collection</code> with an Editor Template</p> <pre><code>@Html.CheckBoxFor(Function(item) item.Selected) @Html.HiddenFor(Function(item) item.Value) </code></pre> <p><strong>Now, what I want, is making sure that at least one checkbox is checked using client-side validation.</strong> </p> <p>I can achieve this by setting a custom validation attribute on the <code>Item.Selected</code> property and registering a new adapter through <code>$.validator.unobtrusive.adapters.add()</code></p> <p>But I feel the attribute should rather be on the <code>ViewModel.Collection</code> property as on the server side I am already validating if one of the collection's item has <code>Selected = True</code> using this custom validation:</p> <pre><code>&lt;AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)&gt; 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 &gt; 0 From attribute In attributes Where attribute.TargetValue = prop.GetValue(item, Nothing)).Count() If count &gt; 0 Then Return Nothing End If Return New ValidationResult(FormatErrorMessage(validationContext.DisplayName)) End Function End Class </code></pre> <p>It uses reflection on <code>SelectOnePropertyAttribute</code> to find which property to check against:</p> <pre><code>&lt;AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False, Inherited:=False)&gt; 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 </code></pre> <p>And this it the client-side validation</p> <pre><code>$.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 &lt; this.errorList.length; i++) { var name = $(this.errorList[i].element).attr("name"); // Do not write out the error more than once. if (test.test(name)) return true; } return inputs.length == 0 || inputs.filter(":checked:not([disabled])").val(); }); </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload