Bind Dropdownlist with optGroup from sql datasource - asp.net

I have to bind a Dropdownlist with coutries which should be grouped by region, I have found a sample code from the following link,
http://www.codeproject.com/KB/custom-controls/DropDownListOptionGroup.aspx?msg=3984074#xx3984074xx
I wanted the country list same as this. But problem is that I want to bind the dropdownlist from sql result. I have tried the following but didn't work,
ddlCountry.DataSource = CountryDtoCollection;
ddlCountry.DataBind();
ddlCountry.Attributes.Add("OptionGroup", "Region");
any one knows any solution for this.

you can write a custom server control and use the data source witch containing the text and region separated by | then split it when use.
[ToolboxData("<{0}:CustomDropDownList runat=server></{0}:CustomDropDownList>")]
public class CustomDropDownList : DropDownList
{
protected override void RenderContents(HtmlTextWriter writer)
{
if (this.Items.Count > 0)
{
bool selected = false;
bool optGroupStarted = false;
string lastOptionGroup = string.Empty;
for (int i = 0; i < this.Items.Count; i++)
{
ListItem item = this.Items[i];
if (item.Enabled)
{
if (lastOptionGroup != item.Text.Split("|")[1])
{
if (optGroupStarted)
{
writer.WriteEndTag("optgroup");
}
lastOptionGroup = item.Text.Split("|")[1];
writer.WriteBeginTag("optgroup");
writer.WriteAttribute("label", lastOptionGroup);
writer.Write('>');
writer.WriteLine();
optGroupStarted = true;
}
writer.WriteBeginTag("option");
if (item.Selected)
{
if (selected)
{
this.VerifyMultiSelect();
}
selected = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.Attributes.Count > 0)
{
item.Attributes.Render(writer);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text.Split("|")[0], writer);
writer.WriteEndTag("option");
writer.WriteLine();
}
}
if (optGroupStarted)
{
writer.WriteEndTag("optgroup");
}
}
}
}

Related

How to Sort the Gridview Based Session values

I have a gridview with hyperlink if i click on hyperlink the values going to button of the gridview.
protected void gvDetails_RowDataBound(object sender, GridViewRowEventArgs e)
{
try
{
if (e.Row.DataItem != null)
{
// DataTable readDt = null;
List<String> rows = null;
//Fetching the List from Session Object
if (Session["ReadJobDetails"] != null)
{
rows = (List<String>)Session["ReadJobDetails"];
}
if (rows.Contains(DataBinder.Eval(e.Row.DataItem, "JobTitle").ToString()))
{
//since we used GridView level CSS class, individual CSS class is not applying, so added manually
//e.Row.BackColor = System.Drawing.Color.FromArgb(208, 217, 206);
e.Row.BackColor = System.Drawing.Color.Silver;
e.Row.ForeColor = System.Drawing.Color.White;
e.Row.Font.Bold = false;
}
else
{
e.Row.BackColor = System.Drawing.Color.FromArgb(247, 247, 247);
e.Row.ForeColor = System.Drawing.Color.Black;
e.Row.Font.Size = 9;
e.Row.Font.Bold = true;
}
}
}
catch (Exception)
{
throw;
}
}

ListItem.Selected returning true

I have a CheckBoxList and I am looping through all of it's ListItems's and checking if any one of them are checked.
The strange thing is that although only one of them is checked, it thinks ALL of them are checked.
C# Code:
foreach (ListItem item in cblPermissions.Items)
{
if (item.Selected)
{
// Even though it's not checked on the HTML page - This code still runs.
}
}
This is how I bind the data:
private void BindPermissions()
{
var roles =
RoleInfoProvider.GetAllRoles(
string.Format("RoleName like 'EmployerAdmin[_]%' AND SiteID = {0} AND RoleName NOT LIKE '%Super_User%'",
CMSContext.CurrentSiteID));
cblPermissions.DataSource = roles;
cblPermissions.DataBind();
var userInfo = UserInfoProvider.GetUserInfoByGUID(QueryHelper.GetGuid("guid", Guid.Empty));
if (userInfo != null)
{
var isSuperUser = userInfo.IsInRole(Constants.EmployerAdminSuperUserRole, CMSContext.CurrentSiteName);
if (!isSuperUser)
{
var userRoles = UserInfoProvider.GetUserRoles(userInfo);
foreach (DataRow row in userRoles.Rows)
{
var roleName = row["RoleName"].ToString();
var roleItem = cblPermissions.Items.FindByValue(roleName);
if (roleItem != null)
{
roleItem.Selected = true;
}
}
}
else
{
MarkAllPermissionsAsChecked();
}
}
}
HTML:
<asp:CheckBoxList runat="server" ID="cblPermissions" DataValueField="RoleName" DataTextField="RoleDescription"/>
Any ideas why this is happening?

Saving State Dynamic UserControls...Help!

