37

I have this Razor Template

<table>
<tr>
    <td>@Html.RadioButtonFor(i => i.Value, "1")</td>
    <td>@Html.LabelFor(i => i.Value, "true")</td>
</tr>
<tr>
    <td>@Html.RadioButtonFor(i => i.Value, "0")</td>
    <td>@Html.LabelFor(i => i.Value, "false")</td>
</tr>
</table>

That gives me this HTML

<table>
<tr>
    <td><input id="Items_1__Value" name="Items[1].Value" type="radio" value="1" /></td>
    <td><label for="Items_1__Value">true</label></td>
</tr>
<tr>
    <td><input checked="checked" id="Items_1__Value" name="Items[1].Value" type="radio" value="0" /></td>
    <td><label for="Items_1__Value">false</label></td>
</tr>
</table>

So I have the ID Items_1__Value twice which is - of course - not good and does not work in a browser when I click on the second label "false" the first radio will be activated.

I know I could add an own Id at RadioButtonFor and refer to that with my label, but that's not pretty good, is it? Especially because I'm in a loop and cannot just use the name "value" with an added number, that would be end up in multiple Dom Ids in my final HTML markup as well.

Shouldn't be a good solution for this?

Marc
  • 6,749
  • 9
  • 47
  • 78
  • I think you best option is to create your own custom HtmlHelper that can output a Radio button and a label that takes in to account the default value you passed in. – eth0 Dec 05 '11 at 14:01
  • I went for the easy option and used Html.Raw to write the label html, there is only so much it needs to do. Used the `ViewData.TemplateInfo.GetFullHtmlFieldId` to generate the id for the radio buttons so it can be used in the label. – PhilW Feb 16 '12 at 20:09

7 Answers7

71

Don't over-engineer a solution for this. All you are trying to accomplish is to have the radio buttons respond to clicks on the text. Keep it simple and just wrap your radio buttons in label tags:

<table>
<tr>
    <td><label>@Html.RadioButtonFor(i => i.Value, "1")True</label></td>
</tr>
<tr>
    <td><label>@Html.RadioButtonFor(i => i.Value, "0")False</label></td>
</tr>
</table>

