views:

122

answers:

4

Im working on a page where the user needs to fill in some information and finally make a selection of 1 or more customers with checkboxes.

The list of customers is an IEnumerable<Customer> which i pass into my Model. Im a bit puzzled how i would go about creating the list of checkboxes using .CheckBoxFor().

And finally i would like to be able to validate if atleast 1 checkbox has been selected.

Request is the object which holds the information the user entered.

<% foreach (var customer in Model.Request.Customers) { %>
   <%= Html.CheckBoxFor(/* customer */) %>
<% } %>

Can anyone point me in the right direction? Or am i going about this all wrong?

+1  A: 

You could create a custom html extensions class and overload the CheckBoxFor method like below. The method evaluates the metadata.Model to the value passed into it (like U.S. State). You can get the checkbox value/s from the FormCollection in the ControllerAction:

public ActionResult Edit(FormCollection formCollection) 
{
    // Get the value(s)
    string checkBox = formCollection["State"];

    // perform validation
    ....
}

Example assumes a keyvaluepair generic list

<% foreach (var element in UnitedStatesDictionary())
{ %>
<%= Html.CheckBoxFor(model => model.State, null, element.Key) %><%= element.Value  %><br />
<% } %>

HtmlExtensions.cs

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;

    public static class HtmlExtensions
    {
        /// <summary>
        /// Checks the box for.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="html">The HTML.</param>
        /// <param name="expression">The expression.</param>
        /// <returns>Checkbox</returns>
        public static MvcHtmlString CheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
        {
            return CheckBoxFor(html, expression, new RouteDirection());
        }


        /// <summary>
        /// Checks the box for.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="html">The HTML.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns>Checkbox</returns>
        public static MvcHtmlString CheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
        {

            return CheckBoxFor(html, expression, htmlAttributes, "");
        }

        /// <summary>
        /// Checks the box for.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="html">The HTML.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <param name="checkedValue">The checked value.</param>
        /// <returns>Checkbox</returns>
        public static MvcHtmlString CheckBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes, string checkedValue)
        {

            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            string htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            TagBuilder tag = new TagBuilder("input");
            tag.Attributes.Add("type", "checkbox");
            tag.Attributes.Add("name", metadata.PropertyName);
            if (!string.IsNullOrEmpty(checkedValue))
            {
                tag.Attributes.Add("value", checkedValue);
            }
            else
            {
                tag.Attributes.Add("value", metadata.Model.ToString());
            }

            if (htmlAttributes != null)
            {
                tag.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            }

            if (metadata.Model.ToString() == checkedValue)
            {
                tag.Attributes.Add("checked", "checked");
            }
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.SelfClosing));
        }
    }

While I'm at it, here's my list of United States to complete code:

