使用 jQuery 和 ASP.NET 创建 ComboBox Web 控件
ComboBox 是一个常用的控件,HTML 规范和 ASP.NET 都没有提供默认支持。在本文中,我将介绍一个使用 jQuery 和 ASP.NET 构建的 ComboBox Web 控件。
引言
ComboBox 是一个非常常见的控件,HTML 规范和标准的 ASP.NET Web 控件库都没有内置支持,因此解决方案是使用 JavaScript 创建一个,并将其封装在 ASP.NET 自定义控件中。您可以使用纯 JavaScript 将基本的 HTML 元素裁剪成 ComboBox,但这非常难以编写、维护和支持不同的浏览器。借助 jQuery JavaScript 库,这项工作变得容易多了。
要求
我所寻找的 ComboBox Web 控件必须满足以下要求:
1. 支持通过标准数据绑定属性和方法绑定数据
.NET 组件规范定义了数据绑定属性和方法,用于将源数据与绑定逻辑分离。遵守此约定,ComboBox Web 控件可以在任何数据绑定场景中轻松理解和使用。
2. 允许使用鼠标从 ComboBox 的下拉列表中选择项目
3. 允许在 ComboBox 的文本框中输入数据,并使用它来过滤下拉列表中的项目,然后用键盘选择预期的项目
创建 Web 控件
为了让 ComboBox Web 控件能在 ASP.NET Web 应用程序中使用,我们将它创建为一个自定义 Web 控件。由于 ComboBox 的行为类似于 TextBox 和 DropDownList 的组合,因此最好同时继承它们。不幸的是,C# 不允许多重继承,所以我们只能选择一个作为基类。由于 ComboBox 最重要的特点是允许用户直接在其中输入数据,因此我决定让它继承自 TextBox。同时,我实现了 DataSource、DataSourceID、DataMember 属性和 DataBind 方法,以支持数据绑定用法,尽管它不是标准 DataBoundControl 的子类。这些数据绑定属性和方法会将调用转发给内部的 DropDownList 控件。通过这样做,我无需花费太多精力编写数据绑定代码。ComboBox 包含的内部 DropDownList 控件仅用作下拉列表项的存储。ComboBox 对应的 JavaScript 文件 ComboBox.js 将使用这些数据进行客户端渲染。
在 OnPreRender 阶段,ComboBox 将 ComboBox.js JavaScript 文件输出到客户端浏览器。ComboBox.js 文件包含了所有客户端逻辑。
在 Render 阶段,ComboBox 创建一个包含基本 HTML 元素的 HTML 块来表示客户端 ComboBox,稍后,ComboBox.js 中的 JavaScript 代码会将生成的 HTML 元素裁剪成我们想要的样式和行为。
ComboBox HTML 块
ComboBox HTML 块如下所示:
<span style="position:relative;" ComboBox="1" > <input name="ComboBox1" type="text" value="Item 2" id="ComboBox1" class="textbox" style="width:200px;" /> <input type="button" value="V" /> <select name="ctl02" tabindex="-1" style="display:none;"> <option selected="selected" value="Item 1">Item 1</option> <option value="Item 2">Item 2</option> <option value="Item 3">Item 3</option> </select> <div style="visibility:hidden; background-color:white"></div> </span>最外层的元素是 SPAN。它用于在 HTML 中包含 ComboBox 控件的所有其他元素。它有一个自定义属性 ComboBox,用于将 SPAN 标记为 ComboBox 容器标记。然后,SPAN 内部的 Text 输入元素用于表示用户可以在 ComboBox 中输入数据的文本框。下一个元素是 Button 输入,用于表示 ComboBox 的按钮。它使用“V”作为按钮图标,以区别于内置的 HTML select 元素。接下来的元素是 Select,用于存储下拉列表项。最后,DIV 被包含在 ComboBox 中,以表示 ComboBox 的下拉列表。尽管 DIV 最初不包含任何内容,但 ComboBox.js 中的引导代码会向其中添加一个表格,使其看起来和行为都像一个下拉列表。
ComboBox.js
此 JavaScript 文件包含了在浏览器中构建 ComboBox 的所有逻辑。在此,我将更详细地解释 ComboBox 中每个路由和方法的用途。
初始化 ComboBox(引导)
DOM 渲染完成后,ComboBox.js 中的引导代码将按以下步骤初始化 ComboBox:
1. 查找页面上的所有 ComboBoxes
2. 在 jQuery .each API 中调用 ComboBox_InitListData 来初始化每个 ComboBox
3. 为 ComboBox 的下拉按钮“V”按钮添加点击事件处理程序
4. 为 ComboBox 的文本框添加 keydown 和 keyup 事件处理程序
5. 为 document 添加点击事件处理程序,以便当用户点击 ComboBox 下拉按钮以外的任何地方时,ComboBox 的下拉列表可以隐藏
//initialize all ComboBoxes after DOM is fully loaded $(function () { var $comboBoxs = $("span[ComboBox]"); //get all ComboBoxes. ComboBox is defined with span element that has ComboBox attribute //init each ComboBox with empty filter $comboBoxs.each(function (i, obj) { ComboBox_InitListData($(obj), ""); }); //add click event handler for ComboBox "V" button $comboBoxs.find(":button").on("click", function (e) { ComboBox_HideList(); //hide all ComboBoxes' dropdown list first var $container = $(this).closest("span"); //get the first element that matches the "span" selector, beginning at the current element and progressing up through the DOM tree ComboBox_InitListData($container, ""); //init ComboBox with empty filter $container.find("div").css({ "zIndex": "1", "visibility": "visible" }); //set css of div on zIndex and visibility e.stopPropagation(); //prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event }); //Set TextBox attribute and add keydown and keyup event handler for ComboBox textbox $comboBoxs.find(":text") .data("currentitem", -1) //current selected item is -1 .attr("autocomplete", "off") //turn off auto complete .on("keydown", function (e) { //set keydown event handler ComboBox_KeySelectItem(e); }) .on("keyup", function (e) { //set keyup event handler ComboBox_KeyFilterItem(e); }); //add click event handler for document $(document).click(function (e) { var element = e.srcElement || e.target; if (element != undefined && element.tagName == "INPUT" && element.value == "V") { //when click on the ComboBox "V" button, then do nothing (note: event handler on "V" button will handle this event) } else { //when click on somewhere else, then hide all ComboBoxes' dropdown list ComboBox_HideList(); } }); });
初始化列表数据
初始化列表数据方法通过在 DIV 中创建一个表格来显示下拉列表,这首先是通过查找 ComboBox 中的 DIV 来完成的,然后创建一个内存中的表格来保存 Select 元素中的所有项。最后,将表格放入 ComboBox DIV 并将其隐藏。
//init combobox items function ComboBox_InitListData($container, filterValue) { var $div = $container.find("div"); var newList = new Array(); var oSelect = $container.find("select")[0]; var len = oSelect.options.length; for (var i = 0; i < len; i++) { if (filterValue == undefined || filterValue == "") { newList[newList.length] = oSelect.options[i].text; } else { if (newList.length >= 9) { break; } var currVal = oSelect.options[i].text; if (currVal.length >= filterValue.length) { if (currVal.toLowerCase().substring(0, filterValue.length) == filterValue.toLowerCase()) { newList[newList.length] = currVal; } } } } var sHtml = []; sHtml.push("<table border=\"0\" cellpadding=\"0\" cellspace=\"0\" width=\"100%\" border=\"1\" style=\"z-index:10; background-color:white;\">"); for (var i = 0; i < newList.length; i++) { sHtml.push("<tr onMouseOver=\"this.bgColor='#191970'; this.style.color='#ffffff'; this.style.cursor='default'; \" onMouseOut=\"this.bgColor='#ffffff'; this.style.color='#000000';\">"); sHtml.push("<td nowrap onClick=\"ComboBox_SelectItemWithMouse(this);\">"); sHtml.push((newList[i] == "" ? " " : newList[i])); sHtml.push("</td>"); sHtml.push("</tr>"); } sHtml.push("</table>"); $div.html(sHtml.join('')); //set the HTML contents of div to concatenated string from sHtml arrary $div.css("overflowY", "auto"); //oTmp.style.overflowY = "auto"; $div.css("border", "1px solid midnightblue"); //oTmp.style.border = "1px solid midnightblue"; $div.css("position", "absolute"); //oTmp.style.position = "absolute"; $div.css("visibility", "hidden"); //oTmp.style.visibility = "hidden"; var count = $container.find("table td").size(); //get total ComboBox items var $text = $container.find(":text"); //get textbox element var $button = $container.find(":button") //get button element $div.css("width", $button.outerWidth() + $button.offset().left - $text.offset().left); //make the dropdown list same width as textbox + "V" button if (count > 7 || count == 0) { $div.css({ "height": "150" }); //limit the height of dropdown list when there is more than 7 items or default the height of dropdown list when there is no item } else { $div.css({ "height": count * 21 }); //set the height of dropdown box same as total of items' height. Each item's height is 21 } }
隐藏列表
当用户在页面的其他部分进行操作时,ComboBox 下拉列表不应该可见。这是 HTML 中 Select 元素的常见行为。通过查找所有 ComboBox 的下拉列表容器(DIV 元素),并使用 CSS 将其设置为隐藏,可以使所有 ComboBox 的下拉列表不可见。
//hide ComboBox dropdown list function ComboBox_HideList() { $("span[ComboBox]").find("div").css("visibility", "hidden"); }
列表是否隐藏
在大多数情况下,ComboBox 的行为取决于下拉列表是否可见,因此此方法用于提供下拉列表的可见性状态。
//is ComboBox dropdown list hidden function ComboBox_IsListHidden($container) { return $container.find("div").css("visibility") == "hidden"; }
设置下拉列表中的项目
每次按下按键时,ComboBox 都会尝试处理 Up、Down、Enter 和 Escape 键来选择下拉列表中的项目。这使得 ComboBox 的下拉列表具有与 Select 下拉列表相同的行为。
//keydown function ComboBox_KeySelectItem(e) { var txt = e.srcElement || e.target; var currentitem = $(txt).data("currentitem"); var val = $.trim($(txt).val()); //get value in textbox var $container = $(txt).closest("span[ComboBox]"); //get ComboBox container that is defined with span element and ComboBox attribute var key = e.keyCode || e.which; //get key code switch (key) { case 38: //up if (val == "" || ComboBox_IsListHidden($container)) { return; } --currentitem; $(txt).data("currentitem", currentitem); ComboBox_ChangeItemWithKey(txt); break; case 40: //down if (val == "" || ComboBox_IsListHidden($container)) { return; } currentitem++; $(txt).data("currentitem", currentitem) ComboBox_ChangeItemWithKey(txt); break; case 13: //enter if (!ComboBox_IsListHidden($container)) { ComboBox_HideList(); return false; } break; case 27: //esc ComboBox_HideList(); return false; break; default: break; } }
重置下拉列表
按键释放后,下拉列表应重置以匹配文本框中输入的文本。
//keyup function ComboBox_KeyFilterItem(e) { var txt = e.srcElement || e.target; //do nothing if up, down, enter, esc key pressed if (e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13 || e.keyCode == 27) { return; } $(txt).data("currentitem", -1); var val = $(txt).val(); if (val == "") { ComboBox_HideList(); return; } var $container = $(txt).closest("span[ComboBox]"); ComboBox_InitListData($container, val); var $div = $container.find("div"); $div.css({ "zIndex": "1", "visibility": "visible" }); //hide dropdown list if there is no item if ($div.find("td").size() == 0) { ComboBox_HideList(); } }
更改项目
当当前项目发生更改时,ComboBox 将切换文本和背景的颜色,使当前项目显示为突出显示。
//change item function ComboBox_ChangeItemWithKey(txt) { var $txt = $(txt); var currentitem = $txt.data("currentitem"); var table = $txt.closest("span[ComboBox]").find("table")[0]; for (i = 0; i < table.rows.length; i++) { table.rows[i].bgColor = "#ffffff"; table.rows[i].style.color = "#000000"; } if (currentitem < 0) { currentitem = table.rows.length - 1; } if (currentitem == table.rows.length) { currentitem = 0; } $txt.data("currentitem", currentitem); if (table.rows[currentitem] == null) { ComboBox_HideList(); return; } table.rows[currentitem].bgColor = "#191970"; //darkblue color table.rows[currentitem].style.color = "#ffffff"; //whit color $txt.val($(table.rows[currentitem].cells[0]).text()); }
选择项目
下拉列表项可以通过鼠标或键盘选择,选择一个项目后,选定项目的值将分配给文本框,然后下拉列表将关闭。
//select item function ComboBox_SelectItemWithMouse(td) { var $div = $(td).closest("div"); var $txt = $div.parent().find(":text"); var selectedValue = $(td).text(); if ($.trim(selectedValue) == "") { selectedValue = ""; } $txt.val(selectedValue); $txt[0].focus(); $div.css("visibility", "hidden"); }
jQuery 1.7 附加事件处理程序 API
ComboBox 使用 jQuery 1.7 的新附加事件处理程序 API .on() 方法来定义事件处理程序。.on() 方法是附加事件处理程序到元素的首选方法。旧版本 jQuery 的用户应优先使用 .delegate() 而非 .live()。
$(selector).live(events, data, handler); // jQuery 1.3+
$(document).delegate(selector, events, data, handler); // jQuery 1.4.3+
$(document).on(events, selector, data, handler); // jQuery 1.7+
如何使用 ComboBox
在 Visual Studio 项目中引用自定义 Web 控件程序集后,使用 ComboBox 最简单的方法就是将其从工具箱拖放到 ASP.NET 网页中。
将其拖放到 ASPX 页面后,Visual Studio 将自动生成 ComboBox 的标记
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Demo.Web.Default" %> <%@ Register assembly="Demo.WebControls" namespace="Demo.WebControls" tagprefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script type="text/javascript" src="Scripts/jquery-1.7.2.js"></script> </head> <body> <form id="form1" runat="server"> <div> <cc1:ComboBox ID="ComboBox1" runat="server"></cc1:ComboBox> </div> </form> </body> </html>添加一些代码隐藏代码将 ComboBox 与 List 集合绑定。
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { List<string> data = new List<string>(); data.Add("Item 1"); data.Add("Item 2"); data.Add("Item 3"); ComboBox1.DataSource = data; ComboBox1.DataBind(); } }这就是 ComboBox 在屏幕上的样子

摘要
借助 jQuery,我们可以将这些基本的 HTML 元素组合成一个更复杂的客户端组件——ComboBox。