开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