/// <summary>
/// United States dictionary.
/// </summary>
/// <returns>List of United States</returns>
public static List<KeyValuePair<string, string>> UnitedStatesDictionary()
{
    var arrList = new List<KeyValuePair<string, string>>();
    arrList.Add(new KeyValuePair<string, string>("AL", "Alabama"));
    arrList.Add(new KeyValuePair<string, string>("AK", "Alaska"));
    arrList.Add(new KeyValuePair<string, string>("AZ", "Arizona" ));
    arrList.Add(new KeyValuePair<string, string>("AR", "Arkansas" ));
    arrList.Add(new KeyValuePair<string, string>("CA", "California" ));
    arrList.Add(new KeyValuePair<string, string>("CO", "Colorado" ));
    arrList.Add(new KeyValuePair<string, string>("CT", "Connecticut" ));
    arrList.Add(new KeyValuePair<string, string>("DE", "Delaware" ));
    arrList.Add(new KeyValuePair<string, string>("DC", "District Of Columbia" ));
    arrList.Add(new KeyValuePair<string, string>("FL", "Florida" ));
    arrList.Add(new KeyValuePair<string, string>("GA", "Georgia" ));
    arrList.Add(new KeyValuePair<string, string>("HI", "Hawaii" ));
    arrList.Add(new KeyValuePair<string, string>("ID", "Idaho" ));
    arrList.Add(new KeyValuePair<string, string>("IL", "Illinois" ));
    arrList.Add(new KeyValuePair<string, string>("IN", "Indiana" ));
    arrList.Add(new KeyValuePair<string, string>("IA", "Iowa" ));
    arrList.Add(new KeyValuePair<string, string>("KS", "Kansas" ));
    arrList.Add(new KeyValuePair<string, string>("KY", "Kentucky" ));
    arrList.Add(new KeyValuePair<string, string>("LA", "Louisiana" ));
    arrList.Add(new KeyValuePair<string, string>("ME", "Maine" ));
    arrList.Add(new KeyValuePair<string, string>("MD", "Maryland" ));
    arrList.Add(new KeyValuePair<string, string>("MA", "Massachusetts" ));
    arrList.Add(new KeyValuePair<string, string>("MI", "Michigan" ));
    arrList.Add(new KeyValuePair<string, string>("MN", "Minnesota" ));
    arrList.Add(new KeyValuePair<string, string>("MS", "Mississippi" ));
    arrList.Add(new KeyValuePair<string, string>("MO", "Missouri" ));
    arrList.Add(new KeyValuePair<string, string>("MT", "Montana" ));
    arrList.Add(new KeyValuePair<string, string>("NE", "Nebraska" ));
    arrList.Add(new KeyValuePair<string, string>("NV", "Nevada" ));
    arrList.Add(new KeyValuePair<string, string>("NH", "New Hampshire" ));
    arrList.Add(new KeyValuePair<string, string>("NJ", "New Jersey" ));
    arrList.Add(new KeyValuePair<string, string>("NM", "New Mexico" ));
    arrList.Add(new KeyValuePair<string, string>("NY", "New York" ));
    arrList.Add(new KeyValuePair<string, string>("NC", "North Carolina" ));
    arrList.Add(new KeyValuePair<string, string>("ND", "North Dakota" ));
    arrList.Add(new KeyValuePair<string, string>("OH", "Ohio" ));
    arrList.Add(new KeyValuePair<string, string>("OK", "Oklahoma" ));
    arrList.Add(new KeyValuePair<string, string>("OR", "Oregon" ));
    arrList.Add(new KeyValuePair<string, string>("PA", "Pennsylvania" ));
    arrList.Add(new KeyValuePair<string, string>("RI", "Rhode Island" ));
    arrList.Add(new KeyValuePair<string, string>("SC", "South Carolina" ));
    arrList.Add(new KeyValuePair<string, string>("SD", "South Dakota" ));
    arrList.Add(new KeyValuePair<string, string>("TN", "Tennessee" ));
    arrList.Add(new KeyValuePair<string, string>("TX", "Texas" ));
    arrList.Add(new KeyValuePair<string, string>("UT", "Utah" ));
    arrList.Add(new KeyValuePair<string, string>("VT", "Vermont" ));
    arrList.Add(new KeyValuePair<string, string>("VA", "Virginia" ));
    arrList.Add(new KeyValuePair<string, string>("WA", "Washington" ));
    arrList.Add(new KeyValuePair<string, string>("WV", "West Virginia" ));
    arrList.Add(new KeyValuePair<string, string>("WI", "Wisconsin" ));
    arrList.Add(new KeyValuePair<string, string>("WY", "Wyoming" ));
    return arrList;
}
gnome
+1 really like the idea of creating custom extensions to solve ... well any issue really
Fabian
+2  A: 

I used a helper class for that. it's quite simple really. With the helper class you can use a SelectList and put it in the helper, like you'd do for a dropdownfor.