I have page with a LinkButton on it that when clicked, I'd like to add a Usercontrol to the page. I need to be able to add/remove as many controls as the user would like. The Usercontrol consists of three dropdownlists. The first dropdownlist has it's auotpostback property set to true and hooks up the OnSelectedIndexChanged event that when fired will load the remaining two dropdownlists with the appropriate values.
My problem is that no matter where I put the code in the host page, the usercontrol is not being loaded properly. I know I have to recreate the usercontrols on every postback and I've created a method that is being executed in the hosting pages OnPreInit method. I'm still getting the following error:
The control collection cannot be modified during DataBind, Init, Load, PreRender or Unload phases.
Here is my code:
Thank you!!!!
bool createAgain = false;
IList<FilterOptionsCollectionView> OptionControls
{
get
{
if (SessionManager.Current["controls"] != null)
return (IList<FilterOptionsCollectionView>)SessionManager.Current["controls"];
else
SessionManager.Current["controls"] = new List<FilterOptionsCollectionView>();
return (IList<FilterOptionsCollectionView>)SessionManager.Current["controls"];
}
set
{
SessionManager.Current["controls"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
Master.Page.Title = Title;
LoadViewControls(Master.MainContent, Master.SideBar, Master.ToolBarContainer);
}
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
System.Web.UI.MasterPage m = Master;
Control control = GetPostBackControl(this);
if ((control != null && control.ClientID ==
(lbAddAndCondtion.ClientID) || createAgain))
{
createAgain = true;
CreateUserControl(control.ID);
}
}
protected void AddAndConditionClicked(object o, EventArgs e)
{
var control = LoadControl("~/Views/FilterOptionsCollectionView.ascx");
OptionControls.Add((FilterOptionsCollectionView)control);
control.ID = "options" + OptionControls.Count.ToString();
phConditions.Controls.Add(control);
}
public event EventHandler<Insight.Presenters.PageViewArg> OnLoadData;
private Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
protected Control GetPostBackControl(System.Web.UI.Page page)
{
Control control = null;
string ctrlname = Page.Request.Params["__EVENTTARGET"];
if (ctrlname != null && ctrlname != String.Empty)
{
control = FindControlRecursive(page, ctrlname.Split('$')[2]);
}
else
{
string ctrlStr = String.Empty;
Control c = null;
foreach (string ctl in Page.Request.Form)
{
if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
{
ctrlStr = ctl.Substring(0, ctl.Length - 2);
c = page.FindControl(ctrlStr);
}
else
{
c = page.FindControl(ctl);
}
if (c is System.Web.UI.WebControls.CheckBox ||
c is System.Web.UI.WebControls.CheckBoxList)
{
control = c;
break;
}
}
}
return control;
}
protected void CreateUserControl(string controlID)
{
try
{
if (createAgain && phConditions != null)
{
if (OptionControls.Count > 0)
{
phConditions.Controls.Clear();
foreach (var c in OptionControls)
{
phConditions.Controls.Add(c);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
Here is the usercontrol's code:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="FilterOptionsCollectionView.ascx.cs" Inherits="Insight.Website.Views.FilterOptionsCollectionView" %>
namespace Insight.Website.Views
{
[ViewStateModeById]
public partial class FilterOptionsCollectionView : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnInit(EventArgs e)
{
LoadColumns();
ddlColumns.SelectedIndexChanged += new RadComboBoxSelectedIndexChangedEventHandler(ColumnsSelectedIndexChanged);
base.OnInit(e);
}
protected void ColumnsSelectedIndexChanged(object o, EventArgs e)
{
LoadCriteria();
}
public void LoadColumns()
{
ddlColumns.DataSource = User.GetItemSearchProperties();
ddlColumns.DataTextField = "SearchColumn";
ddlColumns.DataValueField = "CriteriaSearchControlType";
ddlColumns.DataBind();
LoadCriteria();
}
private void LoadCriteria()
{
var controlType = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].CriteriaSearchControlType;
var ops = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].ValidOperators;
ddlOperators.DataSource = ops;
ddlOperators.DataTextField = "key";
ddlOperators.DataValueField = "value";
ddlOperators.DataBind();
switch (controlType)
{
case ResourceStrings.ViewFilter_ControlTypes_DDL:
criteriaDDL.Visible = true;
criteriaText.Visible = false;
var crit = User.GetItemSearchProperties()[ddlColumns.SelectedIndex].SearchCriteria;
ddlCriteria.DataSource = crit;
ddlCriteria.DataBind();
break;
case ResourceStrings.ViewFilter_ControlTypes_Text:
criteriaDDL.Visible = false;
criteriaText.Visible = true;
break;
}
}
public event EventHandler OnColumnChanged;
public ISearchCriterion FilterOptionsValues { get; set; }
}
}
I figured it out. Here is my solution:
I modified the GetPostBackControl to look for not only the linkbutton that inserts the user control, but for controls that contain the id of child controls of the inserted user control(as to capture the OnSelectedIndexChanged that gets fired from inside my user control).
protected Control GetPostBackControl(System.Web.UI.Page page)
{
Control control = null;
string ctrlname = Page.Request.Params["__EVENTTARGET"];
if (ctrlname != null && ctrlname != String.Empty)
{
//if it contains options then it's a control inside my usercontrol
if (ctrlname.Split('$')[2].Contains("options"))
{
var c = new Control();
c.ID = ctrlname;
return c;
}
else
{
control = FindControlRecursive(page, ctrlname.Split('$')[2]);
}
}
else
{
string ctrlStr = String.Empty;
Control c = null;
foreach (string ctl in Page.Request.Form)
{
if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
{
ctrlStr = ctl.Substring(0, ctl.Length - 2);
c = page.FindControl(ctrlStr);
}
else
{
c = page.FindControl(ctl);
}
if (c is System.Web.UI.WebControls.CheckBox ||
c is System.Web.UI.WebControls.CheckBoxList)
{
control = c;
break;
}
}
}
return control;
}
Then I modify the OnPreInit event to look for controls with an id of the linkbutton or an id that contains "options" :
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
System.Web.UI.MasterPage m = Master;
Control control = GetPostBackControl(this);
if (control != null)
{
if ((control.ClientID == (lbAddAndCondtion.ClientID) || createAgain) || control.ID.Contains("options"))
{
createAgain = true;
CreateUserControl(control.ID);
}
}
}
The critical fix was in the CreateUserControl method. In my original code I was trying to directly load the user control from my generic list that was stored in Session. I changed that to actually create a new instance of the user control, assign that new instance an id that matches the one stored in Session, and then add it to the placeholder:
protected void CreateUserControl(string controlID)
{
try
{
if (createAgain && phConditions != null)
{
if (OptionControls.Count > 0)
{
phConditions.Controls.Clear();
foreach (var c in OptionControls)
{
FilterOptionsCollectionView foc = new FilterOptionsCollectionView();
foc = Page.LoadControl("~/Views/FilterOptionsCollectionView.ascx") as FilterOptionsCollectionView;
foc.ID = c.ID;
phConditions.Controls.Add(foc);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
The only thing I changed in the user control was moving the method that loads my drop down lists's and wiring up the OnSelectedIndexChanged event into the OnInit event. Now I can dynamically load as many instances of the user control I want and all of the event's inside the user control fire correctly and state is persisted across postbacks!!
Hope this helps someone else!!

How to maintain state in (asp.net) custom server control?

I am trying to make a custom server control which inherits from DropDownList. I give the control an XML input containing some key/value pairs and my control shows them as a DropDownList.
I make the list items in the override Render method like this:
foreach (XElement child in root.Elements("Choice"))
{
string title = child.Element("Title").Value;
string score = child.Element("Score").Value;
item = new ListItem();
item.Text = title;
item.Value = score;
this.Items.Add(item);
}
The problem is that, when the user selects and item in the list, and the page posts back, the selected item is lost, and the list is re-initialized with the default data.
Does anyone have any idea how to keep the selected item, i.e. maintain the state?
Here is the complete source:
public class MultipleChoiceQuestionView2 : DropDownList
{
public MultipleChoiceQuestionView2() : base()
{
}
protected override void Render(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
#region Parse Contets
if (!String.IsNullOrEmpty(this.Contents))
{
XElement root = XElement.Parse(this.Contents);
if (root.HasAttributes)
{
this.NoOfChoices = Int32.Parse(root.Attribute("ItemCount").Value);
}
this.Items.Clear();
this.Style.Add("width", "100px");
this.Style.Add("font-family", "Tahoma");
this.Items.Clear();
ListItem item = new ListItem();
item.Text = "";
item.Value = "0";
this.Items.Add(item);
foreach (XElement child in root.Elements("Choice"))
{
string title = child.Element("Title").Value;
string score = child.Element("Score").Value;
item = new ListItem();
item.Text = title;
item.Value = score;
this.Items.Add(item);
}
}
#endregion
base.Render(writer);
writer.RenderEndTag();
if (this.Required)
{
RequiredFieldValidator rfv = new RequiredFieldValidator();
rfv.ControlToValidate = this.ID;
rfv.InitialValue = "0";
rfv.Text = "*";
if (!String.IsNullOrEmpty(this.ValidationGroup))
{
rfv.ValidationGroup = this.ValidationGroup;
}
writer.RenderBeginTag(HtmlTextWriterTag.Td);
rfv.RenderControl(writer);
writer.RenderEndTag();
}
writer.RenderEndTag();
writer.RenderEndTag();
}
#region Properties
public string Contents
{
get
{
return ViewState["Contents"] == null ? "" : ViewState["Contents"].ToString();
}
set
{
ViewState["Contents"] = value;
}
}
private int mNoOfChoices;
public int NoOfChoices
{
get
{
return mNoOfChoices;
}
set
{
mNoOfChoices = value;
}
}
private string mValidationGroup;
public string ValidationGroup
{
get
{
return mValidationGroup;
}
set
{
mValidationGroup = value;
}
}
public string SelectedChoice
{
get
{
return "";
}
}
private bool mRequired = false;
public bool Required
{
get
{
return mRequired;
}
set
{
mRequired = value;
}
}
#endregion
}
Thanks in advance.
You've got two options: ViewState or ControlState.
The difference being ViewState can be overriden by setting EnableViewState="false" in the page directive, whilst ControlState cannot.
Essentially you need to hook into the state bag when you're getting/setting the values of the dropdown.
There's a decent example here where a custom control is derived from the Button class and maintains state between page requests - should fit your scenario nicely.
Hopefully that gets you started.

Dropdownlist control with <optgroup>s for asp.net (webforms)?

Can anyone recommend a dropdownlist control for asp.net (3.5) that can render option groups? Thanks
I've used the standard control in the past, and just added a simple ControlAdapter for it that would override the default behavior so it could render <optgroup>s in certain places. This works great even if you have controls that don't need the special behavior, because the additional feature doesn't get in the way.
Note that this was for a specific purpose and written in .Net 2.0, so it may not suit you as well, but it should at least give you a starting point. Also, you have to hook it up using a .browserfile in your project (see the end of the post for an example).
'This codes makes the dropdownlist control recognize items with "--"
'for the label or items with an OptionGroup attribute and render them
'as <optgroup> instead of <option>.
Public Class DropDownListAdapter
Inherits System.Web.UI.WebControls.Adapters.WebControlAdapter
Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
Dim list As DropDownList = Me.Control
Dim currentOptionGroup As String
Dim renderedOptionGroups As New Generic.List(Of String)
For Each item As ListItem In list.Items
Page.ClientScript.RegisterForEventValidation(list.UniqueID, item.Value)
If item.Attributes("OptionGroup") IsNot Nothing Then
'The item is part of an option group
currentOptionGroup = item.Attributes("OptionGroup")
If Not renderedOptionGroups.Contains(currentOptionGroup) Then
'the header was not written- do that first
'TODO: make this stack-based, so the same option group can be used more than once in longer select element (check the most-recent stack item instead of anything in the list)
If (renderedOptionGroups.Count > 0) Then
RenderOptionGroupEndTag(writer) 'need to close previous group
End If
RenderOptionGroupBeginTag(currentOptionGroup, writer)
renderedOptionGroups.Add(currentOptionGroup)
End If
RenderListItem(item, writer)
ElseIf item.Text = "--" Then 'simple separator
RenderOptionGroupBeginTag("--", writer)
RenderOptionGroupEndTag(writer)
Else
'default behavior: render the list item as normal
RenderListItem(item, writer)
End If
Next item
If renderedOptionGroups.Count > 0 Then
RenderOptionGroupEndTag(writer)
End If
End Sub
Private Sub RenderOptionGroupBeginTag(ByVal name As String, ByVal writer As HtmlTextWriter)
writer.WriteBeginTag("optgroup")
writer.WriteAttribute("label", name)
writer.Write(HtmlTextWriter.TagRightChar)
writer.WriteLine()
End Sub
Private Sub RenderOptionGroupEndTag(ByVal writer As HtmlTextWriter)
writer.WriteEndTag("optgroup")
writer.WriteLine()
End Sub
Private Sub RenderListItem(ByVal item As ListItem, ByVal writer As HtmlTextWriter)
writer.WriteBeginTag("option")
writer.WriteAttribute("value", item.Value, True)
If item.Selected Then
writer.WriteAttribute("selected", "selected", False)
End If
For Each key As String In item.Attributes.Keys
writer.WriteAttribute(key, item.Attributes(key))
Next key
writer.Write(HtmlTextWriter.TagRightChar)
HttpUtility.HtmlEncode(item.Text, writer)
writer.WriteEndTag("option")
writer.WriteLine()
End Sub
End Class
Here's a C# implementation of the same Class:
/* This codes makes the dropdownlist control recognize items with "--"
* for the label or items with an OptionGroup attribute and render them
* as <optgroup> instead of <option>.
*/
public class DropDownListAdapter : WebControlAdapter
{
protected override void RenderContents(HtmlTextWriter writer)
{
//System.Web.HttpContext.Current.Response.Write("here");
var list = (DropDownList)this.Control;
string currentOptionGroup;
var renderedOptionGroups = new List<string>();
foreach (ListItem item in list.Items)
{
Page.ClientScript.RegisterForEventValidation(list.UniqueID, item.Value);
//Is the item part of an option group?
if (item.Attributes["OptionGroup"] != null)
{
currentOptionGroup = item.Attributes["OptionGroup"];
//Was the option header already written, then just render the list item
if (renderedOptionGroups.Contains(currentOptionGroup))
RenderListItem(item, writer);
//The header was not written,do that first
else
{
//Close previous group
if (renderedOptionGroups.Count > 0)
RenderOptionGroupEndTag(writer);
RenderOptionGroupBeginTag(currentOptionGroup, writer);
renderedOptionGroups.Add(currentOptionGroup);
RenderListItem(item, writer);
}
}
//Simple separator
else if (item.Text == "--")
{
RenderOptionGroupBeginTag("--", writer);
RenderOptionGroupEndTag(writer);
}
//Default behavior, render the list item as normal
else
RenderListItem(item, writer);
}
if (renderedOptionGroups.Count > 0)
RenderOptionGroupEndTag(writer);
}
private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer)
{
writer.WriteBeginTag("optgroup");
writer.WriteAttribute("label", name);
writer.Write(HtmlTextWriter.TagRightChar);
writer.WriteLine();
}
private void RenderOptionGroupEndTag(HtmlTextWriter writer)
{
writer.WriteEndTag("optgroup");
writer.WriteLine();
}
private void RenderListItem(ListItem item, HtmlTextWriter writer)
{
writer.WriteBeginTag("option");
writer.WriteAttribute("value", item.Value, true);
if (item.Selected)
writer.WriteAttribute("selected", "selected", false);
foreach (string key in item.Attributes.Keys)
writer.WriteAttribute(key, item.Attributes[key]);
writer.Write(HtmlTextWriter.TagRightChar);
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag("option");
writer.WriteLine();
}
}
My browser file was named "App_Browsers\BrowserFile.browser" and looked like this:
<!--
You can find existing browser definitions at
<windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers
-->
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.WebControls.DropDownList"
adapterType="DropDownListAdapter" />
</controlAdapters>
</browser>
</browsers>
I have used JQuery to achieve this task. I first added an new attribute for every ListItem from the backend and then used that attribute in JQuery wrapAll() method to create groups...
C#:
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");
}
JQuery:
$(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'>");
});
Thanks Joel! everyone... here's C# version if you want it:
using System;
using System.Web.UI.WebControls.Adapters;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Web;
//This codes makes the dropdownlist control recognize items with "--"'
//for the label or items with an OptionGroup attribute and render them'
//as instead of .'
public class DropDownListAdapter : WebControlAdapter
{
protected override void RenderContents(HtmlTextWriter writer)
{
DropDownList list = Control as DropDownList;
string currentOptionGroup;
List renderedOptionGroups = new List();
foreach(ListItem item in list.Items)
{
if (item.Attributes["OptionGroup"] != null)
{
//'The item is part of an option group'
currentOptionGroup = item.Attributes["OptionGroup"];
//'the option header was already written, just render the list item'
if(renderedOptionGroups.Contains(currentOptionGroup))
RenderListItem(item, writer);
else
{
//the header was not written- do that first'
if (renderedOptionGroups.Count > 0)
RenderOptionGroupEndTag(writer); //'need to close previous group'
RenderOptionGroupBeginTag(currentOptionGroup, writer);
renderedOptionGroups.Add(currentOptionGroup);
RenderListItem(item, writer);
}
}
else if (item.Text == "--") //simple separator
{
RenderOptionGroupBeginTag("--", writer);
RenderOptionGroupEndTag(writer);
}
else
{
//default behavior: render the list item as normal'
RenderListItem(item, writer);
}
}
if(renderedOptionGroups.Count > 0)
RenderOptionGroupEndTag(writer);
}
private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer)
{
writer.WriteBeginTag("optgroup");
writer.WriteAttribute("label", name);
writer.Write(HtmlTextWriter.TagRightChar);
writer.WriteLine();
}
private void RenderOptionGroupEndTag(HtmlTextWriter writer)
{
writer.WriteEndTag("optgroup");
writer.WriteLine();
}
private void RenderListItem(ListItem item, HtmlTextWriter writer)
{
writer.WriteBeginTag("option");
writer.WriteAttribute("value", item.Value, true);
if (item.Selected)
writer.WriteAttribute("selected", "selected", false);
foreach (string key in item.Attributes.Keys)
writer.WriteAttribute(key, item.Attributes[key]);
writer.Write(HtmlTextWriter.TagRightChar);
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag("option");
writer.WriteLine();
}
}
The above code renders the end tag for the optgroup before any of the options, so the options don't get indented like they should in addition to the markup not properly representing the grouping. Here's my slightly modified version of Tom's code:
public class ExtendedDropDownList : System.Web.UI.WebControls.DropDownList
{
public const string OptionGroupTag = "optgroup";
private const string OptionTag = "option";
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int count = items.Count;
string tag;
string optgroupLabel;
if (count > 0)
{
bool flag = false;
string prevOptGroup = null;
for (int i = 0; i < count; i++)
{
tag = OptionTag;
optgroupLabel = null;
ListItem item = items[i];
if (item.Enabled)
{
if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null)
{
optgroupLabel = item.Attributes[OptionGroupTag];
if (prevOptGroup != optgroupLabel)
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
writer.WriteBeginTag(OptionGroupTag);
if (!string.IsNullOrEmpty(optgroupLabel))
{
writer.WriteAttribute("label", optgroupLabel);
}
writer.Write('>');
}
item.Attributes.Remove(OptionGroupTag);
prevOptGroup = optgroupLabel;
}
else
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
prevOptGroup = null;
}
writer.WriteBeginTag(tag);
if (item.Selected)
{
if (flag)
{
this.VerifyMultiSelect();
}
flag = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.Attributes != null && item.Attributes.Count > 0)
{
item.Attributes.Render(writer);
}
if (optgroupLabel != null)
{
item.Attributes.Add(OptionGroupTag, optgroupLabel);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag(tag);
writer.WriteLine();
if (i == count - 1)
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
}
}
}
}
}
protected override object SaveViewState()
{
object[] state = new object[this.Items.Count + 1];
object baseState = base.SaveViewState();
state[0] = baseState;
bool itemHasAttributes = false;
for (int i = 0; i < this.Items.Count; i++)
{
if (this.Items[i].Attributes.Count > 0)
{
itemHasAttributes = true;
object[] attributes = new object[this.Items[i].Attributes.Count * 2];
int k = 0;
foreach (string key in this.Items[i].Attributes.Keys)
{
attributes[k] = key;
k++;
attributes[k] = this.Items[i].Attributes[key];
k++;
}
state[i + 1] = attributes;
}
}
if (itemHasAttributes)
return state;
return baseState;
}
protected override void LoadViewState(object savedState)
{
if (savedState == null)
return;
if (!(savedState.GetType().GetElementType() == null) &&
(savedState.GetType().GetElementType().Equals(typeof(object))))
{
object[] state = (object[])savedState;
base.LoadViewState(state[0]);
for (int i = 1; i < state.Length; i++)
{
if (state[i] != null)
{
object[] attributes = (object[])state[i];
for (int k = 0; k < attributes.Length; k += 2)
{
this.Items[i - 1].Attributes.Add
(attributes[k].ToString(), attributes[k + 1].ToString());
}
}
}
}
else
{
base.LoadViewState(savedState);
}
}
}
Use it like this:
ListItem item1 = new ListItem("option1");
item1.Attributes.Add("optgroup", "CatA");
ListItem item2 = new ListItem("option2");
item2.Attributes.Add("optgroup", "CatA");
ListItem item3 = new ListItem("option3");
item3.Attributes.Add("optgroup", "CatB");
ListItem item4 = new ListItem("option4");
item4.Attributes.Add("optgroup", "CatB");
ListItem item5 = new ListItem("NoOptGroup");
ddlTest.Items.Add(item1);
ddlTest.Items.Add(item2);
ddlTest.Items.Add(item3);
ddlTest.Items.Add(item4);
ddlTest.Items.Add(item5);
and here's the generated markup (indented for ease of viewing):
<select name="ddlTest" id="Select1">
<optgroup label="CatA">
<option selected="selected" value="option1">option1</option>
<option value="option2">option2</option>
</optgroup>
<optgroup label="CatB">
<option value="option3">option3</option>
<option value="option4">option4</option>
</optgroup>
<option value="NoOptGroup">NoOptGroup</option>
</select>
The Sharp Pieces project on CodePlex solves this (and several other) control limitations.
I use the reflector to see why is not supported. There is why. In the render method of the ListControl no condition is there to create the optgroup.
protected internal override void RenderContents(HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int count = items.Count;
if (count > 0)
{
bool flag = false;
for (int i = 0; i < count; i++)
{
ListItem item = items[i];
if (item.Enabled)
{
writer.WriteBeginTag("option");
if (item.Selected)
{
if (flag)
{
this.VerifyMultiSelect();
}
flag = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.HasAttributes)
{
item.Attributes.Render(writer);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag("option");
writer.WriteLine();
}
}
}
}
So i create my own dropdown Control with an override of the method RenderContents. There is my control. Is working fine. I use exactly the same code of Microsoft, just add a little condition to support listItem having attribute optgroup to create an optgroup and not a option.
Give me some feed back
public class DropDownListWithOptionGroup : DropDownList
{
public const string OptionGroupTag = "optgroup";
private const string OptionTag = "option";
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int count = items.Count;
string tag;
string optgroupLabel;
if (count > 0)
{
bool flag = false;
for (int i = 0; i < count; i++)
{
tag = OptionTag;
optgroupLabel = null;
ListItem item = items[i];
if (item.Enabled)
{
if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null)
{
tag = OptionGroupTag;
optgroupLabel = item.Attributes[OptionGroupTag];
}
writer.WriteBeginTag(tag);
// NOTE(cboivin): Is optionGroup
if (!string.IsNullOrEmpty(optgroupLabel))
{
writer.WriteAttribute("label", optgroupLabel);
}
else
{
if (item.Selected)
{
if (flag)
{
this.VerifyMultiSelect();
}
flag = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.Attributes != null && item.Attributes.Count > 0)
{
item.Attributes.Render(writer);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag(tag);
writer.WriteLine();
}
}
}
}
}
Based on the posts above I've created a c# version of this control with working view state.
public const string OptionGroupTag = "optgroup";
private const string OptionTag = "option";
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int count = items.Count;
string tag;
string optgroupLabel;
if (count > 0)
{
bool flag = false;
for (int i = 0; i < count; i++)
{
tag = OptionTag;
optgroupLabel = null;
ListItem item = items[i];
if (item.Enabled)
{
if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null)
{
tag = OptionGroupTag;
optgroupLabel = item.Attributes[OptionGroupTag];
}
writer.WriteBeginTag(tag);
// NOTE(cboivin): Is optionGroup
if (!string.IsNullOrEmpty(optgroupLabel))
{
writer.WriteAttribute("label", optgroupLabel);
}
else
{
if (item.Selected)
{
if (flag)
{
this.VerifyMultiSelect();
}
flag = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.Attributes != null && item.Attributes.Count > 0)
{
item.Attributes.Render(writer);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag(tag);
writer.WriteLine();
}
}
}
}
protected override object SaveViewState()
{
object[] state = new object[this.Items.Count + 1];
object baseState = base.SaveViewState();
state[0] = baseState;
bool itemHasAttributes = false;
for (int i = 0; i < this.Items.Count; i++)
{
if (this.Items[i].Attributes.Count > 0)
{
itemHasAttributes = true;
object[] attributes = new object[this.Items[i].Attributes.Count * 2];
int k = 0;
foreach (string key in this.Items[i].Attributes.Keys)
{
attributes[k] = key;
k++;
attributes[k] = this.Items[i].Attributes[key];
k++;
}
state[i + 1] = attributes;
}
}
if (itemHasAttributes)
return state;
return baseState;
}
protected override void LoadViewState(object savedState)
{
if (savedState == null)
return;
if (!(savedState.GetType().GetElementType() == null) &&
(savedState.GetType().GetElementType().Equals(typeof(object))))
{
object[] state = (object[])savedState;
base.LoadViewState(state[0]);
for (int i = 1; i < state.Length; i++)
{
if (state[i] != null)
{
object[] attributes = (object[])state[i];
for (int k = 0; k < attributes.Length; k += 2)
{
this.Items[i - 1].Attributes.Add
(attributes[k].ToString(), attributes[k + 1].ToString());
}
}
}
}
else
{
base.LoadViewState(savedState);
}
}
I hope this helps some people :-)
A more generic approach to Irfan's jQuery-based solution:
backend
private void _addSelectItem(DropDownList list, string title, string value, string group = null) {
ListItem item = new ListItem(title, value);
if (!String.IsNullOrEmpty(group))
{
item.Attributes["data-category"] = group;
}
list.Items.Add(item);
}
...
_addSelectItem(dropDown, "Option 1", "1");
_addSelectItem(dropDown, "Option 2", "2", "Category");
_addSelectItem(dropDown, "Option 3", "3", "Category");
...
client
var groups = {};
$("select option[data-category]").each(function () {
groups[$.trim($(this).attr("data-category"))] = true;
});
$.each(groups, function (c) {
$("select option[data-category='"+c+"']").wrapAll('<optgroup label="' + c + '">');
});
I've done this using an outer repeater for the select and optgroups and an inner repeater for the items within that group:
<asp:Repeater ID="outerRepeater" runat="server">
<HeaderTemplate>
<select id="<%= outerRepeater.ClientID %>">
</HeaderTemplate>
<ItemTemplate>
<optgroup label="<%# Eval("GroupText") %>">
<asp:Repeater runat="server" DataSource='<%# Eval("Items") %>'>
<ItemTemplate>
<option value="<%# Eval("Value") %>"><%# Eval("Text") %></option>
</ItemTemplate>
</asp:Repeater>
</optgroup>
</ItemTemplate>
<FooterTemplate>
</select>
</FooterTemplate>
</asp:Repeater>
The data source for outerRepeater is a simple grouping as follows:
var data = (from o in thingsToDisplay
group oby GetAlphaGrouping(o.Name) into g
orderby g.Key
select new
{
Alpha = g.Key,
Items = g
});
And to get the alpha grouping character:
private string GetAlphaGrouping(string value)
{
string firstChar = value.Substring(0, 1).ToUpper();
int unused;
if (int.TryParse(firstChar, out unused))
return "#";
return firstChar.ToUpper();
}
It's not a perfect solution but it works. The correct solution would be to no longer use WebForms, but us MVC instead. :)
// How to use:
// 1. Create items in a select element or asp:DropDownList control
// 2. Set value of an option or ListItem to "_group_", those will be converted to optgroups
// 3. On page onload call createOptGroups(domElement), for example like this:
// - var lst = document.getElementById('lst');
// - createOptGroups(lst, "_group_");
// 4. You can change groupMarkerValue to anything, I used "_group_"
function createOptGroups(lst, groupMarkerValue) {
// Get an array containing the options
var childNodes = [];
for (var i = 0; i < lst.options.length; i++)
childNodes.push(lst.options[i]);
// Get the selected element so we can preserve selection
var selectedIndex = lst.selectedIndex;
var selectedChild = childNodes[selectedIndex];
var selectedValue = selectedChild.value;
// Remove all elements
while (lst.hasChildNodes())
lst.removeChild(lst.childNodes[0]);
// Go through the array of options and convert some into groups
var group = null;
for (var i = 0; i < childNodes.length; i++) {
var node = childNodes[i];
if (node.value == groupMarkerValue) {
group = document.createElement("optgroup");
group.label = node.text;
group.value = groupMarkerValue;
lst.appendChild(group);
continue;
}
// Add to group or directly under list
(group == null ? lst : group).appendChild(node);
}
// Preserve selected, no support for multi-selection here, sorry
selectedChild.selected = true;
}
Tested on Chrome 16, Firefox 9 and IE8.
As the answers above that overload the RenderContents method do work. You also have to remember to alter the viewstate. I have come into an issue when using the non-altered viewstate in UpdatePanels. This has parts taken from the Sharp Pieces Project.
Protected Overloads Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
Dim list As DropDownList = Me
Dim currentOptionGroup As String
Dim renderedOptionGroups As New List(Of String)()
For Each item As ListItem In list.Items
If item.Attributes("OptionGroup") Is Nothing Then
RenderListItem(item, writer)
Else
currentOptionGroup = item.Attributes("OptionGroup")
If renderedOptionGroups.Contains(currentOptionGroup) Then
RenderListItem(item, writer)
Else
If renderedOptionGroups.Count > 0 Then
RenderOptionGroupEndTag(writer)
End If
RenderOptionGroupBeginTag(currentOptionGroup, writer)
renderedOptionGroups.Add(currentOptionGroup)
RenderListItem(item, writer)
End If
End If
Next
If renderedOptionGroups.Count > 0 Then
RenderOptionGroupEndTag(writer)
End If
End Sub
Private Sub RenderOptionGroupBeginTag(ByVal name As String, ByVal writer As HtmlTextWriter)
writer.WriteBeginTag("optgroup")
writer.WriteAttribute("label", name)
writer.Write(HtmlTextWriter.TagRightChar)
writer.WriteLine()
End Sub
Private Sub RenderOptionGroupEndTag(ByVal writer As HtmlTextWriter)
writer.WriteEndTag("optgroup")
writer.WriteLine()
End Sub
Private Sub RenderListItem(ByVal item As ListItem, ByVal writer As HtmlTextWriter)
writer.WriteBeginTag("option")
writer.WriteAttribute("value", item.Value, True)
If item.Selected Then
writer.WriteAttribute("selected", "selected", False)
End If
For Each key As String In item.Attributes.Keys
writer.WriteAttribute(key, item.Attributes(key))
Next
writer.Write(HtmlTextWriter.TagRightChar)
HttpUtility.HtmlEncode(item.Text, writer)
writer.WriteEndTag("option")
writer.WriteLine()
End Sub
Protected Overrides Function SaveViewState() As Object
' Create an object array with one element for the CheckBoxList's
' ViewState contents, and one element for each ListItem in skmCheckBoxList
Dim state(Me.Items.Count + 1 - 1) As Object 'stupid vb array
Dim baseState As Object = MyBase.SaveViewState()
state(0) = baseState
' Now, see if we even need to save the view state
Dim itemHasAttributes As Boolean = False
For i As Integer = 0 To Me.Items.Count - 1
If Me.Items(i).Attributes.Count > 0 Then
itemHasAttributes = True
' Create an array of the item's Attribute's keys and values
Dim attribKV(Me.Items(i).Attributes.Count * 2 - 1) As Object 'stupid vb array
Dim k As Integer = 0
For Each key As String In Me.Items(i).Attributes.Keys
attribKV(k) = key
k += 1
attribKV(k) = Me.Items(i).Attributes(key)
k += 1
Next
state(i + 1) = attribKV
End If
Next
' return either baseState or state, depending on whether or not
' any ListItems had attributes
If (itemHasAttributes) Then
Return state
Else
Return baseState
End If
End Function
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
If savedState Is Nothing Then Return
' see if savedState is an object or object array
If Not savedState.GetType.GetElementType() Is Nothing AndAlso savedState.GetType.GetElementType().Equals(GetType(Object)) Then
' we have just the base state
MyBase.LoadViewState(savedState(0))
'we have an array of items with attributes
Dim state() As Object = savedState
MyBase.LoadViewState(state(0)) '/ load the base state
For i As Integer = 1 To state.Length - 1
If Not state(i) Is Nothing Then
' Load back in the attributes
Dim attribKV() As Object = state(i)
For k As Integer = 0 To attribKV.Length - 1 Step +2
Me.Items(i - 1).Attributes.Add(attribKV(k).ToString(), attribKV(k + 1).ToString())
Next
End If
Next
Else
'load it normal
MyBase.LoadViewState(savedState)
End If
End Sub
I expanded upon mkl's answer to make a DropDownList control to which you can DataBind. Here's the result (which may be subject to improvement):
public class UcDropDownListWithOptGroup : DropDownList
{
public const string OptionGroupTag = "optgroup";
private const string OptionTag = "option";
protected override void RenderContents(HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int count = items.Count;
string tag;
string optgroupLabel;
if (count > 0)
{
bool flag = false;
string prevOptGroup = null;
for (int i = 0; i < count; i++)
{
tag = OptionTag;
optgroupLabel = null;
ListItem item = items[i];
if (item.Enabled)
{
if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes["data-optgroup"] != null)
{
optgroupLabel = item.Attributes["data-optgroup"];
if (prevOptGroup != optgroupLabel)
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
writer.WriteBeginTag(OptionGroupTag);
if (!string.IsNullOrEmpty(optgroupLabel))
{
writer.WriteAttribute("label", optgroupLabel);
}
writer.Write('>');
}
item.Attributes.Remove(OptionGroupTag);
prevOptGroup = optgroupLabel;
}
else
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
prevOptGroup = null;
}
writer.WriteBeginTag(tag);
if (item.Selected)
{
if (flag)
{
this.VerifyMultiSelect();
}
flag = true;
writer.WriteAttribute("selected", "selected");
}
writer.WriteAttribute("value", item.Value, true);
if (item.Attributes != null && item.Attributes.Count > 0)
{
item.Attributes.Render(writer);
}
if (optgroupLabel != null)
{
item.Attributes.Add(OptionGroupTag, optgroupLabel);
}
if (this.Page != null)
{
this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
}
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag(tag);
writer.WriteLine();
if (i == count - 1)
{
if (prevOptGroup != null)
{
writer.WriteEndTag(OptionGroupTag);
}
}
}
}
}
}
protected override object SaveViewState()
{
object[] state = new object[this.Items.Count + 1];
object baseState = base.SaveViewState();
state[0] = baseState;
bool itemHasAttributes = false;
for (int i = 0; i < this.Items.Count; i++)
{
if (this.Items[i].Attributes.Count > 0)
{
itemHasAttributes = true;
object[] attributes = new object[this.Items[i].Attributes.Count * 2];
int k = 0;
foreach (string key in this.Items[i].Attributes.Keys)
{
attributes[k] = key;
k++;
attributes[k] = this.Items[i].Attributes[key];
k++;
}
state[i + 1] = attributes;
}
}
if (itemHasAttributes)
return state;
return baseState;
}
protected override void LoadViewState(object savedState)
{
if (savedState == null)
return;
if (!(savedState.GetType().GetElementType() == null) &&
(savedState.GetType().GetElementType().Equals(typeof(object))))
{
object[] state = (object[])savedState;
base.LoadViewState(state[0]);
for (int i = 1; i < state.Length; i++)
{
if (state[i] != null)
{
object[] attributes = (object[])state[i];
for (int k = 0; k < attributes.Length; k += 2)
{
this.Items[i - 1].Attributes.Add
(attributes[k].ToString(), attributes[k + 1].ToString());
}
}
}
}
else
{
base.LoadViewState(savedState);
}
}
protected override void PerformDataBinding(IEnumerable dataSource)
{
base.PerformDataBinding(dataSource);
if (!string.IsNullOrWhiteSpace(DataOptGroupField) && OptGroupTitles != null)
{
var currentItems = Items;
var dataSourceItems = dataSource.Cast<object>().ToList();
var staticItemsCount = Items.Count - dataSourceItems.Count;
for (var i = staticItemsCount; i < Items.Count; i++)
{
var dataSourceItem = dataSourceItems[i - staticItemsCount];
var optGroupValue = DataBinder.GetPropertyValue(dataSourceItem, DataOptGroupField);
currentItems[i].Attributes.Add("data-optgroup", OptGroupTitles[optGroupValue]);
}
}
}
public Dictionary<object, string> OptGroupTitles { get; set; }
public string DataOptGroupField { get; set; }
}
Things to note:
You can set a DataOptGroupField property, similiar to DataTextField and DataValueField. It defines which property of the databound item it should take into consideration for determining the correct optgroup.
You can set a OptGroupTitles property which defines to which optgroup label the property defined in DataOptGroupField should be translated.
As an example, take the following code in your page or user control:
<MyControls:UcDropDownListWithOptGroup runat="server" DataSourceID="dsX" DataTextField="MyDataTextField" DataValueField="MyDataValueField" DataOptGroupField="IsActive" OptGroupTitles='<%# MyGroupTitles %>' />
Where the 'IsActive' property of the databound item is a boolean.
In the code-behind of the page or user control, you define the following property:
public Dictionary<object, string> MyGroupTitles
{
get
{
return new Dictionary<object, string>
{
{ true, "Active" },
{ false, "Inactive" }
};
}
}

Resources