Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

ASP.NET DropDownList With Groups

A long time ago I submitted a request to the ASP.NET team for having the standard DropDownList support HTML’s optgroup tag: http://aspnet.codeplex.com/workitem/10318. For those of you not familiar with this tag – that has been around for quite some time, by the way –, it allows for something like this:

In a nutshell, we can have grouped list items. The issue is still open, but, since it is such a handy feature, and one that I need quite often, I decided to implement support for it on top of the existing DropDownList. Again, my solution uses tag mapping, which you should know from my previous posts, so I can add support for it on all of the already declared DropDownLists.

I wanted to support the two basic scenarios:

  1. Adding ListItem entries on markup or by code;
  2. Having the ListItem entries created dynamically through data binding.

I started by implementing a control that inherits from DropDownList:

   1: public class GroupedDropDownList : DropDownList
   2: {
   3:     public String DataOptionGroupField
   4:     {
   5:         get;
   6:         set;
   7:     }
   8:  
   9:     protected override void PerformDataBinding(IEnumerable dataSource)
  10:     {
  11:         base.PerformDataBinding(dataSource);
  12:  
  13:         if ((String.IsNullOrWhiteSpace(this.DataOptionGroupField) == false) && (dataSource != null))
  14:         {
  15:             ListItemCollection items = this.Items;
  16:             IEnumerable<Object> data = dataSource.OfType<Object>();
  17:             Int32 count = data.Count();
  18:  
  19:             for (Int32 i = 0; i < count; ++i)
  20:             {
  21:                 String group = DataBinder.Eval(data.ElementAt(i), this.DataOptionGroupField) as String ?? String.Empty;
  22:  
  23:                 if (String.IsNullOrWhiteSpace(group) == false)
  24:                 {
  25:                     items[i].Attributes["Group"] = group;
  26:                 }
  27:             }
  28:         }
  29:     }
  30:  
  31:     protected override void RenderContents(HtmlTextWriter writer)
  32:     {
  33:         ListItemCollection items = this.Items;
  34:         Int32 count = items.Count;
  35:         var groupedItems = items.OfType<ListItem>().GroupBy(x => x.Attributes["Group"] ?? String.Empty).Select(x => new { Group = x.Key, Items = x.ToList() });
  36:  
  37:         if (count > 0)
  38:         {
  39:             Boolean flag = false;
  40:  
  41:             foreach (var groupedItem in groupedItems)
  42:             {
  43:                 if (String.IsNullOrWhiteSpace(groupedItem.Group) == false)
  44:                 {
  45:                     writer.WriteBeginTag("optgroup");
  46:                     writer.WriteAttribute("label", groupedItem.Group);
  47:                     writer.Write('>');
  48:                 }
  49:  
  50:                 for (Int32 i = 0; i < groupedItem.Items.Count; ++i)
  51:                 {
  52:                     ListItem item = groupedItem.Items[i];
  53:  
  54:                     if (item.Enabled == true)
  55:                     {
  56:                         writer.WriteBeginTag("option");
  57:  
  58:                         if (item.Selected == true)
  59:                         {
  60:                             if (flag == true)
  61:                             {
  62:                                 this.VerifyMultiSelect();
  63:                             }
  64:  
  65:                             flag = true;
  66:  
  67:                             writer.WriteAttribute("selected", "selected");
  68:                         }
  69:  
  70:                         writer.WriteAttribute("value", item.Value, true);
  71:  
  72:                         if (item.Attributes.Count != 0)
  73:                         {
  74:                             item.Attributes.Render(writer);
  75:                         }
  76:  
  77:                         if (this.Page != null)
  78:                         {
  79:                             this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
  80:                         }
  81:  
  82:                         writer.Write('>');
  83:                         HttpUtility.HtmlEncode(item.Text, writer);
  84:                         writer.WriteEndTag("option");
  85:                         writer.WriteLine();
  86:                     }
  87:                 }
  88:  
  89:                 if (String.IsNullOrWhiteSpace(groupedItem.Group) == false)
  90:                 {
  91:                     writer.WriteEndTag("optgroup");
  92:                 }
  93:             }
  94:         }
  95:     }
  96: }

