ASP.NET DropdownList with <optgroup>

If you are looking for an easy and simplest solution to create groups in the ASP.NET DropdownList control without writing unnecessary amount of backend code, then you are at the right place. Let me show you how we can do this using JQuery 1.2.6.

I have divided the solution into two different parts. First part explains achieving the desired output using JQuery's native support for wrapping elements into other elements with the help of wrapAll() method. It takes only 1 line of code and we can have the groups created in the HTML. However, the drawback of this approach is the groups will disappear on postback. But if the DropdownList is small and we are not loading it from the database, then this approach is very suitable as it will save a lot of time and effort.

Ok, enough of the riff-raff and get down to business now.

Part 01 (static binding of list items with no support for postback)

Step 01: Creating a DropdownList

ListItemCollection list = new ListItemCollection();

list.Add(new ListItem("1", "1"));
list.Add(new ListItem("2", "2"));
list.Add(new ListItem("3", "3"));
list.Add(new ListItem("4", "4"));
list.Add(new ListItem("5", "5"));
list.Add(new ListItem("6", "6"));
list.Add(new ListItem("7", "7"));
list.Add(new ListItem("8", "8"));
list.Add(new ListItem("9", "9"));
list.Add(new ListItem("10", "10"));

ddl.DataSource = list;
ddl.DataBind();

Step 02: Creating a new attribute
We have to create a new attribute for every DropdownList item. This new attribute will be used for grouping in the JQuery code.

protected void ddl_DataBound(object sender, EventArgs e)
{
foreach (ListItem item in ((DropDownList)sender).Items)
{
if (System.Int32.Parse(item.Value) < 5)
item.Attributes.Add("classification", "LessThanFive");
else
item.Attributes.Add("classification", "GreaterThanFive");
}

}

Step 03: The fun part - Creating groups using JQuery WrapAll() method

<script>
$(document).ready(function() {
//Create groups for dropdown list
$("select.listsmall option[@classification='LessThanFive']").wrapAll("<optgroup label='Less than five'>");
$("select.listsmall option[@classification='GreaterThanFive']").wrapAll("<optgroup label='Greater than five'>");
});

</script>


Part 02 (dynamic binding of list items with full support for postback)

This approach not only creates groups using JQuery 1.2.6 but also allows DropdownList to remember these groups across postbacks. We will have to create a custom server control using DropdownList class and override SaveViewState() and LoadViewState() methods that will help the page ViewState remember and restore groups respectively.

As mentioned earlier, in this approach we have to create a custom server control with a bit of a backend code and override some of it's important methods. However, if you are one of those lazy developers like me who don't like the idea of creating controls specially custom server controls that reside in a separate assembly for a very tiny job such as the one in hand, then you can create control that is part of the same assembly by adding a new class file and deriving it's class from System.Web.UI.WebControls.DropDownList.


namespace ControlLibrary
{
public class DropdownList : System.Web.UI.WebControls.DropDownList
{
protected override object SaveViewState()
{
// Create an object array with one element for the CheckBoxList's
// ViewState contents, and one element for each ListItem in skmCheckBoxList
object[] state = new object[this.Items.Count + 1];

object baseState = base.SaveViewState();
state[0] = baseState;

// Now, see if we even need to save the view state
bool itemHasAttributes = false;
for (int i = 0; i < this.Items.Count; i++)
{
if (this.Items[i].Attributes.Count > 0)
{
itemHasAttributes = true;

// Create an array of the item's Attribute's keys and values
object[] attribKV = new object[this.Items[i].Attributes.Count * 2];
int k = 0;
foreach (string key in this.Items[i].Attributes.Keys)
{
attribKV[k++] = key;
attribKV[k++] = this.Items[i].Attributes[key];
}

state[i + 1] = attribKV;
}
}

// return either baseState or state, depending on whether or not
// any ListItems had attributes
if (itemHasAttributes)
return state;
else
return baseState;

}

protected override void LoadViewState(object savedState)
{
if (savedState == null) return;

// see if savedState is an object or object array
if (savedState is object[])
{
// we have an array of items with attributes
object [] state = (object[]) savedState;
base.LoadViewState(state[0]); // load the base state

for (int i = 1; i < state.Length; i++)
{
if (state[i] != null)
{
// Load back in the attributes
object [] attribKV = (object[]) state[i];
for (int k = 0; k < attribKV.Length; k += 2)
this.Items[i-1].Attributes.Add(attribKV[k].ToString(),
attribKV[k+1].ToString());
}
}
}
else
// we have just the base state
base.LoadViewState(savedState);
}
}
}

If you search on the internet for a solution to create <optgroup> in ASP.NET DropdownList, you will find some of the articles talking about overriding RenderContents() method of DropdownList to create . However, I personally feel that this job can also be done with less of a hassle using JQuery WrapAll() method that wraps elements inside other elements. All we need is to have a mechanism to have these groups saved upon postbacks which we are doing here by overriding SaveVeiwState() and LoadViewState() as Scott Mitchell describes in his article. Apart from these 2 methods, we need to populate the DropdownList in Page_Load method and create a new attribute in the ddl_DataBound() event as explained in the part 01 above. That is all required to create <optgroup>s in an ASP.NET DropdownList. You can see the output control populated with ListItems which are properly encapsulated within optiongroups below.

The source code for this approach can be downloaded from here.

Output:




HTH,

6 comments:

Anonymous said...

Thanks Irfan. That helped a lot. For the latest jquery, others can use this example:

$("option[classification='lessthanfive']").wrapAll("<optgroup label='less than five' />");

irfan said...

@Anonymous: Great, Thanks.

FreshCode said...

No demo link?

Sven Vranckx said...

I'm encoding the groups in option values, that way getting rid of the need to override the view state:

$('select option[value|="G1"]').wrapAll(...);
$('select option[value|="G2"]').wrapAll(...);

The options have values like 'G1-Option1', 'G1-Options2', 'G2-Options3', etc

irfan said...

@Sven Vranckx, I don't think it would be a good idea to modify options values specially they are being passed to a another page or being used on the client side for comparison. We will have to remember to remove the group information (G1, G2 etc.). Changing the option values might cause more harm than good, I believe.

Thanks.

Anonymous said...

Thanks

Post a Comment