提供多单词建议的自定义AutoCompleteExtender

默认情况下,AutoCompleteExtender显示的结果来自于文本框中输入的全部值,这里我的实现,它可以去搜索文本框中多于一个的单词,它们之间用逗号分割(或者别的符号),任何时间输入逗号,将会显示一个新的建议下拉列表。AutoCompleteExtender并不支持这种类型的列表,我们将通过一些修改来实现这些属性。[英文原文来自于CodeProject]

 

主要内容

1.简介

2.继承AutoCompleteProperties

3.继承AutoCompleteExtender

4.实现自定义的AutoCompleteBehavior

5.测试

 

简介

默认情况下,AutoCompleteExtender显示的结果来自于文本框中输入的全部值,这里我的实现,它可以去搜索文本框中多于一个的单词,它们之间用逗号分割(或者别的符号),任何时间输入逗号,将会显示一个新的建议下拉列表。AutoCompleteExtender并不支持这种类型的列表,我们将通过一些修改来实现这些属性。

 

继承AutoCompleteProperties

第一步为新控件CustomAutoCompleteExtender创建C#类,我们将定义一个继承于AutoCompleteProperties的类CustomAutoCompleteProperties,并添加多单词建议支持和CSS样式属性,为了实现多单词建议,我们需要一个属性SeparatorChar,通过该属性,我们可以拆分成一个一个的单词并为最新输入的单词打开建议列表。

namespace CustomAtlas.Controls

{

    
public class CustomAutoCompleteProperties : AutoCompleteProperties

    
{

        
public string SeparatorChar

        
{

            
get

            
{

                
object obj = base.ViewState["SeparatorChar"];

                
if (obj != nullreturn (string)obj;

                
else return ",";

            }


            
set

            
{

                
base.ViewState["SeparatorChar"= value;

                
base.OnChanged(EventArgs.Empty);

            }


        }


 

        
public string CssList

        
{

            
get

            
{

                
object obj = base.ViewState["CssList"];

                
if (obj != nullreturn (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 != nullreturn (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 != nullreturn (string)obj;

                
else return String.Empty;

            }


            
set

            
{

                
base.ViewState["CssHoverItem"= value;

                
base.OnChanged(EventArgs.Empty);

            }


        }


    }


}

CssList, CssItemCssHoverItem需要构建控件的样式,CssList提供用来画下拉列表,CssItemCssHoverItem用来画列表中的每一项。

 

继承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

现在控件已经完成,我们只剩下管理客户端代码以发送正确的值到WebService并提供我们自定义的CSS样式。在Atlas.js中查找到AutoCompleteBehavior,我们可以拷贝到这里并注册我们自己的类。

ype.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(thisthis._onTimerTick);

        _focusHandler 
= Function.createDelegate(thisthis._onGotFocus);

        _blurHandler 
= Function.createDelegate(thisthis._onLostFocus);

        _keyDownHandler 
= Function.createDelegate(thisthis._onKeyDown);

        _mouseDownHandler 
= Function.createDelegate(thisthis._onListMouseDown);

        _mouseUpHandler 
= Function.createDelegate(thisthis._onListMouseUp);

        _mouseOverHandler 
= Function.createDelegate(thisthis._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, 
nullnullnull,

                                                          [ 
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,如果需要我们也可以改变它的值。

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会验证驻留的分割字符,找到最后一个单词或者TextBox中的全部文本发送到WebService

 

测试

解决方案: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>

调用一个简单的示例WebService,输入一个单词,再输入逗号(逗号是默认值),并重新输入一个新的单词,将会显示出一个新的建议下拉列表。显示效果如下:

希望对你有所帮助!

原文地址:http://www.codeproject.com/Ajax/CustomAutoCompleteExt.asp

posted @ 2006-10-18 22:51  TerryLee  阅读(2896)  评论(2编辑  收藏  举报