提供多单词建议的自定义AutoCompleteExtender
默认情况下,AutoCompleteExtender显示的结果来自于文本框中输入的全部值,这里我的实现,它可以去搜索文本框中多于一个的单词,它们之间用逗号分割(或者别的符号),任何时间输入逗号,将会显示一个新的建议下拉列表。AutoCompleteExtender并不支持这种类型的列表,我们将通过一些修改来实现这些属性。[英文原文来自于CodeProject]
主要内容
1.简介
2.继承AutoCompleteProperties
3.继承AutoCompleteExtender
4.实现自定义的AutoCompleteBehavior
5.测试
简介
默认情况下,AutoCompleteExtender显示的结果来自于文本框中输入的全部值,这里我的实现,它可以去搜索文本框中多于一个的单词,它们之间用逗号分割(或者别的符号),任何时间输入逗号,将会显示一个新的建议下拉列表。AutoCompleteExtender并不支持这种类型的列表,我们将通过一些修改来实现这些属性。
继承AutoCompleteProperties
第一步为新控件CustomAutoCompleteExtender创建C#类,我们将定义一个继承于AutoCompleteProperties的类CustomAutoCompleteProperties,并添加多单词建议支持和CSS样式属性,为了实现多单词建议,我们需要一个属性SeparatorChar,通过该属性,我们可以拆分成一个一个的单词并为最新输入的单词打开建议列表。
{
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继承并为控件添加新的属性。
{
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
现在控件已经完成,我们只剩下管理客户端代码以发送正确的值到WebService并提供我们自定义的CSS样式。在Atlas.js中查找到AutoCompleteBehavior,我们可以拷贝到这里并注册我们自己的类。
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,它用来延迟显示下拉列表,在该函数中,我们可以发送该值到WebService,如果需要我们也可以改变它的值。
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会验证驻留的分割字符,找到最后一个单词或者TextBox中的全部文本发送到WebService。
测试
解决方案:AutoCompleteBehavior.js保存在scriptLibrary文件夹下,CustomAutoCompleteProperties.cs 和CustomAutoCompleteExtender.cs保存在App_Code文件夹下。
创建一个新的.aspx文件,并且添加对CustomAutoCompleteExtender类的引用,并在页面中放一些控件:
<%@ 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>
调用一个简单的示例WebService,输入一个单词,再输入逗号(逗号是默认值),并重新输入一个新的单词,将会显示出一个新的建议下拉列表。显示效果如下:
希望对你有所帮助!
原文地址:http://www.codeproject.com/Ajax/CustomAutoCompleteExt.asp
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com