3

This code:

Html.CheckBoxList(ViewData.TemplateInfo.HtmlFieldPrefix, myList)

Produces this mark-up:

<ul><li><input name="Header.h_dist_cd" type="checkbox" value="BD" />
        <span>BD - Dist BD Name</span></li>
    <li><input name="Header.h_dist_cd" type="checkbox" value="SS" />
        <span>SS - Dist SS Name</span></li>
    <li><input name="Header.h_dist_cd" type="checkbox" value="DS" />
        <span>DS - Dist DS Name</span></li>
    <li><input name="Header.h_dist_cd" type="checkbox" value="SW" />
        <span>SW - Dist SW Name </span></li>
</ul>

You can check multiple selections. The return string parameter Header.h_dist_cd only contains the first value selected. What do I need to do to get the other checked values?

The post method parameter looks like this:

public ActionResult Edit(Header header)
GEOCHET
  • 21,119
  • 15
  • 74
  • 98
Zachary Scott
  • 20,968
  • 35
  • 123
  • 205

3 Answers3

4

I'm assuming that Html.CheckBoxList is your extension and that's markup that you generated.

Based on what you're showing, two things to check:

  1. The model binder is going to look for an object named Header with string property h_dist_cd to bind to. Your action method looks like Header is the root view model and not a child object of your model.
  2. I don't know how you are handling the case where the checkboxes are cleared. The normal trick is to render a hidden field with the same name.

Also a nit, but you want to use 'label for="..."' so they can click the text to check/uncheck and for accessibility.

I've found that using extensions for this problem is error prone. You might want to consider a child view model instead. It fits in better with the EditorFor template system of MVC2.

Here's an example from our system...

In the view model, embed a reusable child model...

[AtLeastOneRequired(ErrorMessage = "(required)")]
public MultiSelectModel Cofamilies { get; set; }

You can initialize it with a standard list of SelectListItem...

MyViewModel(...)
{
  List<SelectListItem> initialSelections = ...from controller or domain layer...;
  Cofamilies = new MultiSelectModel(initialSelections);
  ...

The MultiSelectModel child model. Note the setter override on Value...

public class MultiSelectModel : ICountable
{
  public MultiSelectModel(IEnumerable<SelectListItem> items)
  {
    Items = new List<SelectListItem>(items);
    _value = new List<string>(Items.Count);
  } 

  public int Count { get { return Items.Count(x => x.Selected); } } 
  public List<SelectListItem> Items { get; private set; }

  private void _Select()
  {
    for (int i = 0; i < Items.Count; i++)
      Items[i].Selected = Value[i] != "false";
  }

  public List<SelectListItem> SelectedItems
  {
    get { return Items.Where(x => x.Selected).ToList(); }
  } 

  private void _SetSelectedValues(IEnumerable<string> values)
  {
    foreach (var item in Items)
    {
      var tmp = item;
      item.Selected = values.Any(x => x == tmp.Value);
    }
  } 

  public List<string> SelectedValues
  {
    get { return SelectedItems.Select(x => x.Value).ToList(); }
    set { _SetSelectedValues(value); }
  } 

  public List<string> Value
  {
    get { return _value; }
    set { _value = value; _Select(); }
  }
  private List<string> _value; 
}

Now you can place your editor template in Views/Shared/MultiSelectModel.ascx...

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<WebUI.Cofamilies.Models.Shared.MultiSelectModel>" %>

<div class="set">

<%=Html.LabelFor(model => model)%>

<ul>
  <% for (int i = 0; i < Model.Items.Count; i++)
  {
    var item = Model.Items[i];
    string name = ViewData.ModelMetadata.PropertyName + ".Value[" + i + "]";
    string id = ViewData.ModelMetadata.PropertyName + "_Value[" + i + "]";
    string selected = item.Selected ? "checked=\"checked\"" : "";
  %>
  <li>
    <input type="checkbox" name="<%= name %>" id="<%= id %>" <%= selected %> value="true" />
    <label for="<%= id %>"><%= item.Text %></label>
    <input type="hidden" name="<%= name %>" value="false" />
  </li>
  <% } %>
</ul>
<%= Html.ValidationMessageFor(model => model) %>

Two advantages to this approach:

  1. You don't have to treat the list of items separate from the selection value. You can put attributes on the single property (e.g., AtLeastOneRequired is a custom attribute in our system)

  2. you separate model and view (editor template). We have a horizontal and a vertical layout of checkboxes for example. You could also render "multiple selection" as two listboxes with back and forth buttons, multi-select list box, etc.

Rob
  • 5,525
  • 1
  • 24
  • 26
  • I might be missing something, but wont `ListBox` achieve a multi-selectable list? – Ahmad Jul 30 '10 at 08:46
  • ListBox will, but it doesn't have the checkboxes. Our clients don't understand holding down a control key will allow you to select multiple items. They need the checkbox. Unfortunately, the native checkbox is true/false, not a set of selections. (to my knowledge). – Zachary Scott Jul 30 '10 at 15:01
2

I think what you need is how gather selected values from CheckBoxList that user selected and here is my solution for that:

1- Download Jquery.json.js and add it to your view as reference:

2- I've added a ".cssMyClass" to all checkboxlist items so I grab the values by their css class:

 <script type="text/javascript" >
       $(document).ready(function () {
           $("#btnSubmit").click(sendValues);
         });

     function populateValues()
     {
         var data = new Array();
         $('.myCssClas').each(function () {
             if ($(this).attr('checked')) {
                 var x = $(this).attr("value");
                 data.push(x);
             }
         }); 

         return data;
     }

     function sendValues() {
         var data = populateValues();
               $.ajax({
                   type: 'POST',
                   url: '@Url.Content("~/Home/Save")',
                   data: $.json.encode(data),
                   dataType: 'json',
                   contentType: 'application/json; charset=utf-8',
                   success: function () { alert("1"); }
               });

       } 



 </script>

3- As you can see I've added all selected values to an Array and I've passed it to "Save" action of "Home" controller by ajax 4- in Controller you can receive the values by adding an array as argument:

 [HttpPost]
        public ActionResult Save(int[] val)
        {

I've searched too much but apparently this is the only solution. Please let me know if you find a better solution for it.

Amir978
  • 857
  • 3
  • 15
  • 38
1

when you have multiple items with the same name you will get their values separated with coma

Omu
  • 69,856
  • 92
  • 277
  • 407
  • 1
    Interestingly, MVC wasn't binding this comma separated value to the string. It only took the first value in the set. When we change the property type to IList, it found the other values selected. – Zachary Scott Jul 30 '10 at 15:03
  • 2
    just use object, look at the sample mvc project from here: http://valueinjecter.codeplex.com/ – Omu Jul 31 '10 at 11:30