views:

1136

answers:

2

I have a class called "PropertyFeature" which simply contains PropertyFeatureID and Description. It's a proper model created through LINQ to SQL mapped to an SQL Server database table. An example instance/row would be:

PropertyFeatureID: 2 Description: "Swimming Pool"

The number of rows (PropertyFeatures) can of course grow and shrink and so I want to dynamically render a list of checkboxes so that the user can select any of them. I can dynamically render the Checkboxes easily enough, with something like:

<%foreach (var Feature in (ViewData["Features"] as IEnumerable<MySolution.Models.PropertyFeature>)) { %>
<%=Html.CheckBox("Features", new { @id = Feature.PropertyFeatureID, @value = Feature.PropertyFeatureID })%><label for="Feature<%=Feature.PropertyFeatureID%>"><%=Feature.Description%></label>
<%}%>

I specify the ID for each checkbox and render the matching label so that the user can intuitively click the label and toggle the checkbox - that works great.

I set the CheckBox's "name" to "Features" so all the checkboxes render with the same name, and the MVC Model Binder piles them into a single collection called "Features" when the form is posted. This works nicely.

Once the form is submitted, I use the checked values and store them, so I need the actual int values so I know which PropertyFeature is selected, not just a pile of bools and field names. So ideally, I want it as an array or a collection that's easy to work with.

I am able to retrieve the selected values from within my Controller method when the button is clicked because I have specified the parameter as int[] Features.

But the problem is that it doesn't maintain state. I.e. when I click the submit button and the page reloads (with the form again displayed) I want all of the dynamic checkboxes to retain their checked status (or not). All of the other fields that I've created with Html.DropDownList and Html.TextBox all maintain their states successfully no problems at all on the same page in the same form.

I have spent hours reading all of the other threads and articles on similar issues and there is a lot of talk about using ICollection and IDictionary to bundle things up and include a bool value for each item so that it can maintain the checkbox state. But I don't 100% grasp how to use that in the context of my own personal example. I would like to keep the solution really simple and not have to code up pages of new classes just to maintain my checkbox state.

What is the cleanest/proper way to do this?

A: 

The problem is that when you are rendering your list of checkboxes, you aren't setting any of them as selected. You will need to set your int[] Features in ViewData, and then in your foreach loop, check to see if the ID of that Feature is in the array in ViewData.

something like:

<%=Html.CheckBox("Features", 
    ((int[])ViewData["SelectedFeatures"]).Contains(Feature.PropertyFeatureID),
    new { @id = Feature.PropertyFeatureID, @value = Feature.PropertyFeatureID })%

although I didn't test it, so it might not be 100%.

dave thieben
Thanks. Sorry, to clarify further. I don't need the checkboxes to be checked initially. It is just on a search form. They should always be blank, then the user just selects which ones they want to search for. But then I want those selected ones to be retained when the page reloads.
Aaron
right. the first time the page loads, your SelectedFeatures array will be empty, so no checkboxes will be checked. then on your postback, you'll pop the SelectedFeatures array into ViewData, and it'll get used when the View loads.
dave thieben
Yeah exactly. Cheers for your help Dave.
Aaron
+2  A: 

I got it working after much playing around with the various different approaches.

In the view:

<%string[] PostFeatures = Request.Form.GetValues("Features");%>
<%foreach (var Feature in (ViewData["AllPropertyFeatures"] as IEnumerable<MySolution.Models.PropertyFeature>)) { %>
    <input type="checkbox" name="Features" id="Feature<%=Feature.PropertyFeatureID.ToString()%>" value="<%=Feature.PropertyFeatureID%>" <%if(PostFeatures!=null){if(PostFeatures.Contains(Feature.PropertyFeatureID.ToString())){ Response.Write("checked=\"checked\""); }}%> /><label for="Feature<%=Feature.PropertyFeatureID%>"><%=Feature.Description%></label>
<%}%>

In the receiving controller method:

public ActionResult SearchResults(int[] Features)

This method has a number of advantages:

  • Allows labels to be clicked to toggle the corresponding checkboxes (usability).
  • Allows the Controller method to receive a super tidy array of ints, which ONLY contains the ints that have been selected - and not a whole other pile of items which were unselected or containing false/null/blank/0 etc.
  • Retains the checkbox's checked state when the page reloads containing the form, i.e. the user's selection is retained.
  • No random/stray type=hidden input fields created from the default ASP.Net MVC Html.CheckBox helper - I know it does those for a good reason, but in this instance, I don't require them as I only want to know about which IDs have been selected and for those to be in a single, tidy int[].
  • No masses of additional server side bloated classes, helpers and other gay mess required to achieve such a simple thing.

I would recommend this approach for anyone wanting the cleanest / bloat-free solution for a dynamic checkbox list where you need the IDs and you just want to get down to business!

Aaron