Live Online SharePoint Saturday - EMEA (Free)

To all ya SharePoint ppl. based in EMEA region:

Live Online SharePoint Saturday - EMEA

HTH,

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,

Watchout for getElementById bug

Being a web developer targeting IE 7, We should be very careful with the way getElementById method behaves in IE 7. According to getElementById explanation on MSDN:

this method performs a case-insensitive match on both the ID and NAME attributes, which might produce unexpected results

We may NOT see any problem with this method as long as the ID and Name attributes of the controls on the webpage are same (which is the case most of the time). But, when values of these attributes are different e.g. while using a Master Page which mangles ID attributes by appending "_" and Name attribute using "$", getElementById in IE 7.0 might produce unexpected results. Read more about this bug here.

HTH,