How to create a CheckBoxListFor extension method in ASP.NET MVC?
Asked Answered
B

4

15

I know there is a ListBoxFor extension method among the ASP.NET MVC Html helper extension methods, but I always thought that a checkbox list is more user-friendly than a list box.

There was a very convenient CheckBoxList control in good old WebForms, but obviously that is out of the picture now.

The question is, why is there no way in ASP.NET MVC to create a check box list? How can I write my own extension method that creates a check box list and behaves in a similar way ListBoxFor behaves?

Bessiebessy answered 8/10, 2010 at 9:45 Comment(0)
F
34

Here is a strongly typed HtmlHelper for CheckBoxListFor that handles selected items as an array in your viewdata model. I chose not to wrapper the Html.CheckBox or Html.CheckBoxFor methods as I don't want the hidden "false" fields in my checkbox lists.

Please feel free to improve on this and repost :-)

//View

<%: Html.CheckBoxListFor(model => model.FreightTypeIds, FreightTypeMultiSelectList)  %>

//Controller

    public ActionResult SomeAction(int[] FreightTypeIds)
    {
       //...

       return View();
    }


//Extension
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList allOptions, object htmlAttributes = null)
{
    ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression<TModel, IEnumerable<TProperty>>(expression, htmlHelper.ViewData);

    // Derive property name for checkbox name
    string propertyName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.PropertyName);

    // Get currently select values from the ViewData model
    IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);

    // Convert selected value list to a List<string> for easy manipulation
    IList<string> selectedValues = new List<string>();

    if (list != null)
    {
        selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
    }

    // Create div
    TagBuilder divTag = new TagBuilder("div");
    divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);

    // Add checkboxes
    foreach (SelectListItem item in allOptions)
    {
        divTag.InnerHtml += string.Format(
                                          "<div><input type=\"checkbox\" name=\"{0}\" id=\"{1}_{2}\" " +
                                          "value=\"{2}\" {3} /><label for=\"{1}_{2}\">{4}</label></div>",
                                          propertyName,
                                          TagBuilder.CreateSanitizedId(propertyName),
                                          item.Value,
                                          selectedValues.Contains(item.Value) ? "checked=\"checked\"" : string.Empty,
                                          item.Text);
    }

     return MvcHtmlString.Create(divTag.ToString());
}
Firelock answered 30/10, 2010 at 4:43 Comment(5)
thanks, this seems to be what I was looking for. Will try it once I get home.Bessiebessy
With some modifications, I successfully made it to work as I wanted. Thank you very much for the code! :)Bessiebessy
@RafaelSoares what kind of validation are you planning to do? that at least one has to be ticked or something similar?Firelock
@RafaelSoares - In situations like this I tend to use a hidden field e.g. "CheckboxChecked" with no initial value and a 'Required' validation message on it. Then I wire up some jquery to the checkboxes so that if one or more of the checkboxes is "checked" the value in the hidden field is set to true|yes|ok or anything else. If none are checked just remove the value from the hidden field and the validation should work.Firelock
If you have this nested inside an EditorFor it will not bind correctly. I had to set the propertyName variable from htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.PropertyName); Then use a sanitized id with TagBuilder.CreateSanitizedId()Misfeasance
G
2

I'm still experimenting but this seems to get along with the default binder and persists the user selections after post.. Hidden fields, really?? .. will this fly in html5 ? This feels crazy but I'd rather do this than hit my db for drop down lists and checkbox lists just because ModelState.IsValid is false..

        public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, List<SelectListItem> list, string ModelCollectionName)
    {
        var sb = new StringBuilder();

        if (list != null)
        {
            int i = 0;

            foreach (var l in list)
            {
                string collectionNameIndex = String.Format("{0}[{1}]", ModelCollectionName, i);

                var hiddenName = new TagBuilder("input");
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Text")));
                hiddenName.Attributes.Add(new KeyValuePair<string, string>("value", l.Text));

                var hiddenValue = new TagBuilder("input");
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("type", "hidden"));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("name", String.Format("{0}.{1}", collectionNameIndex, "Value")));
                hiddenValue.Attributes.Add(new KeyValuePair<string, string>("value", l.Value));

                var checkBoxTag = htmlHelper.CheckBox(String.Format("{0}.{1}", collectionNameIndex, "Selected"), l.Selected);

                var labelTag = new TagBuilder("label");
                labelTag.Attributes.Add(new KeyValuePair<string, string>("for", String.Format("{0}.{1}", collectionNameIndex, "Name")));
                labelTag.SetInnerText(l.Text);

                sb.Append(hiddenName);
                sb.Append(hiddenValue);
                sb.Append(checkBoxTag);
                sb.Append(labelTag);
                sb.Append("<br/>");

                i++;
            }
        }

        return MvcHtmlString.Create(sb.ToString());
    }
Garret answered 14/4, 2011 at 21:20 Comment(0)
B
1

While Microsoft employees are probably the only ones that can answer why such helper method doesn't exist you could try:

Model:

public class MyViewModel
{
    public bool[] Values { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel 
        { 
            Values = new[] { true, false, true, false }
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

<% using (Html.BeginForm()) { %>
    <%: Html.EditorFor(x => x.Values) %>
    <input type="submit" value="OK" />
<% } %>

As you can see EditorFor will handle everything that's needed.

Beckmann answered 8/10, 2010 at 13:48 Comment(2)
This is a nice idea, but as I said, I'd like to have something similar to the behaviour of ListBoxFor.Bessiebessy
Can you be a little more specific? ListBoxFor generates <select> and accepts different types. What exactly do you need?Beckmann
R
0

You might be interested in CheckBoxList Helper for MVC article by Jeremiah Clark (unfortunately it's dated Nov 2008 and concerns MVC 1).

Rovit answered 8/10, 2010 at 11:14 Comment(1)
The article is good, but it is not written with strongly typed HTML helpers in mind.Bessiebessy

© 2022 - 2024 — McMap. All rights reserved.