Click here to Skip to main content
15,884,472 members
Articles / All Topics

Radio Buttons for List Items in MVC 4 – Problem with id Uniqueness

Rate me:
Please Sign up or sign in to vote.
4.25/5 (7 votes)
27 Jun 2013CPOL2 min read 106.8K   3   4
If you create radio buttons and labels for items of a list, you may fall victim of a nasty surprise. Rendered HTML markup can contain a serious flaw that will stop your page from working correctly.

Introduction

Let's suppose that we have some model that has a list property and we want to render some radio buttons for items of that list. Take the following basic setup as an example.

Main model class with list:

C#
using System.Collections.Generic;

public class Team
{
    public string Name { get; set; }
    public List<Player> Players { get; set; }
}

List item class:

C#
public class Player
{
    public string Name { get; set; }
    public string Level { get; set; }
}

There are three accepted values for player’s skill Level property: BEG (Beginner), INT (Intermediate) and ADV (Advanced) so we want three radio buttons (with labels) for each player in a team. Yup, normally we would rather use enum instead of a string for Level property, but let’s skip it here for the sake of simplicity…

Controller action method that returns sample data:

C#
public ActionResult Index()
{
    var team = new Team() {
        Name = "Some Team",
        Players = new List<Player> {
               new Player() {Name = "Player A", Level="BEG"},
               new Player() {Name = "Player B", Level="INT"},
               new Player() {Name = "Player C", Level="ADV"}
        }
    };

    return View(team);
}

Here is our Index.cshtml view:

XML
@model Team

<section>
    <h1>@Model.Name</h1>        

    @Html.EditorFor(model => model.Players)            
</section>

Notice that markup for Players is not created inside a loop. Instead, EditorTemplate is used. It’s a good practice since it makes code more clear and maintainable. Framework is smart enough to use code from template for each player on a list, because Team.Players property implements IEnumerable interface...

And here is Players.cshtml EditorTemplate:

XML
@model Player
<div>
    <strong>@Model.Name:</strong>

    @Html.RadioButtonFor(model => model.Level, "BEG")
    @Html.LabelFor(model => model.Level, "Beginner")

    @Html.RadioButtonFor(model => model.Level, "INT")
    @Html.LabelFor(model => model.Level, "Intermediate")

    @Html.RadioButtonFor(model => model.Level, "ADV")
    @Html.LabelFor(model => model.Level, "Advanced")        
</div>

The code looks fine, nice strongly typed helpers that relay on lambda expressions (for compile-time checking and easier refactoring)... but there’s a catch: HTML markup that is generated by such code is actually seriously flawed. Check this snippet of web page source generated for the first player:

XML
<div>
    <strong>Player A:</strong>  
 
    <input name="Players[0].Level" id="Players_0__Level" 
    type="radio" checked="checked" value="BEG">
    <label for="Players_0__Level">Beginner</label>
 
    <input name="Players[0].Level" id="Players_0__Level" type="radio" value="INT">
    <label for="Players_0__Level">Intermediate</label>
 
    <input name="Players[0].Level" id="Players_0__Level" type="radio" value="ADV">
    <label for="Players_0__Level">Advanced</label>        
</div>

Players_0__Level id is used for three different radio buttons! Lack of uniqueness not only violates HTML specification and makes scripting hard but also causes label tags to not work properly (clicking on them doesn’t check their corresponding input element). 

Fortunately MVC framework contains TemplateInfo class that has GetFullHtmlFieldId method. This method returns id for DOM element. That id is constructed by appending name provided as method argument to an automatically determined prefix. This prefix takes into account nesting level and list item's index. Internally, GetFullHtmlFieldId uses TemplateInfo.HtmlFieldPrefix property and TagBuilder.CreateSanitizedId method so even if you pass some illegal characters to id suffix, they will be replaced.

Here is modified EditorTemplate
XML
@model Player
<div>
    <strong>@Model.Name:</strong>

    @{string rbBeginnerId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbBeginner"); }
    @Html.RadioButtonFor(model => model.Level, "BEG", new { id = rbBeginnerId })
    @Html.LabelFor(model => model.Level, "Beginner",  new { @for = rbBeginnerId} )

    @{string rbIntermediateId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbIntermediate"); }
    @Html.RadioButtonFor(model => model.Level, "INT", new { id = rbIntermediateId })
    @Html.LabelFor(model => model.Level, "Intermediate",  new { @for = rbIntermediateId })

    @{string rbAdvancedId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbAdvanced"); }
    @Html.RadioButtonFor(model => model.Level, "ADV", new { id = rbAdvancedId })
    @Html.LabelFor(model => model.Level, "Advanced",  new { @for = rbAdvancedId })
</div>

Calls to ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId method let us obtain ids for radio buttons which are also used to set for attributes of labels. In MVC 3, there was no overload of LabelFor extension method that accepted htmlAttributes object. Luckily, version 4 has it build-in.

The above code produces such markup:

XML
<div>
    <strong>Player A:</strong>
 
    <input name="Players[0].Level" id="Players_0__rbBeginner" 
    type="radio" checked="checked" value="BEG">
    <label for="Players_0__rbBeginner">Beginner</label>
 
    <input name="Players[0].Level" id="Players_0__rbIntermediate" 
    type="radio" value="INT">
    <label for="Players_0__rbIntermediate">Intermediate</label>
 
    <input name="Players[0].Level" id="Players_0__rbAdvanced" 
    type="radio" value="ADV">
    <label for="Players_0__rbAdvanced">Advanced</label>
</div>

Now inputs ids are unique and labels properly reference radio buttons via for attribute. Alright :) 

BTW: The weird name “radio buttons” for mutually exclusive option elements comes from buttons on radio receivers that were used to switch between stations (pushing one in automatically pushed the others out).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland

Comments and Discussions

 
QuestionNeed this, but where the list of RadioButtons is data-driven, and thus rendered in a for loop Pin
Scott Fraley8-Feb-17 11:32
Scott Fraley8-Feb-17 11:32 
This is SO CLOSE to what I was looking for, but given my requirement for a 'data-driven' list, I don't think doing the IDs like you do here would work.
--
Scott K. Fraley
http://www.linkedin.com/in/scottkfraley

Any comments or opinions are mine and mine alone. Smile | :)

QuestionRadio Buttons Pin
johnmcpherson1013-Sep-13 6:29
johnmcpherson1013-Sep-13 6:29 
AnswerRe: Radio Buttons Pin
morzel16-Sep-13 5:01
morzel16-Sep-13 5:01 
GeneralMy vote of 5 Pin
Wieselquist25-Jun-13 1:37
Wieselquist25-Jun-13 1:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.