I need to validate a group of checkboxes using MVC3 unobtrusive validation. how would i do it? I found this and tried it, but it doesn't work.
$(function(){
$.validator.addMethod('cb_selectone', function(value,element){
if(element.length>0){
for(var i=0;i<element.length;i++){
if($(element[i]).val('checked')) return true;
}
return false;
}
return false;
}, 'Please select at least one option');
$('#main').validate({rules:{Services:"cb_selectone"}});
...
My Html:
<input type="checkbox" class="checkbox" name="Services" value="1" />
<input type="checkbox" class="checkbox" name="Services" value="2" />
<input type="checkbox" class="checkbox" name="Services" value="3" />
It would be best if someone provided a complete solution with server side + client side validation (of course using MVC3 unobtrusive validation).
Thanks
Ok, figured it out:
for server validation:
using data annotations (required will do it)
like so in my view model:
[Required(ErrorMessageResourceName = "fld_Service_val_Required_lbl", ErrorMessageResourceType = typeof(Resources.Service.Controllers.Firm))]
public ICollection<int> Services { get; set; }
for client validation in my html i added a class to my input checkboxes:
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="1" />
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="2" />
<input type="checkbox" class="checkbox required-checkbox" name="Services" value="3" />
and also:
$(function(){
$.validator.addMethod('required_group', function(value, element) {
var $module = $(element).parents('form');
return $module.find('input.checkbox:checked').length;
}, 'Select at least one Service please');
$.validator.addClassRules('required-checkbox', { 'required_group' : true });
..
not sure if this is the best solution but it works :). if someone knows a better please post.
This works - validates appropriately on submit, and hides/displays the validation message if a checkbox is subsequently checked or unchecked with minimal overhead (only gets hit once per validation cycle).
JavaScript:
(function ($) {
//functions from unobtrusive:
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
var formEls;
function getUniqueFormId(form) {
if (typeof(formEls==='undefined')) {
formEls = document.getElementsByTagName('form');
}
return 'form' + Array.prototype.indexOf.call(formEls, form);
}
//from jQuery validation
function findByName(name, form) {
// select by name and filter by form for performance over form.find("[name=...]")
return $(document.getElementsByName(name)).map(function (index, element) {
return element.form == form && element.name == name && element || null;
});
}
//-------------------------
$.validator.addMethod('requiredgroup', function (value, element, params) {
for (var i = 0; i < params.length; i++) {
if (params[i].checked) { return true; }
}
return false;
});
valGroups = [];
$.validator.unobtrusive.adapters.add('requiredgroup', function (options) {
var groupName = options.element.name,
uniqueGroup = getUniqueFormId(options.form) + groupName;
if (!valGroups[uniqueGroup]) {
valGroups[uniqueGroup] = findByName(groupName, options.form);
}
//jQuery Validation Plugin 1.9.0 only ever validates first chekcbox in a list
//could probably work around setting this for every element:
setValidationValues(options, 'requiredgroup', valGroups[uniqueGroup]);
});
} (jQuery));
of course the elements must have a data-val-requiredgroup attribute. The following MVC code (as part of a helper) will add the appropriate annotations:
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
var baseAttr = htmlHelper.GetUnobtrusiveValidationAttributes(name, metaData);
baseAttr.Add("name", name);
baseAttr.Add("type", "checkbox");
if (metaData.IsRequired)
{
baseAttr.Add("data-val-requiredgroup", baseAttr["data-val-required"]);
baseAttr.Remove("data-val-required");
Because it looks for the Required attribute, server side validation is handled by the existing framework.
Related
In ASP.NET i have the following
public class TestController : Controller
{
public IActionResult Index()
{
return View(new MyModel()
{
Activities = new Activity[]
{
new Activity(){ },
new Activity(){ }
}
});
}
}
public class MyModel
{
public IList<Activity> Activities { get; set; }
}
public class Activity
{
public bool IsActive { get; set; }
}
cshtml
#model Metatasker.Integration.UI.Controllers.MyModel
<form id="myform">
#for (int i = 0; i < Model.Activities.Count; i++)
{
#Html.CheckBoxFor(x => x.Activities[i].IsActive)
}
</form>
<button id="btn">Click Me</button>
<script type="text/javascript">
$(function () {
function validate() {
var kendoValidator = $('#myform').kendoValidator().data("kendoValidator");
return kendoValidator.validate();
}
$("#btn").click(function () {
alert(validate());
})
})
</script>
I am using Kendo's Validate method to validate form. In code above when i have multiple CheckBoxes The validate() method always returns false. If i have single check box then it works.
I have jsfiddle demo. The html in jsfiddle is a rendered Razor view.
DEMO
Try the following snippet.
<form id="form1">
<!-- Kendo UI Checkbox -->
<input type="checkbox" id="checkboxMale" name="Gender" required />
<label for="checkbox">Male</label>
<span class="k-invalid-msg" data-for="Gender"></span>
<input type="checkbox" id="checkboxFemale" name="Gender" required />
<label for="checkbox">Female</label>
<span class="k-invalid-msg" data-for="Gender"></span>
<button>Validate</button>
</form>
<script>
$(document).ready(function() {
var validator = $("#form1").kendoValidator().data("kendoValidator");
});
</script>
You will notice that the validator treats only true values as inputs which are filled, else it serves up the required validation message. Each check box is validated individually.
So, I suggest using a custom rule.
<form id="myform">
<ul validationMessage="You must enter a gender">
<li>
<input id="activity1" name="activity" type="checkbox" value="activity1" />
<label for="activity1">Activity1</label>
</li>
<li>
<input id="activity2" name="activity" type="checkbox" value="activity2" />
<label for="activity2">Activity2</label>
</li>
</ul>
<button>Validate</button>
</form>
<script>
$("#myform").kendoValidator({
rules: {
customRule1: function(input) {
if (input.is("[name=activity]")) {
return $("[name='activity']:checked").length > 0;
}
}
},
messages: {
customRule1: "Atleast one activity should be selected."
}
});
</script>
There is also a demo which uses radio buttons, which you can refer to.
I have a edit form which should have checkbox.
My Model for the page is
public class MyModel{
public string Name {get;set;}
public List<AdType> AdTypeList { get; set; }
}
Enum AdType{
[Display(Name = "None")]
None,
[Display(Name = "Additional")]
Additional_Photo,
}
So i have to check the check box with respect to the data coming from the database.
And also update should happen if i make changes and submit.
To work that way what changes i need to make in my html helper for the checkbox?
foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label>
<input name="Roles" type="checkbox" value="#role" checked="#(Model != null)" />
#{var x = EHelper.GetDisplayValue<AdType>(role);}
#x
</label>
}
I am new to mvc, please parden if i m doing something stupid.
you can put if else condition in model to create condition based html control
foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label>
#if(Model != null)
{
<input name="Roles" type="checkbox" value="#role" checked="checked" />
}
else
{
<input name="Roles" type="checkbox" value="#role" />
}
#{var x = EHelper.GetDisplayValue<AdType>(role);}
#x
</label>
}
#foreach (AdType role in Enum.GetValues(typeof(AdType)))
{
<label class="col-sm-12">
<input name="AdTypeList" type="checkbox" value="#role" checked="#(Model != null && Model.AdTypeList!= null && Model.AdTypeList.Any(i => i.HasFlag(role)))" />
#{var text = EHelper.GetDisplayValue<AdType>(role);}
#text
</label>
}
I'm following the info on http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx from Phil Haack
He talks about Non-Sequential indices:
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
Is this possible in MVC3 when you use model binding with TextBoxFor?
This is the way to do it with sequentiel indices:
#Html.TextBoxFor(m => m[i].Value)
If it's not possible, is their anything else I can do if my indices will not be sequential?
AFAI understand the non-sequential indices technique would require to add hidden fields for each index value. I'm quite sure that the Html.TextBoxFor helper itself does not generate any additional hidden fields. Probably you could achieve this by manually adding the hidden fields with the non-sequential indices.
I tried this and I couldn't get it to work with textboxfor. I used Textbox and specified the name.
<form method="post" action="/Home/Create">
#{var index = Guid.NewGuid();}
#Html.Textbox("products["+index +"].Name",Beer)
#Html.Textbox("products["+index +"].Price",7.32)
.
.
.
<input type="submit" />
</form>
You don't need a hidden index for MVC 3.0.
Let me know if this does not work I have a way of doing this, I'm just taking this from the top of my head.
To not messing with your view, the easiest way I found to do this is following this extension: BeginCollectionItem.
The complete project is here: https://github.com/danludwig/BeginCollectionItem
But AFAIK you only need this class:
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null) {
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
How to use it in your Views:
<form method="post" action="/Home/Create">
#foreach (var item in Model.Products) {
#using (Html.BeginCollectionItem("Products"))
{
#Html.TextBoxFor(item => item.Name)
#Html.TextBoxFor(item => item.Price)
}
}
...
...
</form>
I think this is cleaner than messing with the indexs in you views...
Here is the post that explains how to do it step by step: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Nuget Package: http://www.nuget.org/packages/BeginCollectionItem/
#{int i=0;}
#foreach (var e in Model.BlockList)
{
#foreach (var e2 in e.RadioButtons)
{
<label>#Html.RadioButton("r"+i, e2.Id, false) #e2.ViewText</label><br />
}
i++;
}
Question: how in the controller through all the answers?
At this moment my Controller class look like this:
[HttpPost]
public ActionResult Index(DocAlpha a)
{
List<int> results = new List<int>();
int i = 0;
while (Request.Params["r" + i.ToString()] != null)
{
int val = 0;
if(int.TryParse(Request.Params.Get("r" + i.ToString()), out val))
{
results.Add(val);
}
i++;
}
return Index();
}
may be better ways?
You can make an array of radio buttons names like so (note the names):
Html :
<input type="text" name="r[0]" value="" />
<input type="text" name="r[1]" value="" />
<input type="text" name="r[2]" value="" />
<!--and so on -->
Then make your action accept an array:
public ActionResult TheAction (string[] r) {
}
The default model binder will automatically populate the answers array in your action with the values entered in the form.
Crazy issue with submitting of values in Html.Checkbox in ASP.NET MVC RC
Some of the values are just not come to Request.Params
At my form I have this line inside the cycle:
<%=Html.CheckBox("cb" + p.Option.Id, p.Option.IsAllowed, new { value = 6 })%>
and it renders to next:
<input checked="checked" id="cb17" name="cb17" type="checkbox" value="6" />
<input name="cb17" type="hidden" value="false" />
<input checked="checked" id="cb18" name="cb18" type="checkbox" value="6" />
<input name="cb18" type="hidden" value="false" />
<input id="cb19" name="cb19" type="checkbox" value="6" />
<input name="cb19" type="hidden" value="false" />
<input id="cb20" name="cb20" type="checkbox" value="6" />
<input name="cb20" type="hidden" value="false" />
<input checked="checked" id="cb21" name="cb21" type="checkbox" value="6" />
<input name="cb21" type="hidden" value="false" />
After submitting the Form I'm get something like:
Form.Params["cb17"] = {6, "false"}
Form.Params["cb18"] = {6, "false"}
Form.Params["cb19"] = {"false"}
Form.Params["cb20"] = {"6,false"}
Form.Params["cb21"] = {"false"}
In the request string Some of the params are displayed twice (normal situation) and some only ONE TIME (only value of hidden field).
It seems that it doesn't rely on whether checkbox was checked or not, whether value has changed or so...
Does anybody faced with such a situation? How can I work around?
<% using(Html.BeginForm("Retrieve", "Home")) %>//Retrieve is the name of the action while Home is the name of the controller
<% { %>
<%foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(""+app.ApplicationId )%></td>
</tr>
<%} %>
<input type"submit"/>
<% } %>
and in your controller
List<app>=newApps; //Database bind
for(int i=0; i<app.Count;i++)
{
var checkbox=Request.Form[""+app[i].ApplicationId];
if(checkbox!="false")// if not false then true,false is returned
}
the reason you check for false because the Html Checkbox helper does some kind of freaky thing for value true
True returns as:
it makes the string read "true, false"
so you may have thought it was two values but its just one and means true
False returns as:
it makes the string read "false"
This is actually the way it should work according to specifications.
It has nothing to do with ASP.NET MVC, but when a check box is left unchecked it is not included in the POST collection.
You get two values because you have both a checkbox and an input with the same name (and the ones you have two values for are most likely the ones with checkboxes checked).
Edit: specifications from W3C
Without the need to ask database about the ids after form submitting/before saving (Stateless mode) I've produced such code:
foreach (string key in Request.Form)
{
var checkbox = String.Empty;
if (key.StartsWith("cb"))
{
checkbox = Request.Form["" + key];
if (checkbox != "false")
{
int id = Convert.ToInt32(key.Remove(0, 2));
}
}
}
Thanks you guys to help me work around this issue!
I use this:
public struct EditedCheckboxValue
{
public bool Current { get; private set; }
public bool Previous { get; private set; }
public bool Changed { get; private set; }
public EditedCheckboxValue(System.Web.Mvc.FormCollection collection, string checkboxID) : this()
{
string[] values = collection[checkboxID].Split(new char[] { ',' });
if (values.Length == 2)
{ // checkbox value changed, Format: current,old
Current = bool.Parse(values[0]);
Previous = bool.Parse(values[1]);
Changed = (Current != Previous);
}
else if (values.Length == 1)
{
Current = bool.Parse(values[0]);
Previous = Current;
Changed = false;
}
else
throw new FormatException("invalid format for edited checkbox value in FormCollection");
}
}
and then call it like this:
EditedCheckboxValue issomething = new EditedCheckboxValue(collection, "FieldName");
instance.IsSomething = issomething.Current;