Friday, October 15, 2010

MVC 2 selct with optgroup support Html Helper

We are current building an Asp.Net MVC 2 application and we had the need for an html helper extention that renders a select with the options grouped by a field. Unfortunately there is no native helper for optgroup html tag support in Asp.Net MVC 2. I have to udmit that this is no common case either so I can not blame the guys in Microsoft for that.

The upside for this is that Asp.Net code is opensource, so I took a sneak peak in the upcomming MVC 3 source code to see what the DropDownListFor code looked like.

Lets cut to the chase.
for all kinds of select html variations MVC uses the SelectListItem class. We just need to subcass it and add a new string property named GroupKey. Here is the code fot that.

public class GroupedSelectListItem : SelectListItem
{
public string GroupKey { get; set; }
}

Then i took the helper overloads for DropDownListFor, renamed them to DropDownGroupListFor, and changed the selectlist parameter type to IEnumerable<GroupedSelectListItem>.

public static MvcHtmlString DropDownGroupListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<GroupedSelectListItem> selectList)

Finaly we just need to change a for loop inside the "GetSelectData" private method in order to take optgroup into consideration. Here is how it looks like.

foreach (var group in selectList.GroupBy(i => i.GroupKey)) {
listItemBuilder.AppendLine(string.Format("<optgroup label=\"{0}\">", group.Key));
foreach (GroupedSelectListItem item in group) {
listItemBuilder.AppendLine(ListItemToOption(item));
}
listItemBuilder.AppendLine("</optgroup>");
}


As requested here is a usage example. Say we have the following list of vehicles in our Model.

.. and the entity class looks like this


Now in our View we add the following statement.

<%: Html.DropDownGroupListFor(model => model.VehicleId,
Lookups.Vehicles.Select(v => new GroupedSelectListItem() {
GroupKey = v.Group,
Text = v.Description,
Value = v.Id.ToString()
}),
"---- select Vehicle ----")%>


Note that Lookups is a static helper class that gets the collection we need to bind to the dropdownlist.

public class Lookups
{
public static ICollection<Vehicle> Vehicles {
get {
ICollection<Vehicle> vehicles = null;
using (DBEntities context = new DBEntities()) {
vehicles = context.Vehicles.ToList();
}
return vehicles;
}
}
}

After that, we use a simple linq selection in order to transform our source collection in the form of List of GroupedSelectListItem. This is the format that the DropDownGroupListFor helper understands

If everything went according to plan then we expect to see something like this in the output:



You can download the code here (Updated: February 2011)

4 comments:

  1. Can you help with a usage example? I am trying:

    <%: Html.DropDownGroupListFor(m => m.SelectedItem, Model.ListOfStuff, new {size = 10}) %>

    and I get "type arguments cannot be inferred from the usage"

    Otherwise, thanks for the great example.

    ReplyDelete
  2. Hi and thanks for the comment. You are absolutely right. I forgot to add a usage example. Check out the blog in a few minutes. I have it updated.

    Note that I also found a minor bug in the attached code. So It will be updated too.

    Cheers!

    ReplyDelete
  3. I'm not sure what bug you found in the code, but I think I found a small one. This is hard, because I can't post html in the comment. The line:

    optgroup label=\"{0}\"", group.Key));

    Shouldn't it be:

    optgroup label=\"{0}\">", group.Key));

    So the optgroup tag gets closed? In my program, the optgroup tag was not getting closed and the first item did not show up.

    Billy C.

    ReplyDelete
  4. @Billy

    Yes that was the minor fix I was talking about. I double ckecked it and as far as I can see I included it in the updated file.

    ReplyDelete

 

Athens

Indice,
13 Kazani str, 11 526 Athens, Greece
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +306945293858 και +306942615932

Copenhagen

Indice,
Asminderødgade 6, 4tv, 2200 Κøbenhavn N,
Denmark
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +4560753926

BERLIN

Indice,
Rubensstrasse 43, 12159 Berlin,
Germany
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +3015155616682