具有多词建议的自定义 AutoCompleteExtender






4.27/5 (4投票s)
实现一个 AutoCompleteExtender,用于多词建议并更改控件的样式。
引言
默认情况下,AutoCompleteExtender
显示来自一个文本框的整个值的结果。通过我的实现,可以在一个用逗号(或其他字符)分隔的文本框中搜索多个单词。每次写入逗号时,都会为新单词显示建议列表。
目前,AutoCompleteExtender
不支持弹出列表的样式。我们将在修改多词建议时实现这些属性。
继承 AutoCompleteProperties
第一步是为新的控件 CustomAutoCompleteExtender
创建 C# 类。我们想要声明一个名为 CustomAutoCompleteProperties
的类,该类继承自 AutoCompleteProperties
并添加对“多词建议”和 CSS 样式属性的支持。对于“多词建议”,我们需要一个属性:SeparatorChar
。通过此属性,我们可以逐字分隔,并为编写的新单词打开建议列表。
namespace CustomAtlas.Controls
{
public class CustomAutoCompleteProperties : AutoCompleteProperties
{
public string SeparatorChar
{
get
{
object obj = base.ViewState["SeparatorChar"];
if (obj != null) return (string)obj;
else return ",";
}
set
{
base.ViewState["SeparatorChar"] = value;
base.OnChanged(EventArgs.Empty);
}
}
public string CssList
{
get
{
object obj = base.ViewState["CssList"];
if (obj != null) return (string)obj;
else return String.Empty;
}
set
{
base.ViewState["CssList"] = value;
base.OnChanged(EventArgs.Empty);
}
}
public string CssItem
{
get
{
object obj = base.ViewState["CssItem"];
if (obj != null) return (string)obj;
else return String.Empty;
}
set
{
base.ViewState["CssItem"] = value;
base.OnChanged(EventArgs.Empty);
}
}
public string CssHoverItem
{
get
{
object obj = base.ViewState["CssHoverItem"];
if (obj != null) return (string)obj;
else return String.Empty;
}
set
{
base.ViewState["CssHoverItem"] = value;
base.OnChanged(EventArgs.Empty);
}
}
}
}
CssList
、CssItem
和 CssHoverItem
对于构建控件的样式是必需的。CssList
用于绘制列表框,而 CssItem
和 CssHoverItem
用于绘制列表中的每个项目。
继承 AutoCompleteExtender
完成第一步后,我们继续使用 Extender。在这种情况下,我们继承自 AutoCompleteExtender
类,并向控件添加新属性
namespace CustomAtlas.Controls
{
public class CustomAutoCompleteExtender : AutoCompleteExtender
{
protected override void RenderScript(
Microsoft.Web.Script.ScriptTextWriter writer, Control targetControl)
{
// get our CustomAutoCompleteProperties
CustomAutoCompleteProperties cacp =
(CustomAutoCompleteProperties)
base.GetTargetProperties(targetControl);
if ((cacp != null) && cacp.Enabled)
{
// check if the ServicePath is set
string _ServicePath = cacp.ServicePath;
if (_ServicePath == String.Empty)
{
_ServicePath = this.ServicePath;
}
if (_ServicePath == String.Empty)
{
throw new InvalidOperationException("The ServicePath " +
"must be set for AutoCompleteBehavior");
}
// check if the ServiceMethod is set
string _ServiceMethod = cacp.ServiceMethod;
if (_ServiceMethod == String.Empty)
{
_ServiceMethod = this.ServiceMethod;
}
if (_ServiceMethod == String.Empty)
{
throw new InvalidOperationException("The ServiceMethod " +
"must be set for AutoCompleteBehavior");
}
// search for the completion list control if an ID was supplied
Control c = null;
string drp = this.DropDownPanelID;
if (drp != String.Empty)
{
c = this.NamingContainer.FindControl(drp);
if (c == null)
{
throw new InvalidOperationException("The specified " +
"DropDownPanelID is not a valid ID");
}
}
// write the Atlas markup on page
writer.WriteStartElement("autoComplete");
writer.WriteAttributeString("serviceURL",
base.ResolveClientUrl(_ServicePath));
writer.WriteAttributeString("serviceMethod", _ServiceMethod);
if (c != null)
writer.WriteAttributeString("completionList", c.ClientID);
writer.WriteAttributeString("minimumPrefixLength",
cacp.MinimumPrefixLength.ToString());
writer.WriteAttributeString("separatorChar",
cacp.SeparatorChar);
writer.WriteAttributeString("cssList", cacp.CssList);
writer.WriteAttributeString("cssItem", cacp.CssItem);
writer.WriteAttributeString("cssHoverItem",
cacp.CssHoverItem);
writer.WriteEndElement();
}
}
}
}
实现自定义 AutoCompleteBehavior
现在控件已准备就绪,我们只需要管理客户端代码,将正确的值发送到 Web 服务(逗号分隔的单词)并应用我们的自定义 CSS 样式。
在 Atlas.js 文件中搜索 AutoCompleteBehavior
类,我们可以复制它并注册我们的自定义类
Type.registerNamespace('Custom.UI');
Custom.UI.AutoCompleteBehavior = function() {
Custom.UI.AutoCompleteBehavior.initializeBase(this);
var _appURL;
var _serviceURL;
var _serviceMethod;
var _separatorChar = ',';
var _minimumPrefixLength = 3;
var _cssList;
var _cssItem;
var _cssHoverItem;
var _completionSetCount = 10;
var _completionInterval = 1000;
var _completionListElement;
var _popupBehavior;
var _timer;
var _cache;
var _currentPrefix;
var _selectIndex;
var _focusHandler;
var _blurHandler;
var _keyDownHandler;
var _mouseDownHandler;
var _mouseUpHandler;
var _mouseOverHandler;
var _tickHandler;
this.get_appURL = function() {
return _appURL;
}
this.set_appURL = function(value) {
_appURL = value;
}
this.get_completionInterval = function() {
return _completionInterval;
}
this.set_completionInterval = function(value) {
_completionInterval = value;
}
this.get_completionList = function() {
return _completionListElement;
}
this.set_completionList = function(value) {
_completionListElement = value;
}
this.get_completionSetCount = function() {
return _completionSetCount;
}
this.set_completionSetCount = function(value) {
_completionSetCount = value;
}
this.get_minimumPrefixLength = function() {
return _minimumPrefixLength;
}
this.set_minimumPrefixLength = function(value) {
_minimumPrefixLength = value;
}
this.get_separatorChar = function() {
return _separatorChar;
}
this.set_separatorChar = function(value) {
_separatorChar = value;
}
this.get_serviceMethod = function() {
return _serviceMethod;
}
this.set_serviceMethod = function(value) {
_serviceMethod = value;
}
this.get_serviceURL = function() {
return _serviceURL;
}
this.set_serviceURL = function(value) {
_serviceURL = value;
}
/* styles */
this.get_cssList = function() {
return _cssList;
}
this.set_cssList = function(value) {
_cssList = value;
}
this.get_cssItem = function() {
return _cssItem;
}
this.set_cssItem = function(value) {
_cssItem = value;
}
this.get_cssHoverItem = function() {
return _cssHoverItem;
}
this.set_cssHoverItem = function(value) {
_cssHoverItem = value;
}
this.dispose = function() {
if (_timer) {
_timer.tick.remove(_tickHandler);
_timer.dispose();
}
var element = this.control.element;
element.detachEvent('onfocus', _focusHandler);
element.detachEvent('onblur', _blurHandler);
element.detachEvent('onkeydown', _keyDownHandler);
_completionListElement.detachEvent('onmousedown', _mouseDownHandler);
_completionListElement.detachEvent('onmouseup', _mouseUpHandler);
_completionListElement.detachEvent('onmouseover', _mouseOverHandler);
_tickHandler = null;
_focusHandler = null;
_blurHandler = null;
_keyDownHandler = null;
_mouseDownHandler = null;
_mouseUpHandler = null;
_mouseOverHandler = null;
Sys.UI.AutoCompleteBehavior.callBaseMethod(this, 'dispose');
}
this.getDescriptor = function() {
var td = Custom.UI.AutoCompleteBehavior.callBaseMethod(this,
'getDescriptor');
td.addProperty('completionInterval', Number);
td.addProperty('completionList', Object, false,
Sys.Attributes.Element, true);
td.addProperty('completionSetCount', Number);
td.addProperty('minimumPrefixLength', Number);
td.addProperty('separatorChar', String);
td.addProperty('cssList', String);
td.addProperty('cssItem', String);
td.addProperty('cssHoverItem', String);
td.addProperty('serviceMethod', String);
td.addProperty('serviceURL', String);
td.addProperty('appURL', String);
return td;
}
this.initialize = function() {
Custom.UI.AutoCompleteBehavior.callBaseMethod(this, 'initialize');
_tickHandler = Function.createDelegate(this, this._onTimerTick);
_focusHandler = Function.createDelegate(this, this._onGotFocus);
_blurHandler = Function.createDelegate(this, this._onLostFocus);
_keyDownHandler = Function.createDelegate(this, this._onKeyDown);
_mouseDownHandler = Function.createDelegate(this,
this._onListMouseDown);
_mouseUpHandler = Function.createDelegate(this, this._onListMouseUp);
_mouseOverHandler = Function.createDelegate(this,
this._onListMouseOver);
_timer = new Sys.Timer();
_timer.set_interval(_completionInterval);
_timer.tick.add(_tickHandler);
var element = this.control.element;
element.autocomplete = "off";
element.attachEvent('onfocus', _focusHandler);
element.attachEvent('onblur', _blurHandler);
element.attachEvent('onkeydown', _keyDownHandler);
var elementBounds = Sys.UI.Control.getBounds(element);
if (!_completionListElement) {
_completionListElement = document.createElement('DIV');
document.body.appendChild(_completionListElement);
}
// apply styles
var completionListStyle = _completionListElement.style;
if ( _cssList != '' )
{
_completionListElement.className = _cssList;
}
else
{
completionListStyle.backgroundColor = 'window';
completionListStyle.color = 'windowtext';
completionListStyle.border = 'solid 1px buttonshadow';
completionListStyle.cursor = 'default';
}
// default styles
completionListStyle.unselectable = 'unselectable';
completionListStyle.overflow = 'hidden';
completionListStyle.visibility = 'hidden';
completionListStyle.width = (elementBounds.width - 2) + 'px';
_completionListElement.attachEvent('onmousedown', _mouseDownHandler);
_completionListElement.attachEvent('onmouseup', _mouseUpHandler);
_completionListElement.attachEvent('onmouseover', _mouseOverHandler);
document.body.appendChild(_completionListElement);
var popupControl = new Sys.UI.Control(_completionListElement);
_popupBehavior = new Sys.UI.PopupBehavior();
_popupBehavior.set_parentElement(element);
_popupBehavior.set_positioningMode(Sys.UI.PositioningMode.BottomLeft);
popupControl.get_behaviors().add(_popupBehavior);
_popupBehavior.initialize();
popupControl.initialize();
}
this._hideCompletionList = function() {
_popupBehavior.hide();
_completionListElement.innerHTML = '';
_selectIndex = -1;
}
this._highlightItem = function(item) {
var children = _completionListElement.childNodes;
// non-selecteditems
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child != item) {
if ( _cssItem != '' )
{
child.className = _cssItem;
}
else
{
child.style.backgroundColor = 'window';
child.style.color = 'windowtext';
}
}
}
// selected item
if ( _cssHoverItem != '' )
{
item.className = _cssHoverItem;
}
else
{
item.style.backgroundColor = 'highlight';
item.style.color = 'highlighttext';
}
}
this._onListMouseDown = function() {
if (window.event.srcElement != _completionListElement) {
this._setText(window.event.srcElement.firstChild.nodeValue);
}
}
this._onListMouseUp = function() {
this.control.focus();
}
this._onListMouseOver = function() {
var item = window.event.srcElement;
_selectIndex = -1;
this._highlightItem(item);
}
this._onGotFocus = function() {
_timer.set_enabled(true);
}
this._onKeyDown = function() {
var e = window.event;
if (e.keyCode == 27) {
this._hideCompletionList();
e.returnValue = false;
}
else if (e.keyCode == Sys.UI.Key.Up) {
if (_selectIndex > 0) {
_selectIndex--;
this._highlightItem(
_completionListElement.childNodes[_selectIndex]);
e.returnValue = false;
}
}
else if (e.keyCode == Sys.UI.Key.Down) {
if (_selectIndex < (_completionListElement.childNodes.length - 1)) {
_selectIndex++;
this._highlightItem(
_completionListElement.childNodes[_selectIndex]);
e.returnValue = false;
}
}
else if (e.keyCode == Sys.UI.Key.Return) {
if (_selectIndex != -1) {
this._setText(_completionListElement.childNodes[_selectIndex].
firstChild.nodeValue);
e.returnValue = false;
}
}
if (e.keyCode != Sys.UI.Key.Tab) {
_timer.set_enabled(true);
}
}
this._onLostFocus = function() {
_timer.set_enabled(false);
this._hideCompletionList();
}
function _onMethodComplete(result, response, context) {
var acBehavior = context[0];
var prefixText = context[1];
acBehavior._update(prefixText, result, true);
}
this._onTimerTick = function(sender, eventArgs) {
if (_serviceURL && _serviceMethod) {
var text = this.control.element.value;
if ( text.lastIndexOf(_separatorChar) > -1 )
{
// found separator char in the text
var pos = text.lastIndexOf(_separatorChar);
pos++;
text = text.substring(pos, (text.length));
text = text.trim();
}
if (text.trim().length < _minimumPrefixLength) {
this._update('', null, false);
return;
}
if (_currentPrefix != text) {
_currentPrefix = text;
if (_cache && _cache[text]) {
this._update(text, _cache[text], false);
return;
}
Sys.Net.ServiceMethod.invoke(_serviceURL, _serviceMethod,
_appURL, { prefixText : _currentPrefix, count:
_completionSetCount }, _onMethodComplete, null,
null, null, [ this, text ]);
}
}
}
this._setText = function(text) {
_timer.set_enabled(false);
_currentPrefix = text;
if (Sys.UI.TextBox.isInstanceOfType(this.control)) {
this.control.set_text(text);
}
else {
var currentValue = this.control.element.value;
if ( currentValue.lastIndexOf(_separatorChar) > -1 )
{
// found separator char in the text
var pos = currentValue.lastIndexOf(_separatorChar);
pos++;
currentValue = currentValue.substring(0, pos) + text;
}
else
{
// no separator char found
currentValue = text;
}
this.control.element.value = currentValue;
}
this._hideCompletionList();
}
this._update = function(prefixText, completionItems, cacheResults) {
if (cacheResults) {
if (!_cache) {
_cache = { };
}
_cache[prefixText] = completionItems;
}
_completionListElement.innerHTML = '';
_selectIndex = -1;
if (completionItems && completionItems.length) {
for (var i = 0; i < completionItems.length; i++) {
var itemElement = document.createElement('div');
itemElement.appendChild(
document.createTextNode(completionItems[i]));
itemElement.__item = '';
if ( _cssItem != '' )
{
itemElement.className = _cssItem;
}
else
{
var itemElementStyle = itemElement.style;
itemElementStyle.padding = '1px';
itemElementStyle.textAlign = 'left';
itemElementStyle.textOverflow = 'ellipsis';
itemElementStyle.backgroundColor = 'window';
itemElementStyle.color = 'windowtext';
}
_completionListElement.appendChild(itemElement);
}
_popupBehavior.show();
}
else {
_popupBehavior.hide();
}
}
}
Custom.UI.AutoCompleteBehavior.registerSealedClass(
'Custom.UI.AutoCompleteBehavior', Sys.UI.Behavior);
Sys.TypeDescriptor.addType('script', 'autoComplete',
Custom.UI.AutoCompleteBehavior);
首先,我们必须添加在 Extender 类中创建的四个属性。现在,我们可以使用放置在 .aspx 页面容器中的控件的属性值。
在代码中搜索,有一个函数 _onTimerTick
用于在延迟一段时间后显示列表。在这个函数中,我们将拦截要发送到 Web 服务的值,并根据需要更改它
var text = this.control.element.value;
if ( text.lastIndexOf(_separatorChar) > -1 )
{
// found separator char in the text, choosing the right word
var pos = text.lastIndexOf(_separatorChar);
pos++;
text = text.substring(pos, (text.length));
text = text.trim();
}
现在,当用户在文本框中键入值时,AutoCompleteBehavior
会验证分隔符字符是否存在。如果存在,则发送到 Web 服务的文本是找到的最后一个单词,而不是文本框的整个值。
像标准一样,我习惯将 .js 代码保存在 scriptLibrary 文件夹中。
让我们试一试
情况:AutoCompleteBehavior.js 保存在 scriptLibrary 文件夹中,CustomAutoCompleteProperties.cs 和 CustomAutoCompleteExtender.cs 保存在 App_Code 文件夹中...我们准备好试一试了。
创建一个新的 .aspx 文件,并添加对 CustomAutoCompleteExtender
类的引用,并将控件放置在页面中
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="CustomAtlas.Controls" TagPrefix="customAtlas" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>CustomAutoCompleteExtender</title>
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="scriptManager" runat="server">
<Scripts>
<atlas:ScriptReference ScriptName="Custom" />
<atlas:ScriptReference
Path="scriptLibrary/CustomAutoCompleteBehavior.js" />
</Scripts>
</atlas:ScriptManager>
<div>
<asp:TextBox ID="txtSuggestions"
runat="server"></asp:TextBox>
<customAtlas:CustomAutoCompleteExtender
ID="CustomAutoCompleteExtender1" runat="server">
<customAtlas:CustomAutoCompleteProperties
TargetControlID="txtSuggestions"
ServicePath="WebServiceDemo.asmx"
ServiceMethod="GetSuggestions"
MinimumPrefixLength="1"
SeparatorChar=","
CssList="autoCompleteList"
CssItem="autoCompleteItem"
CssHoverItem="autoCompleteHoverItem"
Enabled="true" />
</customAtlas:CustomAutoCompleteExtender>
</div>
</form>
</body>
</html>
调用一个演示 Web 服务(返回与编写的相同值 10 次),输入一个单词,然后输入逗号(逗号是默认字符),并以一个新单词开头,将显示一个带有新项目建议的列表。
希望这会有所帮助。