How to use a radio button to select a list item in MVC3
I need to present my user with a list of package options, from which they select one. I don't want to use a radio button list, as I need fairly complex templating for each list item. Forgive me开发者_Python百科, but I just can't seem to figure out how to link a column of radio buttons to a selection property in my view model.
My view model has a SelectedPackageId
(int), and a list of MemberPackageListItem
view models that represent the individual packages. MemberPackageListItem
has a PackageId
(int), so I need to couple the PackageId
of the selected item to the SelectedPackageId
of the root view model.
It is hard for me to post code, as inheritance etc. obscures much of what you would want to see, so I'm hoping my outline of a fairly common scenario, and some general guidelines on using radio buttons in this scenario will suffice to help me continue.
I would suggest using an HtmlHelper to render your radio button list, as follows:
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString RadioButtonListFor<TModel, TList, TSelectedItem>(
this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TList>> expression,
Expression<Func<TModel, TSelectedItem>> selectedItem)
{
return RadioButtonListFor(htmlHelper, expression, selectedItem, null /* htmlAttributes */);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString RadioButtonListFor<TModel, TList, TSelectedItem>(
this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TList>> expression,
Expression<Func<TModel, TSelectedItem>> selectedItem, object htmlAttributes)
{
return RadioButtonListFor(htmlHelper, expression, selectedItem, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString RadioButtonListFor<TModel, TList, TSelectedItem>(
this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TList>> expression,
Expression<Func<TModel, TSelectedItem>> selectedItem, IDictionary<string, object> htmlAttributes)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IEnumerable<SelectListItem> items = null;
if (metadata.Model != null)
{
IEnumerable<SelectListItem> modelItems = (IEnumerable<SelectListItem>)metadata.Model;
if (modelItems != null)
{
items = modelItems;
}
}
ModelMetadata selectedItemMetadata = ModelMetadata.FromLambdaExpression(selectedItem, htmlHelper.ViewData);
return RadioButtonListHelper(htmlHelper, metadata, selectedItemMetadata, ExpressionHelper.GetExpressionText(selectedItem), items, htmlAttributes);
}
private static MvcHtmlString RadioButtonListHelper(HtmlHelper htmlHelper, ModelMetadata metadata,
ModelMetadata selectedItemMetadata, string name, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
{
// Verify arguments
if (String.IsNullOrEmpty(name)) throw new ArgumentNullException("name", "Name cannot be null");
if (selectList == null) throw new ArgumentNullException("selectList", "Select list cannot be null");
if (selectList.Count() < 1) throw new ArgumentException("Select list must contain at least one value", "selectList");
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
string fullId = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix + "_" + name;
IDictionary<string, object> validationAttributes = htmlHelper
.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(name), selectedItemMetadata);
// Define items
StringBuilder items = new StringBuilder();
// Loop through items
Int32 index = 0;
foreach (SelectListItem i in selectList)
{
// Define check box input
TagBuilder input = new TagBuilder("input");
input.MergeAttribute("type", "radio");
input.MergeAttribute("name", fullName, true);
if (i.Selected)
input.MergeAttribute("checked", "checked");
input.MergeAttribute("value", i.Value);
if (index == 0)
input.MergeAttributes(validationAttributes);
input.MergeAttributes(htmlAttributes);
// Define label
TagBuilder label = new TagBuilder("label");
label.MergeAttribute("for", fullId + "[" + index.ToString() + "].Selected");
label.InnerHtml = i.Text;
// Add item
items.AppendFormat("\r\t<div>\r\t\t{0}\r\t\t{1}\r\t</div>",
input.ToString(TagRenderMode.Normal),
label.ToString(TagRenderMode.Normal));
index++;
}
// Return list
return new MvcHtmlString(items.ToString() + "\r");
}
Please note that MemberPackageListItem
must be of type IEnumerable<SelectListItem>
. Usage is as follows (Razor syntax):
@Html.RadioButtonListFor(m => m.MemberPackageListItem, m => m.SelectedPackageId)
counsellorben
While I appreciate the technical comprehensiveness of @counsellorben's answer, and will keep it around for future use, today I arrived at a more immediate and not altogether clumsy jQuery solution. The selectors could be more specific, but I have no need now. My solution is below. Radio type inputs are grouped by their name attribute, which is given a different index for each row. Therefore:
$(function () {
// Back up current names of package radio buttons, then make all their names the same for grouping.
$("#packageForm :radio[name$='.IsSelected']").each(function () {
$(this).attr("oldname", $(this).attr("name"));
});
$(":radio[name$='.IsSelected']").attr("name", "Package.IsSelected");
// Hook the 'submit' click to restore original radio button names.
$("#packageForm :submit").click(function () {
$(":radio[name='Package.IsSelected']").each(function () {
$(this).attr("oldname", $(this).attr("name"));
});
});
});
IsSelected is my property per row that tells me if that row is selected, it isn't a jQuery or DOM property.
精彩评论