The LabelFor html helper is usually used to bring in the Name from the Display attribute on your View Model (e.g. "[Display(Name = "Enter your Name")]).

With radio buttons, the name isn't particularly useful, because you have a different line of text for each radio button, meaning you are stuck hard coding the text into your view anyway.

Roger Rouse
  • 2,287
  • 2
  • 15
  • 10
52

I've been wondering how MVC determines "nested" field names and IDs. It took a bit of research into the MVC source code to figure out, but I think I have a good solution for you.

How EditorTemplates and DisplayTemplates determine field names and IDs

With the introduction of EditorTemplates and DisplayTemplates, the MVC framework added ViewData.TemplateInfo that contains, among other things, the current "field prefix", such as "Items[1].". Nested templates use this to create unique names and IDs.

Create our own unique IDs:

The TemplateInfo class contains an interesting method, GetFullHtmlFieldId. We can use this to create our own unique IDs like so:

@{string id = ViewData.TemplateInfo.GetFullHtmlFieldId("fieldName");}
@* This will result in something like "Items_1__fieldName" *@

For The Win

Here's how to achieve the correct behavior for your example:

<table>
<tr>
    @{string id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioTrue");}
    <td>@Html.RadioButtonFor(i => i.Value, "1", new{id})</td>
    <td>@Html.LabelFor(i => i.Value, "true", new{@for=id})</td>
</tr>
<tr>
    @{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioFalse");}
    <td>@Html.RadioButtonFor(i => i.Value, "0", new{id})</td>
    <td>@Html.LabelFor(i => i.Value, "false", new{@for=id})</td>
</tr>
</table>

Which will give you the following HTML:

<table>
<tr>
    <td><input id="Items_1__radioTrue" name="Items[1].Value" type="radio" value="1" /></td>
    <td><label for="Items_1__radioTrue">true</label></td>
</tr>
<tr>
    <td><input checked="checked" id="Items_1__radioFalse" name="Items[1].Value" type="radio" value="0" /></td>
    <td><label for="Items_1__radioFalse">false</label></td>
</tr>
</table>

Disclaimer

My Razor syntax is underdeveloped, so please let me know if this code has syntax errors.

For what its worth

It's pretty unfortunate that this functionality isn't built-in to RadioButtonFor. It seems logical that all rendered Radio Buttons should have an ID that is a combination of its name AND value, but that's not the case -- maybe because that would be different from all other Html helpers.
Creating your own extension methods for this functionality seems like a logical choice, too. However, it might get tricky using the "expression syntax" ... so I'd recommend overloading .RadioButton(name, value, ...) instead of RadioButtonFor(expression, ...). And you might want an overload for .Label(name, value) too.
I hope that all made sense, because there's a lot of "fill in the blanks" in that paragraph.

Scott Rippey
  • 15,614
  • 5
  • 70
  • 85
  • The compiler may complain about the declaration of anonymous type in the above code `new { for = id }`. This is because `for` is a reserved keyword. In that case just upper-case the attribute name like so `new { For = id }`. Thanks Scott. – Taras Alenin May 14 '12 at 01:03
  • I didn't notice. I typically use syntax like `new { @for = id, @class = "cssClass" }`, but I never thought of using an upper-case property. Did you verify that the output is lower-case? – Scott Rippey May 14 '12 at 18:35
  • 4
    Yes, the name of the generated attribute is lower case, however I have switched to `@for` syntax as it seems to be the convention. Cheers – Taras Alenin May 14 '12 at 23:46
  • You could use the Dictionary overload instead: `new Dictionary() { {"for", id}, {"class", "cssClass"} }` – Christopher Stevenson Aug 01 '13 at 15:46
  • 1
    @ScottRippey How is it possible to just use `new { id }`? How does the engine knows to map that value to the `id` attribute of the label? – silkfire Sep 21 '15 at 12:27
  • Why bother with `GetFullHtmlFieldId`? You can use any ID you want as long as it's unique and you use the same one for both the radio button and the label. E.G. `@Html.RadioButtonFor(i => i.Value, new { id = "rb_val_0" })` and `` Of course you could still assign your ID to a variable to keep it DRY if you wanted. – xr280xr Dec 26 '18 at 19:21
8

@Scott Rippey nearly has it, but i guess he must be using a different version of MVC3 to me because for me @Html.LabelFor has no overloads that will take 3 arguments. I found that using the normal @Html.Label works just fine:

@{string id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioButton_True");}
@Html.Label(id, "True:")
@Html.RadioButtonFor(m => m.radioButton, true, new { id })

@{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioButton_False");}
@Html.Label(id, "False:")
@Html.RadioButtonFor(m => m.radioButton, false, new { id })

this allows you to click on the label and select the associated radiobutton as you'd expect.

Ben
  • 5,525
  • 8
  • 42
  • 66
  • I'll be honest, I didn't test my code whatsoever. But, I did look at [the documentation for `Html.LabelFor(expression, labelText, htmlAttributes)`](http://msdn.microsoft.com/en-us/library/hh833693%28v=vs.108%29.aspx), which looks like it should work just fine. – Scott Rippey Nov 06 '12 at 17:20
  • 1
    @ScottRippey That is for MVC4, since the question was about MVC3, this should be the accepted answer. – Kevin Feb 19 '13 at 13:41
  • Upvoted, this set me on the right course for solving my issues with clicks on the labels not activating the radio buttons in IE6. What was not included in the above answers was the id and for attributes. – LuqJensen Dec 25 '16 at 14:46
2

Here's an HtmlHelper you can use, though you may with to customize it. Only barely tested, so YMMV.

     public static MvcHtmlString RadioButtonWithLabelFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object value, object htmlAttributes = null)
     {
        var name = ExpressionHelper.GetExpressionText(expression);
        var id = helper.ViewData.TemplateInfo.GetFullHtmlFieldId(name + "_" + value);

        var viewData = new ViewDataDictionary(helper.ViewData) {{"id", id}};

        if (htmlAttributes != null)
        {
            var viewDataDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
            foreach (var keyValuePair in viewDataDictionary)
            {
                viewData[keyValuePair.Key] = keyValuePair.Value;
            }
        }

        var radioButton = helper.RadioButtonFor(expression, value, viewData);

        var tagBuilder = new TagBuilder("label");
        tagBuilder.MergeAttribute("for", id);
        tagBuilder.InnerHtml = value.ToString();

        return new MvcHtmlString(radioButton.ToHtmlString() + tagBuilder.ToString());
    }
Geoff
  • 5,283
  • 2
  • 17
  • 11
  • 1
    I split this into 2 helpers, one for the label and one for the radio button. Also, I am altering the label to account for situations where the Inner HTML needs to be different from the value, for example if the value is 2 words. – Louise Eggleton Dec 16 '19 at 21:48
1

Not Perfect but work though,

<table>
<tr>
    <td>@ReplaceName(Html.RadioButtonFor(i => i.Value, "1"))</td>
    <td>@Html.LabelFor(i => i.Value[0], "true")</td>
</tr>
<tr>
    <td>@ReplaceName(Html.RadioButtonFor(i => i.Value, "0"))</td>
    <td>@Html.LabelFor(i => i.Value[1], "false")</td>
</tr>
</table>


@functions {
    int counter = 0;
    MvcHtmlString ReplaceName(MvcHtmlString html){
        return MvcHtmlString.Create(html.ToString().Replace("__Value", "__Value_" + counter++ +"_"));
    }
}
imran_ku07
  • 1,404
  • 12
  • 13
0

You can add text with tags

<td>@Html.RadioButtonFor(i => i.Value, true) <text>True</text></td>
<td>@Html.RadioButtonFor(i => i.Value, false) <text>False</text></td>
Jannik
  • 401
  • 1
  • 5
  • 17
-3

Apparently there is a good solution

    <td>@Html.RadioButtonFor(i => i.Value, true)</td>
    <td>@Html.RadioButtonFor(i => i.Value, false)</td>
Madd0g
  • 3,841
  • 5
  • 37
  • 59
  • 2
    Sorry but that's not a good solution - with that code I will get only the radiobuttons but no labels. I want to have labels where the user can click on to activate the radiobutton. Just the radios isn't user friendly. – Marc Nov 30 '11 at 03:33
  • yeah, I misread your question a bit. Check out the answer [here](http://stackoverflow.com/questions/7098411/how-do-i-use-html-editorfor-to-render-radio-buttons-in-mvc3), it uses a more complex value than bool, which will allow to have working labels – Madd0g Nov 30 '11 at 03:54
  • I don't get it - how should that help me in my case? In the other answer there are different properties used, I only have one property with two different values. Could you update your answer the way you think it should work for me? – Marc Nov 30 '11 at 05:12
  • No no, you're right - I was thinking of my own code and I have a special editorfor that creates ids for an enum, when posting I didn't realize it wasn't automatic. See [here](http://john.katsiotis.com/blog/asp.net-mvc-3---a-radiobuttonlist-for-enum-properties) – Madd0g Nov 30 '11 at 13:12