谁能推荐一个可以呈现选项组的asp.net(3.5)下拉列表控件?谢谢
12 回答
我过去使用过标准控件,只是为它添加了一个简单的ControlAdapter,它将覆盖默认行为,因此它可以在某些地方呈现 <optgroup>。即使您有不需要特殊行为的控件,这也很有效,因为附加功能不会妨碍您。
请注意,这是出于特定目的并用 .Net 2.0 编写的,因此它可能不适合您,但至少应该为您提供一个起点。此外,您必须在项目中使用 .browserfile 将其连接起来(请参阅文章末尾的示例)。
'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
这是同一类的 C# 实现:
/* 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();
}
}
我的浏览器文件名为“App_Browsers\BrowserFile.browser”,如下所示:
<!--
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>
我已经使用 JQuery 来完成这项任务。我首先从后端为每个添加了一个新属性ListItem
,然后在 JQuerywrapAll()
方法中使用该属性来创建组......
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");
}
查询:
$(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'>");
});
谢谢乔尔!大家……如果你想要的话,这里是 C# 版本:
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();
}
}
上面的代码在任何选项之前呈现 optgroup 的结束标记,因此除了标记不能正确表示分组之外,选项不会像它们应该的那样缩进。这是我对汤姆的代码稍作修改的版本:
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);
}
}
}
像这样使用它:
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);
这是生成的标记(为了便于查看而缩进):
<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>
CodePlex 上的Sharp Pieces 项目解决了这个(以及其他几个)控制限制。
我用反射器看看为什么不支持。这是有原因的。在 ListControl 的渲染方法中,不存在创建 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();
}
}
}
}
因此,我创建了自己的下拉控件,并覆盖了 RenderContents 方法。有我的控制。工作正常。我使用与 Microsoft 完全相同的代码,只是添加了一个小条件来支持 listItem 具有属性 optgroup 来创建一个 optgroup 而不是一个选项。
给我一些反馈
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();
}
}
}
}
}
根据上面的帖子,我创建了具有工作视图状态的此控件的 ac# 版本。
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);
}
}
我希望这可以帮助一些人:-)
Irfan 的基于 jQuery 的解决方案的更通用的方法:
后端
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");
...
客户
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 + '">');
});
我已经为 select 和 optgroups 使用了一个外部转发器,为该组中的项目使用了一个内部转发器:
<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>
的数据源outerRepeater
是一个简单的分组,如下所示:
var data = (from o in thingsToDisplay
group oby GetAlphaGrouping(o.Name) into g
orderby g.Key
select new
{
Alpha = g.Key,
Items = g
});
并获得 alpha 分组字符:
private string GetAlphaGrouping(string value)
{
string firstChar = value.Substring(0, 1).ToUpper();
int unused;
if (int.TryParse(firstChar, out unused))
return "#";
return firstChar.ToUpper();
}
这不是一个完美的解决方案,但它有效。正确的解决方案是不再使用 WebForms,而是使用 MVC。:)
正如上面的答案,重载 RenderContents 方法确实有效。您还必须记住更改视图状态。在 UpdatePanels 中使用未更改的视图状态时遇到了问题。这部分内容取自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
// 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;
}
在 Chrome 16、Firefox 9 和 IE8 上测试。
我扩展了mkl 的答案,制作了一个可以 DataBind 的 DropDownList 控件。这是结果(可能会有所改进):
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; }
}
注意事项:
- 您可以设置 DataOptGroupField 属性,类似于 DataTextField 和 DataValueField。它定义了在确定正确的 optgroup 时应该考虑数据绑定项的哪个属性。
- 您可以设置一个 OptGroupTitles 属性,该属性定义应将 DataOptGroupField 中定义的属性转换为哪个 optgroup 标签。
例如,在您的页面或用户控件中使用以下代码:
<MyControls:UcDropDownListWithOptGroup runat="server" DataSourceID="dsX" DataTextField="MyDataTextField" DataValueField="MyDataValueField" DataOptGroupField="IsActive" OptGroupTitles='<%# MyGroupTitles %>' />
其中数据绑定项的“IsActive”属性是一个布尔值。
在页面或用户控件的代码隐藏中,您定义以下属性:
public Dictionary<object, string> MyGroupTitles
{
get
{
return new Dictionary<object, string>
{
{ true, "Active" },
{ false, "Inactive" }
};
}
}