in the folder "Helpers" i have Checkboxlist.cs

    using System;
    using System.Web.Mvc;
    using System.Collections.Generic;
    using System.Text;
    using System.Linq;

    namespace MVC2_NASTEST.Helpers {
        public static class CheckBoxListHelper {

            public static string CheckBoxList(this HtmlHelper helper, string name, IDictionary<string, string> items) {
                return CheckBoxList(helper, name, items, null, null);
            }

            public static string CheckBoxList(this HtmlHelper helper, string name, IDictionary<string, string> items, IDictionary<string, object> checkboxHtmlAttributes) {
                return CheckBoxList(helper, name, items, null, checkboxHtmlAttributes);
            }

            public static string CheckBoxList(this HtmlHelper helper, string name, IDictionary<string, string> items, IEnumerable<string> selectedValues) {
                return CheckBoxList(helper, name, items, selectedValues, null);
            }

            public static string CheckBoxList(this HtmlHelper helper, string name, IDictionary<string, string> items, IEnumerable<string> selectedValues, IDictionary<string, object> checkboxHtmlAttributes) {

                var selectListItems = from i in items
                                      select new SelectListItem {
                                          Text = i.Key,
                                          Value = i.Value,
                                          Selected = (selectedValues != null && selectedValues.Contains(i.Value))
                                      };

                return CheckBoxList(helper, name, selectListItems, checkboxHtmlAttributes);
            }

            public static string CheckBoxList(this HtmlHelper helper, string name, IEnumerable<SelectListItem> items) {
                return CheckBoxList(helper, name, items, null);
            }

            public static string CheckBoxList(this HtmlHelper helper, string name, IEnumerable<SelectListItem> items, IDictionary<string, object> checkboxHtmlAttributes) {
                var output = new StringBuilder();

                foreach (var item in items) {
                    output.Append("<div class=\"fields\"><label>");
                    var checkboxList = new TagBuilder("input");
                    checkboxList.MergeAttribute("type", "checkbox");
                    checkboxList.MergeAttribute("name", name);
                    checkboxList.MergeAttribute("value", item.Value);

                    // Check to see if it's checked
                    if (item.Selected)
                        checkboxList.MergeAttribute("checked", "checked");

                    // Add any attributes
                    if (checkboxHtmlAttributes != null)
                        checkboxList.MergeAttributes(checkboxHtmlAttributes);

                    checkboxList.SetInnerText(item.Text);
                    output.Append(checkboxList.ToString(TagRenderMode.SelfClosing));
                    output.Append("&nbsp; " + item.Text + "</label></div>");
                }

                return output.ToString();
            }
        }
    }

the code in my controller:

    public static List<SelectListItem> lesgeverList() {
        return lesgeverList(-1);
    }

    public static List<SelectListItem> lesgeverList(int selectedID) {
        return lesgeverList(new int[] { selectedID });
    }

    public static List<SelectListItem> lesgeverList(int[] lg) {
        NASDataContext _db = new NASDataContext();
        var lesg = (from l in _db.Lesgevers
                    where l.LG_Naam != "leeg"
                    orderby l.LG_Naam, l.LG_Vnaam
                    select l).ToSelectList(m => m.LG_Naam + " " + m.LG_Vnaam, m => m.LG_ID.ToString(), m => lg.Contains(m.LG_ID));
        return lesg.ToList();
    }

    //
    // GET: /Projectleiders/Create

    public ActionResult Create(int projID) {
        ViewData["projNaam"] = getProject(projID).Proj_Kortenaam;
        int[] keys = (from p in _db.ProjectleiderProjectens
                      where p.Proj_ID == projID
                      from l in _db.Lesgevers
                      where p.LG_ID == l.LG_ID
                      select l.LG_ID).ToArray();

        ViewData["projleiders"] = MvcApplication.lesgeverList(keys);

        return toegankelijk(projID, null);
    }

    //
    // POST: /Projectleiders/Create

    [HttpPost]
    public ActionResult Create(FormCollection collection, int projID) {

        if (collection["lesgeverlist"] != null) {
            string[] lgevers = collection["lesgeverlist"].Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            List<ProjectleiderProjecten> lst = new List<ProjectleiderProjecten>();
            foreach (string s in lgevers) {
                ProjectleiderProjecten prl = new ProjectleiderProjecten();
                prl.LG_ID = int.Parse(s);
                prl.Proj_ID = projID;

                int count = (from m in _db.ProjectleiderProjectens
                             where m.LG_ID == prl.LG_ID && m.Proj_ID == prl.Proj_ID
                             select m).Count();

                if (count <= 0) {
                    //deze bestaat nog niet
                    lst.Add(prl);
                }
            }
            //var test = _db.ProjectleiderProjectens.Where(p => p.Proj_ID == projID && !lgevers.Contains(p.LG_ID.ToString())).ToList();

            _db.ProjectleiderProjectens.DeleteAllOnSubmit(_db.ProjectleiderProjectens.Where(p => p.Proj_ID == projID && !lgevers.Contains(p.LG_ID.ToString())));

            _db.ProjectleiderProjectens.InsertAllOnSubmit(lst);
            _db.SubmitChanges();

            return RedirectToAction("Index");
        } else {

            ModelState.AddModelError("lesgeverlist", "Je hebt geen lesgevers geselecteerd");

            ViewData["projleiders"] = MvcApplication.lesgeverList();
            ViewData["projNaam"] = getProject(projID).Proj_Kortenaam;
            return View();
        }
    }