In case you are wondering, the implementation of RenderContents comes from the standard ListControl, here: http://msdn.microsoft.com/en-us/library/4c9zdz95.aspx. As you can see, I overrode two methods and added an extra property:

  • RenderContents is the method responsible for producing the HTML for each of the ListItem items in the Items collection; I modified it so as to output an optgroup declaration for each of the ListItems that contain references to a group, keeping the order;
  • PerformDataBinding is where the data binding process actually populates the  Items collection; I changed it so as to include a Group attribute, in case the DataOptionGroupField property is set.

The new DataOptionGroupField property, similar to DataTextField and DataValueField, if specified, will contain the name of the property in the data source that holds each item’s group name, when using data binding. If not specified, no grouping will occur. Do note that this only applies to data binding, not manually added ListItems.

Now, for using this control, we can either use it explicitly:

   1: <My:GroupedDropDownList runat="server">
   2:     <asp:ListItem Group="Web" Text="CSS"/>
   3:     <asp:ListItem Group="Web" Text="HTML"/>
   4:     <asp:ListItem Group="Web" Text="JavaScript"/>
   5:     <asp:ListItem Group="Windows" Text="WPF"/>
   6:     <asp:ListItem Group="Windows" Text="Windows Forms"/>
   7: </My:GroupedDropDownList>

Or use a tag mapping:

   1: <tagMapping>
   2:     <add tagType="System.Web.UI.WebControls.DropDownList" mappedTagType="MyNamespace.GroupedDropDownList, MyAssembly"/>
   3: </tagMapping>

In which case, you keep the standard DropDownList declaration but add the Group attribute to the ListItems, which works because ListItem implements IAttributeAccessor:

   1: <asp:DropDownList runat="server">
   2:     <asp:ListItem Group="Web" Text="CSS"/>
   3:     <asp:ListItem Group="Web" Text="HTML"/>
   4:     <asp:ListItem Group="Web" Text="JavaScript"/>
   5:     <asp:ListItem Group="Windows" Text="WPF"/>
   6:     <asp:ListItem Group="Windows" Text="Windows Forms"/>
   7: </asp:DropDownList>

And if you prefer to use data binding, you add an extra attribute for the DataGroupField property (DropDownList also implements IAttributeAccessor):

   1: <asp:DropDownList runat="server" ID="list" DataTextField="Name" DataValueField="Id" DataGroupField="Type"/>

And on code behind (or a data source control):

   1: protected void OnLoad(EventArgs e)
   2: {
   3:     var items = new List<Object>();
   4:     items.Add(new { Text = "CSS", Group = "Web" });
   5:     items.Add(new { Text = "HTML", Group = "Web" });
   6:     items.Add(new { Text = "JavaScript", Group = "Web" });
   7:     items.Add(new { Text = "WPF", Group = "Windows" });
   8:     items.Add(new { Text = "Windows Forms", Group = "Windows" });
   9:  
  10:     this.list.DataSource = items;
  11:     this.list.DataBind();
  12:  
  13:     base.OnLoad(e);
  14: }

And you will get all items nicely grouped!

As you can see, tag mapping is very powerful, and, if used with controls that support extensibility, can be very useful. Hope this helps!

Comments

Ben from Complete IT Professional said:

Thanks for the tips! I'll have to try this out myself, I'm sure it'll be very useful.

# February 11, 2013 5:56 AM

krillehbg said:

Hi,

Thanks for this solution, I was just what I was looking for. Can you please provide me with the source code?

Please send it to my mail.

Thanks!!

Regards Kristian

# May 22, 2013 7:31 AM

krillehbg said:

Hi again, I have tried your solution and it works fine. The only problem is to get it to work with the databound DataGroupField property. I won't add any groups when using that one.

Am I doing anything wrong?

Thanks for helping me!

Regards Kristian

# May 22, 2013 3:43 PM

krillehbg said:

Hi again,

I found out that the problem was a misspelling in your code. Should be DataOptionGroupField instead of DataGroupField

Thanks!

# May 22, 2013 4:03 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)