Not used to ask questions here but there is a first time for everything.
I'm currently working on a web project in ASP.NET MVC and I am stuck at the very first form... What I'm trying to do is to display a group of checkboxes and require at least one of them to be checked. Fortunately, I already found several answers on the matter and it works... only on the server side.
But I can't figure out why it is not working on the client side! Although all my other fields custom rules apply well on client side! I'm still rather new to this tech so maybe I am missing something here...
I've been on it for days now. I hope someone here will be kind enough to help me.
Below is my code around the checkboxes :
View
<div class="col-sm-3">
<label>Documents:<br/>(select at least one)*</label><br/>
@Html.ValidationMessageFor(m => m.AllDocumentsChecked, "", new @class = "text-danger" })
</div>
<div class="col-sm-3">
<div class="form-check">
@Html.CheckBoxFor(m => m.AllDocumentsChecked, new { @class = "form-check-input", @id = "AllDocumentsChecked", @name = "outputFiles" })
<label for="select_all" class="form-check-label">All Documents</label>
</div>
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc1Checked, new { @class = "form-check-input check", @id = "Doc1Checked", @name = "outputFiles" })
<label for="file_doc1" class="form-check-label">Doc1</label>
</div>
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc2Checked, new { @class = "form-check-input check", @id = "Doc2Checked", @name = "outputFiles" })
<label for="file_doc2" class="form-check-label">Doc2</label>
</div>
</div>
<div class="col-sm-3">
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc3Checked, new { @class = "form-check-input check", @id = "Doc3Checked", @name = "outputFiles" })
<label for="file_doc3" class="form-check-label">Doc3</label>
</div>
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc4Checked, new { @class = "form-check-input check", @id = "Doc4Checked", @name = "outputFiles" })
<label for="file_doc4" class="form-check-label">Doc4</label>
</div>
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc5Checked, new { @class = "form-check-input check", @id = "Doc5Checked", @name = "outputFiles" })
<label for="file_doc5" class="form-check-label">Doc5</label>
</div>
</div>
<div class="col-sm-3">
<div class="form-check">
@Html.CheckBoxFor(m => m.Doc6Checked, new { @class = "form-check-input check", @id = "Doc6Checked", @name = "outputFiles" })
<label for="file_doc6" class="form-check-label">Doc6</label>
</div>
</div>
Model
[Display(Name = "All Documents")]
[RequireAtLeastOneOfGroup("Documents")]
public bool AllDocumentsChecked { get; set; }
[Display(Name = "Doc1")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc1Checked { get; set; }
[Display(Name = "Doc2")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc2Checked { get; set; }
[Display(Name = "Doc3")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc3Checked { get; set; }
[Display(Name = "Doc4")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc4Checked { get; set; }
[Display(Name = "Doc5")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc5Checked { get; set; }
[Display(Name = "Doc6")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc6Checked { get; set; }
RequireAtLeastOneFromGroupAttribute class
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequireAtLeastOneOfGroupAttribute : ValidationAttribute, IClientValidatable
{
public string GroupName { get; private set; }
public RequireAtLeastOneOfGroupAttribute(string groupName)
{
ErrorMessage = string.Format("You must select at least one value from this group", groupName);
GroupName = groupName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
foreach (var property in GetGroupProperties(validationContext.ObjectType))
{
var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
if (propertyValue)
{
// at least one property is true in this group => the model is valid
return ValidationResult.Success;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
{
return
from property in type.GetProperties()
where property.PropertyType == typeof(bool)
let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
where attributes.Count() > 0
from attribute in attributes
where attribute.GroupName == GroupName
select property;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage
};
rule.ValidationType = string.Format("group", GroupName.ToLower());
rule.ValidationParameters.Add("group", string.Join(",", groupProperties));
//rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
yield return rule;
}
}
require_at_least_one_from_group javascript
$.validator.unobtrusive.adapters.add('atleastone', ['propertynames'],
function (options) {
options.rules['group'] = { propertynames: options.params.propertynames.split(',') };
options.messages['group'] = options.message;
});
$.validator.addMethod('group',
function (value, element, params) {
var properties = params.properties.split(',');
var isValid = false;
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
if ($('#' + property).is(':checked')) {
isValid = true;
break;
}
}
return isValid;
}, '');
EDIT
Example of html tagging for a checkbox
Below the html resulting from the helpers above. Still can't see what is wrong.
<input class="form-check-input check" data-val="true" data-val-atleastone="You must select at least one value from this group"
data-val-atleastone-group="AllDocumentsChecked,Doc1Checked,Doc2Checked,
Doc3Checked,Doc4Checked,Doc5Checked,Doc6Checked" data-val-required="The Doc1 field is required."
id="Doc1Checked" name="Doc1Checked" value="true" type="checkbox">
<input name="Doc1Checked" value="false" type="hidden">
Thanks by advance!