I use the ToSelectList extension which is something everyone should have.

public static class VSKOExtensions {
    public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> itemsToMap, Func<T, string> textProperty, Func<T, string> valueProperty, Predicate<T> isSelected) {
        var result = new List<SelectListItem>();

        foreach (var item in itemsToMap) {
            result.Add(new SelectListItem {
                Value = valueProperty(item),
                Text = textProperty(item),
                Selected = isSelected(item)
            });
        }
        return result;
    }
}

the code in my create view (which also is an edit view in the same time) is very simple

    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MVC2_NASTEST.Models.ProjectleiderProjecten>" %>
    <%@ Import Namespace="MVC2_NASTEST.Helpers" %>

    <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
        Create
    </asp:Content>

    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

        <h2>Koppel projectleider voor
            <%= ViewData["projNaam"].ToString() %></h2>

        <% using (Html.BeginForm()) {%>
            <%= Html.ValidationSummary(true) %>

            <fieldset>
                <legend>Fields</legend>

                <div class="editor-label">
                    <%= Html.Label("Lesgevers")%>
                </div>
                <div class="editor-field">
                    <%= Html.CheckBoxList("Lesgeverlist", ViewData["projleiders"] as List<SelectListItem>)%>
                    <%= Html.ValidationMessage("Lesgeverlist")%>
                </div>

                <p>
                    <input type="submit" value="Create" />
                </p>
            </fieldset>

        <% } %>

        <div>
            <%= Html.ActionLink("Back to List", "Index") %>
        </div>

    </asp:Content>

how it works:

i get the values from the DB of the already selected users, get their id's and give it to the method MvcApplication.lesgeverList(keys);

then i get the selectlist back, put it in the viewdata, and the selected persons are selected in my view when i open it up. then when i change some boxes and save it, i check if the collection isnt null (so something is selected) then i split the variables i get back, which are the 'value's you give in the SelectList. I foreach through them, parse them to ints, get the users from the DB with their parsed ID's. With the count i look if they exist or not in the 'linked' table which is called _db.ProjectleiderProjectens

when all are added, i delete all the 'unselected' ones in 1 time using the linq statement

   _db.ProjectleiderProjectens.DeleteAllOnSubmit(_db.ProjectleiderProjectens.Where(p => p.Proj_ID == projID && !lgevers.Contains(p.LG_ID.ToString())));

which i think is pretty readable. delete all objects from, get all objects their ID and check which ones are not in the string[] of ID's

it works rather well i must say. if you have some more questions just ask.

Stefanvds
+1 awesome use of extensions with .CheckBoxList() .ToSelectList<T>() ... psst ViewData -> bad! :D
Fabian
So what do you use instead of ViewData for these kind of things? :) not a big fan of it, but dont know a better sollution.
Stefanvds
I would pass that info through the view model
Fabian
A: 

Just use jQuery validate on the client side and then to double check yourself on the server side just ensure that the form collection has a value populated.

There is nothing wrong with your for loop.

Martin Murphy
I dont have access to the single customer object (from the loop) in the labmda expression
Fabian
I don't know the answer but you could just do <input type="check" value="<%= customer.whatever %>" /> and insert whatever you like.
Martin Murphy
+1  A: 

Html.CheckBoxFor() extension method is designed to edit model's property of type boolean. You want to use it to select some objects from IEnumerable collection. It's wrong.

Right way:

in view

<form action="/Customer/Process">
<% foreach (var customer in Model.Request.Customers)
   { %>
        <input type="checkbox" name="selectedId" value="<%= customer.id %>" />
        <%= customer.name %>
        <br/>
<% } %>
        <input type="submit"/>
</form>

in controller

public string Process(IEnumerable<Guid> selectedId)
{
    if (selectedId == null)
    {
        ModelState.AddModelError("selectedId", "Have to select at least one customer!");

        return View();
    }

    // do something with customers' ids
}
Alexander Prokofyev