Adding Images to Select Lists in MVC3
Preface
At my last company, I was working on moving a legacy ASP.NET WebForms application to MVC3. During the migration process, we had to implement some features in our legacy ASP.NET WebForms app (y’know, to stay competitive and stuff). Our WebForms guy did some fairly slick UI work – he leveraged the Telerik controls we purchased to create some dropdown lists that had icons next to each item in the list.
During one of our standups, my manager asked “Jeff, you’re working in an area related to [feature x], right?”
“Yeah…” I replied sheepishly, knowing what was coming next.
“Do you think you could make the dropdown list on your MVC screen look like the existing WebForms screen?”
Without really thinking, I replied with my de facto silly answer: “I’m a programmer – I can do anything! I’ll look into it and see what I can find.”
Before continuing any further, I’d like to throw out a word of caution: “I can do anything” is the worst possible answer a developer can give. Period. I’d even go so far as to say that “I can do anything” is the worst possible combination and sequence of words in the English language. Your manager/product manager/in-charge person will most likely take you seriously, regardless of your tone. If you find that infernal phrase on the tip of your tongue, take a deep breath and count to 10. Then say something else. Please learn from my mistake and consider yourself warned.
But I digress.
So, off to the Google-net I went.
Requirements
I headed back to my desk, my brain already churning on the issue and how to solve it. Here’s what we want to do:
- For each item in the select list, we want to display an icon/image that is associated with the item.
- The icon file names will be stored in the database for each item in the select list.
- The solution should be easy to use – it shouldn’t take 8 or 9 steps for each select list that needs to have images.
- It’d be neat if our solution would just extend the existing Html.DropdownList() and Html.DropdownListFor() helpers. That would be neat indeed…
For those following along, the database table might look something like this:
Discovery & Analysis
As I mentioned before, we had licenses for the Telerik ASP.NET and MVC controls, so that’s where I went first. Unfortunately, the MVC combobox control didn’t have an easy way to introduce images into a select list. Besides, I’d prefer not to have to rely on a third-party control* if I can help it.
At my last company, “jQuery to the rescue!” was one of my battle cries. That cry seems to apply here, so I went with it. I searched Google for a jQuery plugin to fit the bill. True to Google form, I got quite a number of hits. To pick one, I threw a Nerf dart at my monitor and went with whatever I hit**. The dart landed squarely on JavaScript image combobox v2.38, so that’s the tool I went with.
Overall, the JavaScript image combobox works on a very simple principle: add a title attribute to the option element. The title attribute will describe the full path to the image you want to display. Include a couple of .js and .css files, and presto! Images in dropdown lists! Hooray – my victory is assured!
* = Well, a third-party control that costs money.
** = Okay, maybe that isn’t exactly how it went. Maybe I read through a couple of them and picked the one that I thought would be easiest to implement.
Challenges
Now all I have to do is add the path of the image file I want to show for each option. Easy, right? Yeah – sure thing – it’s a trivial exercise to fetch the image file names from the database and wire up the results to a collection of SelectListItems (or whatever you’re displaying in your select lists). Then again, maybe not. Here’s the problem now:
How do I tell MVC to add the title attribute to the option element when it’s rendering the screen?
When MVC3 renders the select list, each item in the select list is rendered as an option element. That’s fine – that’s exactly what we want. However, there’s no mechanism to add additional attributes to the option elements.
I could use jQuery to do some processing on the page to add the title attribute after the page loads. But I’d still have to set the value of the title attribute to point to the right image file. Even if I could set the title attribute appropriately, this still seems like a bit of a hustle – other developers would have to remember when/where to add the jQuery script/code that would handle this.
In the immortal words of my mentor, life-coach and hero (hare-o? Sorry – couldn’t resist) Bugs Bunny: How now, brown cow?
Solution
Yes indeed, it seemed like I was in a bit of a lurch. After thinking about the following statement for a few minutes, it turns out that the following statement isn’t quite true:
there’s no mechanism to add additional attributes to the option elements.
The statement should be revised to read like so:
there’s no out of the box mechanism to add additional attributes to the option elements.
With a bit more Google searching – which I freely admit took considerably longer than finding the jQuery plugin I liked – I found this article. This clever gentleman is trying to get optgroup elements to display in his select lists, but the principle is the same – he’s modifying the contents of his select list, and he’s telling MVC3 how to do it.
How It Works
At the heart of the solution to this problem are two classes:
- ListItemExtensions: This class contains additional DropDownList() and DropDownListFor() helper methods. These methods accept an imagePath parameter, which will be used as the value for our title attribute. Also, these methods accept an IEnumerable instead of the normal SelectListItem.
- ImageSelectListItem:This class extends the existing SelectListItem, adding an ImageFileName property. The ImageFileName will be…. Well… it’ll be the name of the icon/image we want to display.
- NOTE: there isn’t a strong technical reason why I chose to extend the SelectListItem instead of creating my own ImageSelectListItem.
Both of the classes cited above are added to the System.Web.Mvc.Html namespace. As far as .NET is concerned, these classes are part of the MVC family. That means we won’t need any additional using statements in our views. Yay!
NOTE: If you want to move these classes to another namespace, go right ahead. Just remember to import the namespace you choose and you should be all set.
LISTITEMEXTENSIONS
For the most part, the ListItemExtensions class is a series of overloads that are used to build the select element and its options. Here’s what happens:
- The DropDownList or DropDownListFor method you call ultimately drills down into the DropDownListHelper method.
- The DropDownListHelper method calls into SelectInternal, which is used to actually produce the MvcHtmlString that represents the HTML of the select list.
- To solve our problem of adding the title attribute with the path to our desired image file, this is the interesting part of the SelectInternal method:
// Convert each ListItem to an <option> tag
StringBuilder listItemBuilder = new StringBuilder();
if (defaultValue != null)
{
IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
List<ImageSelectListItem> newSelectList = new List<ImageSelectListItem>();
foreach (ImageSelectListItem item in selectList)
{
item.Selected = (item.Value != null) ?
selectedValues.Contains(item.Value) :
selectedValues.Contains(item.Text);
newSelectList.Add(item);
}
}
// Make optionLabel the first item that gets rendered.
if (optionLabel != null)
{
listItemBuilder.AppendLine(ListItemToOption(new ImageSelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
}
Notice the ListnewSelectList… – normally, MVC3 uses the Listthat we all know and love. Because we’re using our ImageSelectListItem, the name of our icon file will be carried on for use when the option element is actually created.
Also Notice the last line. In particular, notice the call to ListItemToOption where we’re passing in the imagePath parameter. This is where the option element is actually built, complete with our title attribute:
internal static string ListItemToOption(ImageSelectListItem item, string imagePath = "")
{
TagBuilder builder = new TagBuilder("option")
{
InnerHtml = HttpUtility.HtmlEncode(item.Text)
};
if (item.Value != null)
{
builder.Attributes["value"] = item.Value;
}
if (!string.IsNullOrEmpty(imagePath))
{
builder.Attributes["title"] = BuildFullImagePath(imagePath, item.ImageFileName);
}
if (item.Selected)
{
builder.Attributes["selected"] = "selected";
}
return builder.ToString(TagRenderMode.Normal);
}
The code for the ListItemToOption method is pretty straightforward – we’re building an option tag and setting its attributes. In addition to the value and selected attributes, we’re also setting the title attribute. The BuildFullImagePath is a helper method that looks like this:
private static string BuildFullImagePath(string path, string fileName)
{
if (string.IsNullOrEmpty(path))
{
return fileName;
}
path = path.EndsWith("/") ? path : string.Format("{0}/", path);
return string.Format("{0}{1}", path, fileName);
}
If we happen to get an empty or null value for path, we’re making the assumption that the entire path to the image is contained in the ImageFileName property of the ImageSelectListItem (e.g. ImageSelectListItem.ImageFileName = “~/content/images/myicon.png” or ImageSelectListItem.ImageFileName = “../../content/images/myicon.png”). Otherwise, we will concatenate the path and fileName parameters together, adding a “/” to the end of the path if we need to.
CODE SMELL ALERT!
Sorry – I need to stop for a second. The BuildFullImagePath method reeks of smelly code, but it gets the job done. Unfortunately, I couldn’t find another way to correctly build the path to the image (Neither Path.Combine nor VirtualPathUtility.Combine seemed to do the trick). If anyone has any suggestions on refactoring to get rid of the BuildFullImagePath method, I’m certainly willing to listen.
IMAGESELECTLISTITEM
The ImageSelectListItem class doesn’t do anything very interesting, but I’ll show it for the sake of completeness:
namespace System.Web.Mvc.Html
{
public class ImageSelectListItem : SelectListItem
{
public string ImageFileName { get; set; }
}
}
At this point we have:
- A class that will transfer the name of our image file from our server to the UI.
- If we want, this class can tranfer the entire path of the image file.
- A class that will build our option elements the way we want – with a title attribute pointing to our desired image.
- This class will take in the path to where our images are stored and concatenate that path with the name of the image file we want to display.
- Both of these classes are in the System.Web.Mvc.Html namespace.
- The extension methods from our classes will now show up in intellisense whenever a developer types @Html.DropDownList() or @Html.DropDownListFor().
- A jQuery plugin that will render our images next to each corresponding item in the select list.
Putting It All Together
We’re on the home stretch, now everyone! If you’ve hung in with me this long, then you’re to be commended.
As a reward for your patience, you can find a demo using the classes cited earlier on GitHub here.
Here’s what the Create.cshtml view from the demo looks like with the image path included (the value for imagePath is highlighted in red):
@using MVC3.ImagesInDropdownLists.ViewModels;
@model CreateContestantViewModel
@{
ViewBag.Title = "Contest Registration";
}
<h2>Register For the Big, Super-Secret Event!</h2>
@using (Html.BeginForm("Create", "Contestant", FormMethod.Post))
{
<div class="editor-label">@Html.LabelFor(m => m.FirstName)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.FirstName)</div>
<div>@Html.ValidationMessageFor(m => m.FirstName)</div>
<div class="editor-label">@Html.LabelFor(m => m.LastName)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.LastName)</div>
<div>@Html.ValidationMessageFor(m => m.LastName)</div>
<div class="editor-label">@Html.LabelFor(m => m.Age)</div>
<div class="editor-field">@Html.TextBoxFor(m => m.Age)</div>
<div>@Html.ValidationMessageFor(m => m.Age)</div>
<div class="editor-label">@Html.LabelFor(m => m.CountryId)</div>
<div class="editor-field">@Html.DropDownListFor(m => m.CountryId, "../../Content/Images/", Model.AllCountries, new { id = "selectCountry" })</div>
<input id="save" type="submit" value="Submit"/>
}
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/msdropdown/jquery.dd.js")"></script>
<script type="text/javascript">
$(function () {
$('#selectCountry').msDropDown();
})
</script>
Yessir, the @Html.DropDownListFor method includes an imagePath parameter. We’ve pointed it at ../../Content/Images, which is where the icons for the demo are stored. No special using statements, no clunky code, no nothin’. Just the same old razor syntax we’re used to seeing with a slightly different method call. As an added bonus, our databinding works! If you put a breakpoint on the POST version of the Create method, you’ll see that whatever country you chose has been transmitted to the server. That means all of our fancy-pants coding in combination with the jQuery plugin didn’t break anything.
At the bottom of the view are the script elements necessary to make the jQuery image combobox plugin work. You’ll notice the last script sets up the image combobox by calling .msDropDown() on our select list. There are a host of other options/configuration for the plugin. If you’re interested in the other capabilities of the jQuery image combobox (of which there are quite a few), I’d recommend heading to the author’s site and check it out.
Here’s a screenshot of what it looks like in action:
Potential Pitfalls
As with all things, there are a couple of gotchas to watch out for:
- Be sure to include the jquery.dd.js file on the Views that are going to have select lists with images.
- The jQuery image combobox plugin comes with quite a few files. The following are absolutely necessary:
- Jquery.dd.js
- Dd.css
- Dd_arrow.gif
- Actually, this file isn’t strictly necessary. This is the arrow image used by the jQuery image combobox to draw the dropdown arrow. If you want to use your own, go ahead – just update dd.css to reference your image instead.
- Be sure to add the ListItemExtensions and ImageSelectListItemclasses to your project.
- In the demo, I have added these classes in the Extensions folder.
- If you’re using ReSharper, it might complain that the namespace doesn’t match the folder. As cool as ReSharper is, sometimes it needs to know its place.
- Having said that, if you want to change the namespace for these classes, be my guest.
Summary
So that about does it.
With a few keystrokes and a helpful, easy-to-implement jQuery plugin, we’ve implemented some helper methods that will allow us to easily display images in a select list of an MVC3 application. I encourage you to read the article and check out the plugin cited in the References section.
References
Here are the articles and tools that I found incredibly helpful in getting this accomplished:
- Extending the dropdownlist to show the items grouped by a category.
- The jQuery image combobox plugin
- Source code for the plugin is on GitHub here.
- An MVC demo of this solution can be found on GitHub here.
- If you’re looking for some light reading, you can find the MVC3 source code here.



Form For Web / April 16, 2013
Awesome things here. I’m very happy to see your post. Thanks so much and I’m looking ahead to contact you. Will you please drop me a mail?