`

JS-自动提示组件

阅读更多
注:源自Ajax实战

实现自动提示功能:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
        <title>Type Ahead</title>
        <%@ include file="/views/js/importjs.jsp" %>
        <script type="text/javascript">
            
            window.onload = function(){
                var elemSpan = document.createElement("span");
                elemSpan.id = "spanOutput";
                elemSpan.className = "spanTextDropdown";
                document.body.appendChild(elemSpan);
                
                /* 用来给文本框设置属性,用定制的对象给这个属性赋值,这样就能够在整个脚本中引用它来获取值,而不是使用全局变量来获取值 */
                document.getElementById('txtUserInput').obj = SetProperties(document.getElementById('txtUserInput'), $('txtUserValue'), 'TypeAheadAction-getContent.action', true, true, true, true, "No matching Data", false, null);
            };
            
            /**
             * 将属性分配给对象
             * @param {Object} xElem 要分配输入前提示功能的文本框
             * @param {Object} xHidden 用来保存值的hidden元素
             * @param {Object} xserverCode 服务器端URL
             * @param {Object} xignoreCase 搜索过程中忽略大小写?
             * @param {Object} xmatchAnywhere 字符串中匹配任意位置的文本?
             * @param {Object} xmatchTextBoxWidth 匹配文本框宽度?
             * @param {Object} xshowNoMatchMessage 显示无匹配消息?
             * @param {Object} xnoMatchingDataMessage 用于显示的消息
             * @param {Object} xuseTimeout 选项显示一段时间后是否隐藏?
             * @param {Object} xtheVisibleTime span保持打开的时间
             */
            function SetProperties(xElem, xHidden, xserverCode, xignoreCase, xmatchAnywhere, xmatchTextBoxWidth, xshowNoMatchMessage, xnoMatchingDataMessage, xuseTimeout, xtheVisibleTime){
            
                /**
                 * 对于xignoreCase,xmatchAnywhere的处理,没有在属性中保持布尔值,而是保存了等价的正则表达式
                 * 在正则表达式中使用i来忽略大小写,使用^来匹配字符串的开始位置
                 * 在这里设置正则表达式参数,而不是在每次调用函数时使用if语句,对我们来说更加容易些
                 */
                var props = {
                    elem: xElem,
                    hidden: xHidden,
                    serverCode: xserverCode,
                    regExFlags: ((xignoreCase) ? "i" : ""),
                    regExAny: ((xmatchAnywhere) ? "" : "^"),
                    matchAnywhere: xmatchAnywhere,
                    matchTextBoxWidth: xmatchTextBoxWidth,
                    theVisibleTime: xtheVisibleTime,
                    showNoMatchMessage: xshowNoMatchMessage,
                    noMatchingDataMessage: xnoMatchingDataMessage,
                    useTimeout: xuseTimeout
                };
                /* 将事件处理函数分配给文本框 */
                AddHandler(xElem);
                return props;
            }
            /**
             * 附加事件处理函数,监听用户的输入、是否离开了文本框
             */
            function AddHandler(objText){
                /* 键盘按键释放 */
                objText.onkeyup = GiveOptions;
                objText.onblur = function(){
                    if (this.obj.useTimeout) 
                        StartTimeout();
                }
                
                /* Opera浏览器出发onkeyup事件处理函数的方式与其他浏览器不同。
                 * 当触发onkeyup事件时,Opera不会再包括当前按键的文本框中显示值
                 * 我们为Opera添加onkeypress事件处理函数纠正了这个问题
                 */
                if (Browser.isOpera) {
                    objText.onkeypress = GiveOptions;
                }
            }
            
            
            var arrOptions = new Array();/* 从服务器查询中获取的所有可用选项 */
            var strLastValue = "";/* 文本框中包含的最后的字符串 */
            var bMadeRequest;/* 请求是否已经发送到服务器,而不必持续发送附加的请求,该标志正对快速打字员,这样我们就不必操心使用像Google那样的超时设置了 */
            var theTextBox;/* 保存对拥有焦点的文本框的引用 */
            var objLastActive;/* 最后激活的文本框 ,如果用户切换了文本框,这个变量将用来确定数据集是否需要重新刷新。在一个拥有多个文本框的窗口中实现这个解决方案,就需要知道哪一个文本框拥有焦点 */
            var currentValueSelected = -1;/* 作用于选择列表的selectedIndex类似,-1表示没有选项被选中 */
            var bNoResults = false;/* 是否有结果?这样我们就不必费心去试图找到任何结果了 */
            var isTiming = false;/* 允许确定页面上是否运行了一个定时器,如果在一段时间内没有操作选项列表,那么运行的这个定时器会将选项列表从用户的视线中隐藏起来 */
            
			/**
             * 检测用户按键 
             * @param {Object} e
             */
            function GiveOptions(e){
            
                /* 按下键的键编码 */
                var intKey = -1;
                /* 检测用户按键 */
				if (window.event) {
                    intKey = event.keyCode;
                    theTextBox = event.srcElement;
                }
                else {
                    intKey = e.which;
                    theTextBox = e.target;
                }
                
                /* 重置定时器 */
                if (theTextBox.obj.useTimeout) {
                    if (isTiming) {
						EraseTimeout();/* 取消定时器 */
					}
					/* 重启定时器 */
                    StartTimeout();
                }
                
                /* 确定是否存在文本 ,如果文本框不包含文本,隐藏下拉列表*/
                if (theTextBox.value.length == 0 && !Browser.isOpera) {
                    arrOptions = new Array();
                    HideTheBox();
                    strLastValue = "";
                    return false;
                }
                
                /* 在检测回车键、箭头键之前,需要验证当前激活的文本框是否是最后激活的文本框 */
                if (objLastActive == theTextBox) {
                    if (intKey == 13) {/* 回车键 */
                        GrabHighlighted();
                        theTextBox.blur();
                        return false;
                    }
                    else if (intKey == 38) {
                        MoveHighlight(-1);
                        return false;
                    }
                    else if (intKey == 40) {
                        MoveHighlight(1);
                        return false;
                    }
                }
				
				/* 处理按键操作
				 * 使用脚本缓存的机制,来限制回送喝减少服务器的副段
				 * 我们执行一些检查来查看是否得到了新的结果
				 */
				/* 访问服务器获取值 ,如果这些检查中的任何一个通过了,就需要检查服务器以获取数据*/
                if (objLastActive != theTextBox/* 确定最后激活的文本框是否是当前拥有焦点的文本框 */ 
					|| theTextBox.value.indexOf(strLastValue) != 0 /* 检查文本框中输入的文本与上一次相同,只是在末尾处附加了一些内容 */
					|| ((arrOptions.length == 0 || arrOptions.length == 15) && !bNoResults)/* 如果没有结果,或者结果集包含了15个或者更少的元素 */ 
               		|| (theTextBox.value.length <= strLastValue.length)/* 确保当前的长度大于最后的长度 */) {
                    objLastActive = theTextBox;
                    bMadeRequest = true;
                    TypeAhead(theTextBox.value);
                }
                else if (!bMadeRequest) {/* 使用已经从服务器获取的列表 */
                    BuildList(theTextBox.value);
                }
				
				/* 保存用户输入 到“最近使用” */
                strLastValue = theTextBox.value;
            }
            
            /* 发送请求道服务器 */
            function TypeAhead(xStrText){
                var strParams = "q=" + xStrText + "&where=" + theTextBox.obj.matchAnywhere;
                new Sky.Ajax({
                	url: theTextBox.obj.serverCode,
                	params: strParams,
                	onload: BuildChoices,
                	method: "POST"
                });
            }
            
            /* 将resopnseText属性转换为数组 */
            function BuildChoices(data){
                eval(data);
                BuildList(strLastValue);
                bMadeRequest = false;/* 通知脚本的其他部分发送到服务器的请求已经完成 */
            }
            
            /* 将结果格式化为可显示的格式 */
            function BuildList(theText){
                SetElementPosition(theTextBox);/* 设置元素的位置,需要将span元素动态定位为直接位于实现输入前提示功能的文本框的底部 */
                var theMatches = MakeMatches(theText);/* 格式化匹配的文本 */
                theMatches = theMatches.join("");
                if (theMatches.length > 0) {/* 显示结果 */
                    document.getElementById("spanOutput").innerHTML = theMatches;
                    document.getElementById("OptionsList_0").className = "spanHighElement";
                    currentValueSelected = 0;
                    bNoResults = false;
                }
                else {/* 显示没有匹配 */
                    currentValueSelected = -1;
                    bNoResults = true;
                    if (theTextBox.obj.showNoMatchMessage) 
                        document.getElementById("spanOutput").innerHTML = "<span class='noMatchData'>" +
                        theTextBox.obj.noMatchingDataMessage +
                        "</span>";
                    else 
                        HideTheBox();
                }
            }
            
            /* 动态查找未定位元素的位置 */
            function SetElementPosition(theTextBoxInt){
                var selectedPosX = 0;
                var selectedPosY = 0;
                var theElement = theTextBoxInt;
                if (!theElement) 
                    return;
                var theElemHeight = theElement.offsetHeight;
                var theElemWidth = theElement.offsetWidth;
				/* 遍历文档树,获取元素想对于其父节点在X,Y方向上的位置。通过遍历每一个已定位的父节点,增加对应于父节点位置的偏移量,就能够得到元素的准确位置 */
                while (theElement != null) {
                    selectedPosX += theElement.offsetLeft;
                    selectedPosY += theElement.offsetTop;
                    theElement = theElement.offsetParent;
                }
				
				
                xPosElement = document.getElementById("spanOutput");
                xPosElement.style.left = selectedPosX;
                if (theTextBoxInt.obj.matchTextBoxWidth) {/* 匹配文本框的宽度 */
					xPosElement.style.width = theElemWidth;
				}
                xPosElement.style.top = selectedPosY + theElemHeight;
                xPosElement.style.display = "block";/* 显示下拉列表 */
                if (theTextBoxInt.obj.useTimeout) {
                    xPosElement.onmouseout = StartTimeout;
                    xPosElement.onmouseover = EraseTimeout;
                }
                else {
                    xPosElement.onmouseout = null;
                    xPosElement.onmouseover = null;
                }
            }
            
            /* 使用正则来限制结果数量 */
            var countForId = 0;
            function MakeMatches(xCompareStr){
                countForId = 0;
                var matchArray = new Array();
                var regExp = new RegExp(theTextBox.obj.regExAny + xCompareStr, theTextBox.obj.regExFlags);
                for (i = 0; i < arrOptions.length; i++) {
                    var theMatch = arrOptions[i][0].match(regExp);
                    if (theMatch) {
                        matchArray[matchArray.length] = CreateUnderline(arrOptions[i][0], xCompareStr, i);
                    }
                }
                return matchArray;
            }
            
            
            /* 操作字符串 */ 
            var undeStart = "<span class='spanMatchText'>";/* span的起始标签 */
            var undeEnd = "</span>";/* span的结束标签 */
			/* 容器,提供背景及确定单元格是否被点击  */
            var selectSpanStart = "<span style='width:100%;display:block;' class='spanNormalElement' onmouseover='SetHighColor(this)' ";
            var selectSpanEnd = "</span>";
            function CreateUnderline(xStr, xTextMatch, xVal){
                selectSpanMid = "onclick='SetText(" + xVal + ")'" +
                	"id='OptionsList_" + countForId + "' theArrayNumber='" + xVal + "'>";
                var regExp = new RegExp(theTextBox.obj.regExAny + xTextMatch, theTextBox.obj.regExFlags);
                var aStart = xStr.search(regExp);
                var matchedText = xStr.substring(aStart, aStart + xTextMatch.length);
                countForId++;
                return selectSpanStart + selectSpanMid + xStr.replace(regExp, undeStart + matchedText + undeEnd) + selectSpanEnd;
            }
            
            /* 高亮 */
            function MoveHighlight(xDir){
                if (currentValueSelected >= 0) {
                    newValue = parseInt(currentValueSelected) + parseInt(xDir);
                    if (newValue > -1 && newValue < countForId) {
                        currentValueSelected = newValue;
                        SetHighColor(null);
                    }
                }
            }
            
            function SetHighColor(theTextBox){
                if (theTextBox) {
                    currentValueSelected = theTextBox.id.slice(theTextBox.id.indexOf("_") + 1, theTextBox.id.length);
                }
                for (i = 0; i < countForId; i++) {
                    document.getElementById('OptionsList_' + i).className = 'spanNormalElement';
                }
                document.getElementById('OptionsList_' +
                currentValueSelected).className = 'spanHighElement';
            }
            
            /* 处理箭头、鼠标点击事件 */
            function SetText(xVal){
                theTextBox.value = arrOptions[xVal][0]; //set text value
                theTextBox.obj.hidden.value = arrOptions[xVal][1];
                document.getElementById("spanOutput").style.display = "none";
                currentValueSelected = -1; //remove the selected index
            }
            
			/* 获取选中条目的文本和值 */
            function GrabHighlighted(){
                if (currentValueSelected >= 0) {
                    xVal = document.getElementById("OptionsList_" + currentValueSelected).getAttribute("theArrayNumber");
                    SetText(xVal);
                    HideTheBox();
                }
            }
            
            function HideTheBox(){
                document.getElementById("spanOutput").style.display = "none";
                currentValueSelected = -1;
                EraseTimeout();
            }
            
           
            function EraseTimeout(){
                clearTimeout(isTiming);
                isTiming = false;
            }
            
            function StartTimeout(){
                isTiming = setTimeout("HideTheBox()", theTextBox.obj.theVisibleTime);
            }
        </script>
        <style type="text/css">
            
            span.spanTextDropdown {
                position: absolute;
                top: 0px;
                left: 0px;
                width: 150px;
                z-index: 101;
                background-color: #C0C0C0;
                border: 1px solid #000000;
                padding-left: 2px;
                overflow: visible;
                display: none;
            }
            
            span.spanMatchText {
                text-decoration: underline;
                font-weight: bold;
            }
            
            span.spanNormalElement {
                background: #C0C0C0;
            }
            
            span.spanHighElement {
                background: #000040;
                color: white;
                cursor: pointer;
            }
            
            span.noMatchData {
                font-weight: bold;
                color: #0000FF;
            }
        </style>
    </head>
    <body>
 AutoComplete Text Box:<input type="text" id="txtUserInput"/><input type="hidden" id="txtUserValue" ID="hidden1" />
    </body>
</html>


后台测试页面:
public class TypeAheadPage extends Page {

	/**
	 * 从客户端接收数值,并且将查询得到的数据处理成字符串形式的JavaScript数组, 
	 * 这个心创建的数组返回给客户端,并在客户端执行
	 */
	public void getData() {

		// 需要搜索的字符串
		String strQuery = Request.getString("q");

		// 在一个单词的中间位置查找结果?
		boolean isAny = "true".equalsIgnoreCase(Request.getString("where"));

		// 查询出的搜索结果集
		DataTable dtQuestions = queryDataTable(isAny, strQuery);

		// 创建JavaScript数组
		StringBuffer strJSArr = new StringBuffer("arrOptions = new Array(");
		boolean isFirstRecord = true;

		// 循环结果
		for (DataRow row : dtQuestions.getDataRows()) {
			if (!isFirstRecord) {
				strJSArr.append(",");
				isFirstRecord = false;
			}
			// 创建包含产品名称,Id的二维数组
			strJSArr.append("['").append(row.getString("ProductName")).append(
					"','").append(row.getString("Productid")).append("']");
		}

		strJSArr.append(");");

		// 将字符串写入响应流
		Response.write(strJSArr.toString());
	}

	/**
	 * 查询出的搜索结果集
	 * 
	 * @param isAny 在一个单词的中间位置查找结果?
	 * @param strQuery 需要搜索的字符串
	 * @return 包含搜索字符串的结果集
	 */
	private DataTable queryDataTable(boolean isAny, String strQuery) {

		String strAny = isAny ? "%" : "";

		// 创建查询SQL语句,限制只允许搜索返回15条记录
		String strSql = "SELECT top 15 ProductName, ProductId FROM Products "
				+ "WHERE ProductName LIKE '" + strAny + strQuery
				+ "%' ORDER BY ProductName";

		return new QueryBuilder(strSql).executeDataTable();
	}

}



重构:
var logger = {
            	msgs: '',
            	append: function(msg){
            		logger.msgs += msg;
            	},
            	show: function() {
            		alert(logger.msgs);
            	}
            };     

/**
 * @class TextSuggest 自动提示
 */
TextSuggest = Base.extend({

    /**
     * @method constructor 构造器,根据配置初始化自动提示,并对需要自动提示的文本框注入键盘监听事件
     * @param {String} anId 自动提示对应的文本框id
     * @param {String} url 服务器端url
     * @param {Object} options 配置参数对象
     */
    constructor: function(anId, url, options){
        /**
         * @property {String} id 自动提示对应的文本框id
         */
        this.id = anId;
        
        /**
         * @property {Element} id 自动提示对应的文本框
         */
        this.textInput = $(anId);
        
        /**
         * @property {Array} suggestions {text: strText, value: strValue}格式数据
         */
        this.suggestions = [];
        
        /**
         * @property {String} url 服务器端url
         */
        this.url = url;
        
        this.setOptions(options);
        
        this.injectSuggestBehavior();
    },
    
    /**
     * @method setOptions 初始化配置参数
     * @param {Object} options 参数
     */
    setOptions: function(options){
        /**
         * @property options 配置参数
         */
        this.options = Sky.apply({
            suggestDivClassName: 'suggestDiv',
            suggestionClassName: 'suggestion',
            matchClassName: 'spanMatchText',
            matchTextWidth: true,
            selectionColor: '#b1c09c',
            matchAnywhere: false,
            ignoreCase: false,
            count: 10
        }, options || {});
    },
    
    /**
     * @method injectSuggestBehavior 注入Suggest行为:
     * 		为自动提示文本框添加对应的隐藏字段(其id命名为文本框id+"_hidden"),创建自动提示DIV,并对文本框添加键盘监听
     */
    injectSuggestBehavior: function(){
    
        logger.append("injectSuggest");
        if (Browser.isIE) 
            this.textInput.autocomplete = "off";
        
        /* 添加键盘监听 */
        new TextSuggestKeyHandler(this);
        
        /* 添加隐藏字段 */
        var hiddenInput = document.createElement("input");
        hiddenInput.name = this.id + '_hidden';
        hiddenInput.id = this.id + '_hidden';
        hiddenInput.type = "hidden";
        this.textInput.parentNode.appendChild(hiddenInput);
		
		/**
		 * @property {Element} inputHidden 隐藏字段
		 */
		this.hiddenInput = hiddenInput;
        
        this.createSuggestionsDiv();
        logger.append("end injectSuggest");
    },
	
    /**
     * @method sendRequestForSuggestions 请求数据:如果正在请求,加入等待队列
     */
    sendRequestForSuggestions: function(){
    
        logger.append("request");
        
        /**
         * @property {Boolean} handlingRequest 正在处理请求?
         */
        if (this.handlingRequest) {
            /**
             * @property {Boolean} pendingRequest 有等待的请求?
             */
            this.pendingRequest = true;
            return;
        }
        
        this.handlingRequest = true;
        this.callAjaxEngine();
        
        logger.append("end request");
    },
	
    /**
     * @method callAjaxEngine 发送请求
     */
    callAjaxEngine: function(){
    
        logger.append("callAjaxEngine");
        
        var callParms = [];
        callParms.push(this.id + '_request');
        callParms.push('id=' + this.id);
        callParms.push('count=' + this.options.count);
        callParms.push('query=' + this.lastRequestString);
        callParms.push('match_anywhere=' + this.options.matchAnywhere);
        callParms.push('ignore_case=' + this.options.ignoreCase);
        
        var additionalParms = this.options.requestParameters || [];
        for (var i = 0; i < additionalParms.length; i++) 
            callParms.push(additionalParms[i]);
        
        var instance = this;
       /* new Sky.Ajax({
            url: this.url,
            params: callParms.join("&"),
            onload: instance.ajaxUpdate.bind(instance),
            method: "POST"
        });*/
        DWRActionUtil.executeaction(this.url,{query:this.lastRequestString},instance.ajaxUpdate.bind(instance));
        logger.append("end callAjaxEngine");
    },
	
	/**
	 * @method ajaxUpdate 处理请求响应:没有匹配项,隐藏下拉列表,清空隐藏值。
	 * 		否则,更新并显示下拉列表,设置第一个下拉项为选中状态。如果有等待的请求,发送请求(注:只处理最后输入框中的请求)
	 * @param {String} responseText 请求响应的文本
	 */
    ajaxUpdate: function(responseText){
    
        this.createSuggestions(responseText.responseText);
        
        if (this.suggestions.length == 0) {
            this.suggestionsDiv.hide();
            this.hiddenInput.value = "";
        }
        else {
            this.updateSuggestionsDiv();
            this.showSuggestions();
            this.updateSelection(0);
        }
        
        this.handlingRequest = false;
        
        if (this.pendingRequest) {
            this.pendingRequest = false;
            this.lastRequestString = this.textInput.value;
            this.sendRequestForSuggestions();
        }
    },
	
	/**
	 * @method createSuggestions 解析响应文本,并设置匹配项,匹配项为{text: strText, value: strValue}格式数据
	 * @param {String} responseText 响应文本
	 */
    createSuggestions: function(responseText){
        this.suggestions = [];
        eval(responseText);
        
        if(!arrOptions) return;
        
        for (var i = 0; i < arrOptions.length; i++) {
            var strText = arrOptions[i][0];
            var strValue = arrOptions[i][1];
            this.suggestions.push({
                text: strText,
                value: strValue
            });
        }
    },
	
	/**
	 * @method getElementContent // TODO 这个方法需移除
	 * @param {Object} element
	 */
    getElementContent: function(element){
        return element.firstChild.data;
    },
	
	/**
	 * @method updateSuggestionsDiv 更新下拉列表
	 */
    updateSuggestionsDiv: function(){
        this.suggestionsDiv.innerHTML = "";
        var suggestLines = this.createSuggestionSpans();
        for (var i = 0; i < suggestLines.length; i++) {
            this.suggestionsDiv.appendChild(suggestLines[i]);
            logger.append("adding div " + suggestLines[i]);
        }
    },
    
	/**
	 * @method createSuggestionSpans 创建下拉列表选项
	 */
    createSuggestionSpans: function(){
        var regExpFlags = this.options.ignoreCase ? "i" : "";
        var startRegExp = this.options.matchAnywhere ? "" : "^";
        
        var regExp = new RegExp(startRegExp + this.lastRequestString, regExpFlags);
        
        var suggestionSpans = [];
        for (var i = 0; i < this.suggestions.length; i++) {
            suggestionSpans.push(this.createSuggestionSpan(i, regExp))
            logger.append("suggestion: " + this.suggestions[i].text);
        }
        return suggestionSpans;
    },
    
    addBeforeSpan: function() {
    	return null;
    },
    
	/**
	 * @method createSuggestionSpan 创建单个下拉选项
	 * @param {Number} index 下拉选项索引
	 * @param {RegExp} regExp
	 */
    createSuggestionSpan: function(n, regExp){
        var suggestion = this.suggestions[n];
        
        var suggestionSpan = document.createElement("span");
        suggestionSpan.className = this.options.suggestionClassName;
        suggestionSpan.style.width = '100%';
        suggestionSpan.style.display = 'block';
        suggestionSpan.id = this.id + "_" + n;
        suggestionSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
        suggestionSpan.onclick = this.itemClickHandler.bindAsEventListener(this);
        
        var textValues = this.splitTextValues(suggestion.text, this.lastRequestString.length, regExp);
        
        var textMatchSpan = document.createElement("span");
        textMatchSpan.id = this.id + "_match_" + n;
        textMatchSpan.className = this.options.matchClassName;
        textMatchSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
        textMatchSpan.onclick = this.itemClickHandler.bindAsEventListener(this);
        
        textMatchSpan.appendChild(document.createTextNode(textValues.mid));
        
        var beforeSpan = this.addBeforeSpan();
        if(beforeSpan){
        	suggestionSpan.appendChild(document.createTextNode(beforeSpan));
        }
        suggestionSpan.appendChild(document.createTextNode(textValues.start));
        suggestionSpan.appendChild(textMatchSpan);
        suggestionSpan.appendChild(document.createTextNode(textValues.end));
        
        return suggestionSpan;
    },
	
	/**
	 * @method updateSelection 更新第n个选项为选中,同时改变背景色
	 * @param {Object} n
	 */
    updateSelection: function(n){
        this.selectedIndex = n;
        for (var i = 0; i < this.suggestions.length; i++) {
            var span = $(this.id + "_" + i);
            if (i != this.selectedIndex) 
                span.style.backgroundColor = "";
            else 
                span.style.backgroundColor = this.options.selectionColor;
        }
    },
    
    /**
     * @method handleTextInput 文本框输入改变处理器
     */
    handleTextInput: function(){
        var previousRequest = this.lastRequestString;
        
        this.setLastRequestString();
        
        logger.append("text input: " + previousRequest + " -> " + this.lastRequestString);
        if (this.lastRequestString == "") {
            this.suggestionsDiv.hide();
        }
        else if (this.lastRequestString != previousRequest) {
            this.sendRequestForSuggestions();
        }
    },
    
    setLastRequestString: function() {
    	/**
         * @property {String} lastRequestString 最后输入的字符串
         */
        this.lastRequestString = this.textInput.value;
        
    },
    
	/**
	 * @method moveSelectionUp 设置当前选项的上一个选项为选中状态
	 */
    moveSelectionUp: function(){
        if (this.selectedIndex == 0) {
        	this.selectedIndex = this.suggestions.length;
        }
        this.updateSelection(this.selectedIndex - 1);
    },
    
	/**
	 * @method moveSelectionDown 设置当前选项的下一个选项为选中状态
	 */
    moveSelectionDown: function(){
        if (this.selectedIndex == (this.suggestions.length - 1)) {
        	this.selectedIndex = -1;
        }
        this.updateSelection(this.selectedIndex + 1);
    },
    
    /**
     * @method createSuggestionsDiv 创建自动提示div
     */
    createSuggestionsDiv: function(){
        /**
         * @property {Element} suggestionsDiv 自动提示div元素
         */
        this.suggestionsDiv = document.createElement("div");
        this.suggestionsDiv.className = this.options.suggestDivClassName;
        
        var divStyle = this.suggestionsDiv.style;
        divStyle.position = 'absolute';
        divStyle.zIndex = 101;
        divStyle.display = "none";
        
        this.textInput.parentNode.appendChild(this.suggestionsDiv);
    },
	
	/**
	 * @method setInputFromSelection 设置表单文本及隐藏域的值
	 */
    setInputFromSelection: function(){
        var suggestion = this.suggestions[this.selectedIndex];
        
        this.textInput.value = $(this.id + "_" + this.selectedIndex).innerText;//suggestion.text;
        this.hiddenInput.value = suggestion.value;
        this.suggestionsDiv.hide();
    },
    
	/**
	 * @method showSuggestions 显示下拉列表
	 */
    showSuggestions: function(){
        var divStyle = this.suggestionsDiv.style;
        if (divStyle.display == '') 
            return;
        this.positionSuggestionsDiv();
        divStyle.display = '';
    },
    
	/**
	 * @method positionSuggestionsDiv 设置下拉列表的位置、大小
	 */
    positionSuggestionsDiv: function(){
        var textPos = this.textInput.getPositionEx();
        var divStyle = this.suggestionsDiv.style;
        divStyle.top = (textPos.y + this.textInput.offsetHeight) + "px";
        divStyle.left = textPos.x + "px";
        logger.append("position suggest div: " + divStyle.left + "," + divStyle.top);
        if (this.options.matchTextWidth) 
            divStyle.width = (this.textInput.offsetWidth) + "px";
    },
    
	/**
	 * @method mouseoverHandler 鼠标移过事件处理器
	 * @param {Event} e
	 */
    mouseoverHandler: function(e){
        var src = e.srcElement ? e.srcElement : e.target;
        var index = parseInt(src.id.substring(src.id.lastIndexOf('_') + 1));
        this.updateSelection(index);
    },
    
	/**
	 * @method itemClickHandler 下拉选项点击事件处理器
	 * @param {Object} e
	 */
    itemClickHandler: function(e){
        this.mouseoverHandler(e);
        this.suggestionsDiv.hide();
        this.textInput.focus();
    },
    
	/**
	 * @method splitTextValues 将为本拆开为开始、中间、结尾三部分,其中中间部分为匹配文本 
	 * @param {String} text 需要匹配的文本
	 * @param {Number} len 匹配文本的长度
	 * @param {RegExp} regExp 匹配文本正则
	 * @return {Object} {start: startText, mid: matchText, end: endText}格式的对象
	 */
    splitTextValues: function(text, len, regExp){
        var startPos = text.search(regExp);
        var matchText = text.substring(startPos, startPos + len);
        var startText = startPos == 0 ? "" : text.substring(0, startPos);
        var endText = text.substring(startPos + len);
        return {
            start: startText,
            mid: matchText,
            end: endText
        };
    }
});

/**
 * @class TextSuggestKeyHandler 自动提示文本监听事件
 */
TextSuggestKeyHandler = Base.extend({
    /**
     * @method constructor 构造器
     * @param {TextSuggest} textSuggest 自动提示组件
     */
    constructor: function(textSuggest){
        /**
         * @property {TextSuggest} textSuggest 自动提示组件
         */
        this.textSuggest = textSuggest;
        
        /**
         * @property {Element} input 自动提示文本框
         */
        this.input = this.textSuggest.textInput;
        
        this.addKeyHandling();
    },
    /**
     * @method addKeyHandling 添加事件处理
     */
    addKeyHandling: function(){
        addEvent(this.input, "keyup", this.keyupHandler.bindAsEventListener(this));
        addEvent(this.input, "keydown", this.keydownHandler.bindAsEventListener(this));
        addEvent(this.input, "blur", this.onblurHandler.bindAsEventListener(this));
        if (Sky.isOpera) 
            addEvent(this.input, "keypress", this.keyupHandler.bindAsEventListener(this));
    },
    /**
     * @method keydownHandler 键盘按下处理器
     * @param {Event} e 事件,为自动注入
     */
    keydownHandler: function(e){
        var upArrow = 38;
        var downArrow = 40;
        
        if (e.keyCode == upArrow) {
            this.textSuggest.moveSelectionUp();
            setTimeout(this.moveCaretToEnd.bind(this), 1);
        }
        else 
            if (e.keyCode == downArrow) 
                this.textSuggest.moveSelectionDown();
    },
    /**
     * @method keyupHandler 键盘弹起处理器
     * @param {Event} e 事件,为自动注入
     */
    keyupHandler: function(e){
        if (this.input.length == 0 && !Sky.isOpera) 
            this.textSuggest.hideSuggestions();
        
        if (!this.handledSpecialKeys(e)) 
            this.textSuggest.handleTextInput();
    },
    /**
     * @method handledSpecialKeys 处理特殊按键
     * @param {Event} e 事件,为自动注入
     */
    handledSpecialKeys: function(e){
        var enterKey = 13;
        var upArrow = 38;
        var downArrow = 40;
        
        if (e.keyCode == upArrow || e.keyCode == downArrow) {
            return true;
        }
        else 
            if (e.keyCode == enterKey) {
                this.textSuggest.setInputFromSelection();
                return true;
            }
        
        return false;
    },
    
    moveCaretToEnd: function(){
        var pos = this.input.value.length;
        if (this.input.setSelectionRange) {
            this.input.setSelectionRange(pos, pos);
        }
        else 
            if (this.input.createTextRange) {
                var m = this.input.createTextRange();
                m.moveStart('character', pos);
                m.collapse();
                m.select();
            }
    },
    
    onblurHandler: function(e){
        if (this.textSuggest.suggestionsDiv.style.display == '') 
            this.textSuggest.setInputFromSelection();
        this.textSuggest.suggestionsDiv.hide();
    }
});





继承测试,以适应业务需要:
Array.prototype.containsRoute = function(route) {
	for(var i=0;i<this.length;i++) {
		if(this[i].text == route.text && this[i].value == route.value){
			return true;
		}
	}
	return false;
}

RouteTextSuggest = TextSuggest.extend({
	allRoutes: [],
	setLastRequestString: function() {
	
        var routes = this.textInput.value.split("-");
        if(routes.length >= 2) {
	        this.lastRequestString = routes[routes.length-2] + "-" + routes[routes.length-1];
        } else {
        	this.lastRequestString = this.textInput.value;
        }
    },
    addBeforeSpan: function(){
    	
    	//return this.textInput.value.replace(this.lastRequestString,"");
    	return this.textInput.value.substring(0,this.textInput.value.lastIndexOf(this.lastRequestString));
    },
    getRoutes: function(){
    	var routeIds = [];
    	
    	var routes = this.textInput.value.split("-");
    	for(var i=0;i<routes.length-1;i++){
    		var route = routes[i] + "-" + routes[i+1];
    		for(var j=0;j<this.allRoutes.length;j++){
    			if(this.allRoutes[j].text==route){
    				routeIds.push(this.allRoutes[j].value);
    			}
    		}
    	}
    	
    	return routeIds;
    },
    createSuggestions: function(responseText){
    	this.base(responseText);
    	for (var i = 0; i < this.suggestions.length; i++) {
            if(!this.allRoutes.containsRoute(this.suggestions[i])){
            	this.allRoutes.push(this.suggestions[i]);
            }
        }
    }
});


页面测试:
new RouteTextSuggest(id, "RouteAction-getRoutes")


后台方法重构:
/**
 * 自动提示组件生成数据获取对应key,value值的接口
 * @author Darkness
 * Create on Aug 19, 2010 4:56:12 PM
 * @version 1.0
 */
public interface ITextSuggest {
	
	/**
	 * 获取id
	 */
	String getId(Object entry);

	/**
	 * 获取name
	 * @param entry
	 * @return
	 */
	String getName(Object entry);
}

/**
	 * 初始化TextSuggest数据
	 * @param list
	 * @param textSuggest
	 */
	protected void initTextSuggest(List list, ITextSuggest textSuggest) {
		// 创建JavaScript数组
		StringBuffer strJSArr = new StringBuffer("arrOptions = new Array(");
		boolean isFirstRecord = true;

		for (int i = 0; i < list.size(); i++) {
			Object entry = list.get(i);

			if (!isFirstRecord) {
				strJSArr.append(",");
			}
			// 创建包含产品名称,Id的二维数组
			strJSArr.append("['").append(textSuggest.getName(entry)).append("','")
					.append(textSuggest.getId(entry)).append("']");
			
			isFirstRecord = false;
		}

		strJSArr.append(");");

		// 将字符串写入响应流
		responseText = strJSArr.toString();
	}


重构后的方法:
/**
	 * 获取自动提示数组
	 */
	public void getRoutes() {

		// 查询出的搜索结果集
		List<RouteEntry> list = routeEntryService.getRouteEntryList(query);

		initTextSuggest(list, new ITextSuggest() {

			public String getId(Object entry) {
				return ((RouteEntry) entry).getId().toString();
			}

			public String getName(Object entry) {
				return ((RouteEntry) entry).getName();
			}
		});
	}


样式:
.suggestDiv {
                position: absolute;
                top: 0px;
                left: 0px;
                width: 150px;
                z-index: 101;
                background-color: #C0C0C0;
                border: 1px solid #000000;
                padding-left: 2px;
                overflow: visible;
            }
            
            span.spanMatchText {
                text-decoration: underline;
                font-weight: bold;
            }
            
            .suggestion {
                background: #C0C0C0;
            }
            
            span.spanHighElement {
                background: #000040;
                color: white;
                cursor: pointer;
            }
            
            span.noMatchData {
                font-weight: bold;
                color: #0000FF;
            }



jquery版本:
var logger = {
	msgs: '',
	append: function(msg){
		logger.msgs += msg;
	},
	show: function() {
		alert(logger.msgs);
	}
};     

/**
 * @class TextSuggest 自动提示
 */
TextSuggest = Base.extend({

    /**
     * @method constructor 构造器,根据配置初始化自动提示,并对需要自动提示的文本框注入键盘监听事件
     * @param {String} anId 自动提示对应的文本框id
     * @param {String} url 服务器端url
     * @param {Object} options 配置参数对象
     */
    constructor: function(anId, url, options){
        /**
         * @property {String} id 自动提示对应的文本框id
         */
        this.id = anId;
        
        /**
         * @property {Element} id 自动提示对应的文本框
         */
        this.textInput = findElement(anId);
        
        /**
         * @property {Array} suggestions {text: strText, value: strValue}格式数据
         */
        this.suggestions = [];
        
        /**
         * @property {String} url 服务器端url
         */
        this.url = url;
        
        this.setOptions(options);
        
        this.injectSuggestBehavior();
    },
    
    /**
     * @method setOptions 初始化配置参数
     * @param {Object} options 参数
     */
    setOptions: function(options){
        /**
         * @property options 配置参数
         */
        this.options = $.extend({
            suggestDivClassName: 'suggestDiv',
            suggestionClassName: 'suggestion',
            matchClassName: 'spanMatchText',
            matchTextWidth: true,
            selectionColor: '#b1c09c',
            matchAnywhere: false,
            ignoreCase: false,
            count: 10
        }, options || {});
    },
    
    /**
     * @method injectSuggestBehavior 注入Suggest行为:
     * 		为自动提示文本框添加对应的隐藏字段(其id命名为文本框id+"_hidden"),创建自动提示DIV,并对文本框添加键盘监听
     */
    injectSuggestBehavior: function(){
    
        logger.append("injectSuggest");
        if ($.browser.isie) 
            this.textInput.autocomplete = "off";
        
        /* 添加键盘监听 */
        new TextSuggestKeyHandler(this);
        
        /* 添加隐藏字段 */
        var hiddenInput = document.createElement("input");
        hiddenInput.name = this.id + '_hidden';
        hiddenInput.id = this.id + '_hidden';
        hiddenInput.type = "hidden";
        this.textInput.parentNode.appendChild(hiddenInput);
		
		/**
		 * @property {Element} inputHidden 隐藏字段
		 */
		this.hiddenInput = hiddenInput;
        
        this.createSuggestionsDiv();
        logger.append("end injectSuggest");
    },
	
    /**
     * @method sendRequestForSuggestions 请求数据:如果正在请求,加入等待队列
     */
    sendRequestForSuggestions: function(){
    
        logger.append("request");
        
        /**
         * @property {Boolean} handlingRequest 正在处理请求?
         */
        if (this.handlingRequest) {
            /**
             * @property {Boolean} pendingRequest 有等待的请求?
             */
            this.pendingRequest = true;
            return;
        }
        
        this.handlingRequest = true;
        this.callAjaxEngine();
        
        logger.append("end request");
    },
	
    /**
     * @method callAjaxEngine 发送请求
     */
    callAjaxEngine: function(){
    
        logger.append("callAjaxEngine");
        
        var callParms = [];
        callParms.push(this.id + '_request');
        callParms.push('id=' + this.id);
        callParms.push('count=' + this.options.count);
        callParms.push('query=' + this.lastRequestString);
        callParms.push('match_anywhere=' + this.options.matchAnywhere);
        callParms.push('ignore_case=' + this.options.ignoreCase);
        
        var additionalParms = this.options.requestParameters || [];
        for (var i = 0; i < additionalParms.length; i++) 
            callParms.push(additionalParms[i]);
        
        var instance = this;
       /* new Sky.Ajax({
            url: this.url,
            params: callParms.join("&"),
            onload: instance.ajaxUpdate.bind(instance),
            method: "POST"
        });*/
        
        $.post(this.url,{query:this.lastRequestString},instance.ajaxUpdate.bind(instance));
        
        logger.append("end callAjaxEngine");
    },
	
	/**
	 * @method ajaxUpdate 处理请求响应:没有匹配项,隐藏下拉列表,清空隐藏值。
	 * 		否则,更新并显示下拉列表,设置第一个下拉项为选中状态。如果有等待的请求,发送请求(注:只处理最后输入框中的请求)
	 * @param {String} responseText 请求响应的文本
	 */
    ajaxUpdate: function(responseText){
    	console.log('xxxxxxxxxxxxxxxxxxxx')
        this.createSuggestions(responseText);
        
        if (this.suggestions.length == 0) {
            this.suggestionsDiv.hide();
            this.hiddenInput.value = "";
        }
        else {
            this.updateSuggestionsDiv();
            this.showSuggestions();
            this.updateSelection(0);
        }
        
        this.handlingRequest = false;
        
        if (this.pendingRequest) {
            this.pendingRequest = false;
            this.lastRequestString = this.textInput.value;
            this.sendRequestForSuggestions();
        }
    },
	
	/**
	 * @method createSuggestions 解析响应文本,并设置匹配项,匹配项为{text: strText, value: strValue}格式数据
	 * @param {String} responseText 响应文本
	 */
    createSuggestions: function(arrOptions){
        this.suggestions = [];
        //eval(responseText);
        
        //if(!arrOptions) return;
        
        for (var i = 0; i < arrOptions.length; i++) {
            var strText = arrOptions[i].name;
            var strValue = arrOptions[i].id;
            this.suggestions.push({
                text: strText,
                value: strValue,
                raw: arrOptions[i]
            });
        }
    },
	
	/**
	 * @method getElementContent // TODO 这个方法需移除
	 * @param {Object} element
	 */
    getElementContent: function(element){
        return element.firstChild.data;
    },
	
	/**
	 * @method updateSuggestionsDiv 更新下拉列表
	 */
    updateSuggestionsDiv: function(){
        this.suggestionsDiv.innerHTML = "";
        var suggestLines = this.createSuggestionSpans();
        for (var i = 0; i < suggestLines.length; i++) {
            this.suggestionsDiv.appendChild(suggestLines[i]);
            logger.append("adding div " + suggestLines[i]);
        }
    },
    
	/**
	 * @method createSuggestionSpans 创建下拉列表选项
	 */
    createSuggestionSpans: function(){
        var regExpFlags = this.options.ignoreCase ? "i" : "";
        var startRegExp = this.options.matchAnywhere ? "" : "^";
        
        var regExp = new RegExp(startRegExp + this.lastRequestString, regExpFlags);
        
        var suggestionSpans = [];
        for (var i = 0; i < this.suggestions.length; i++) {
            suggestionSpans.push(this.createSuggestionSpan(i, regExp))
            logger.append("suggestion: " + this.suggestions[i].text);
        }
        return suggestionSpans;
    },
    
    addBeforeSpan: function(n) {
    	var suggestion = this.suggestions[n];
    	var raw = suggestion.raw;
    	
    	var before = '';
    	var level = raw.level;
    	for(var i=1; i<level; i++) {
    		before += '--';
    	}
    	
    	return before;
    },
    
	/**
	 * @method createSuggestionSpan 创建单个下拉选项
	 * @param {Number} index 下拉选项索引
	 * @param {RegExp} regExp
	 */
    createSuggestionSpan: function(n, regExp){
        var suggestion = this.suggestions[n];
        
        var suggestionSpan = document.createElement("span");
        suggestionSpan.className = this.options.suggestionClassName;
        suggestionSpan.style.width = '100%';
        suggestionSpan.style.display = 'block';
        suggestionSpan.id = this.id + "_" + n;
        suggestionSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
        suggestionSpan.onclick = this.itemClickHandler.bindAsEventListener(this);
        
        var textValues = this.splitTextValues(suggestion.text, this.lastRequestString.length, regExp);
        
        var textMatchSpan = document.createElement("span");
        textMatchSpan.id = this.id + "_match_" + n;
        textMatchSpan.className = this.options.matchClassName;
        textMatchSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
        textMatchSpan.onclick = this.itemClickHandler.bindAsEventListener(this);
        
        textMatchSpan.appendChild(document.createTextNode(textValues.mid));
        
        var beforeSpan = this.addBeforeSpan(n);
        if(beforeSpan){
        	suggestionSpan.appendChild(document.createTextNode(beforeSpan));
        }
        suggestionSpan.appendChild(document.createTextNode(textValues.start));
        suggestionSpan.appendChild(textMatchSpan);
        suggestionSpan.appendChild(document.createTextNode(textValues.end));
        
        return suggestionSpan;
    },
	
	/**
	 * @method updateSelection 更新第n个选项为选中,同时改变背景色
	 * @param {Object} n
	 */
    updateSelection: function(n){
        this.selectedIndex = n;
        for (var i = 0; i < this.suggestions.length; i++) {
            var span = findElement( this.id + "_" + i);
            if (i != this.selectedIndex) 
                span.style.backgroundColor = "";
            else 
                span.style.backgroundColor = this.options.selectionColor;
        }
    },
    
    /**
     * @method handleTextInput 文本框输入改变处理器
     */
    handleTextInput: function(){
        var previousRequest = this.lastRequestString;
        
        this.setLastRequestString();
        
        logger.append("text input: " + previousRequest + " -> " + this.lastRequestString);
        if (this.lastRequestString == "") {
           $(this.suggestionsDiv).hide();
        }
        else if (this.lastRequestString != previousRequest) {
            this.sendRequestForSuggestions();
        }
    },
    
    setLastRequestString: function() {
    	/**
         * @property {String} lastRequestString 最后输入的字符串
         */
        this.lastRequestString = this.textInput.value;
        
    },
    
	/**
	 * @method moveSelectionUp 设置当前选项的上一个选项为选中状态
	 */
    moveSelectionUp: function(){
        if (this.selectedIndex == 0) {
        	this.selectedIndex = this.suggestions.length;
        }
        this.updateSelection(this.selectedIndex - 1);
    },
    
	/**
	 * @method moveSelectionDown 设置当前选项的下一个选项为选中状态
	 */
    moveSelectionDown: function(){
        if (this.selectedIndex == (this.suggestions.length - 1)) {
        	this.selectedIndex = -1;
        }
        this.updateSelection(this.selectedIndex + 1);
    },
    
    /**
     * @method createSuggestionsDiv 创建自动提示div
     */
    createSuggestionsDiv: function(){
        /**
         * @property {Element} suggestionsDiv 自动提示div元素
         */
        this.suggestionsDiv = document.createElement("div");
        this.suggestionsDiv.className = this.options.suggestDivClassName;
        
        var divStyle = this.suggestionsDiv.style;
        divStyle.position = 'absolute';
        divStyle.zIndex = 101;
        divStyle.display = "none";
        
        this.textInput.parentNode.appendChild(this.suggestionsDiv);
    },
	
	/**
	 * @method setInputFromSelection 设置表单文本及隐藏域的值
	 */
    setInputFromSelection: function(){
        var suggestion = this.suggestions[this.selectedIndex];
        
        this.textInput.value = suggestion.text;//findElement(this.id + "_" + this.selectedIndex).innerText;//suggestion.text;
        this.hiddenInput.value = suggestion.value;
        $(this.suggestionsDiv).hide();
    },
	/**
	 * @method showSuggestions 显示下拉列表
	 */
    showSuggestions: function(){
        var divStyle = this.suggestionsDiv.style;
        if (divStyle.display == '') 
            return;
        this.positionSuggestionsDiv();
        divStyle.display = '';
    },
    
	/**
	 * @method positionSuggestionsDiv 设置下拉列表的位置、大小
	 */
    positionSuggestionsDiv: function(){
        var textPos = $E.getPositionEx(this.textInput);
        var divStyle = this.suggestionsDiv.style;
        divStyle.top = (textPos.y + this.textInput.offsetHeight) + "px";
        divStyle.left = textPos.x + "px";
        logger.append("position suggest div: " + divStyle.left + "," + divStyle.top);
        if (this.options.matchTextWidth) 
            divStyle.width = (this.textInput.offsetWidth) + "px";
    },
    
	/**
	 * @method mouseoverHandler 鼠标移过事件处理器
	 * @param {Event} e
	 */
    mouseoverHandler: function(e){
        var src = e.srcElement ? e.srcElement : e.target;
        var index = parseInt(src.id.substring(src.id.lastIndexOf('_') + 1));
        this.updateSelection(index);
    },
    
	/**
	 * @method itemClickHandler 下拉选项点击事件处理器
	 * @param {Object} e
	 */
    itemClickHandler: function(e){
        this.mouseoverHandler(e);
        this.suggestionsDiv.hide();
        this.textInput.focus();
    },
    
	/**
	 * @method splitTextValues 将为本拆开为开始、中间、结尾三部分,其中中间部分为匹配文本 
	 * @param {String} text 需要匹配的文本
	 * @param {Number} len 匹配文本的长度
	 * @param {RegExp} regExp 匹配文本正则
	 * @return {Object} {start: startText, mid: matchText, end: endText}格式的对象
	 */
    splitTextValues: function(text, len, regExp){
        var startPos = text.search(regExp);
        var matchText = text.substring(startPos, startPos + len);
        var startText = startPos == 0 ? "" : text.substring(0, startPos);
        var endText = text.substring(startPos + len);
        return {
            start: startText,
            mid: matchText,
            end: endText
        };
    }
});

/**
 * @class TextSuggestKeyHandler 自动提示文本监听事件
 */
TextSuggestKeyHandler = Base.extend({
    /**
     * @method constructor 构造器
     * @param {TextSuggest} textSuggest 自动提示组件
     */
    constructor: function(textSuggest){
        /**
         * @property {TextSuggest} textSuggest 自动提示组件
         */
        this.textSuggest = textSuggest;
        
        /**
         * @property {Element} input 自动提示文本框
         */
        this.input = this.textSuggest.textInput;
        
        this.addKeyHandling();
    },
    /**
     * @method addKeyHandling 添加事件处理
     */
    addKeyHandling: function(){
        $(this.input).on("keyup", this.keyupHandler.bindAsEventListener(this));
        $(this.input).on("keydown", this.keydownHandler.bindAsEventListener(this));
        $(this.input).on("blur", this.onblurHandler.bindAsEventListener(this));
    },
    /**
     * @method keydownHandler 键盘按下处理器
     * @param {Event} e 事件,为自动注入
     */
    keydownHandler: function(e){
        var upArrow = 38;
        var downArrow = 40;
        
        if (e.keyCode == upArrow) {
            this.textSuggest.moveSelectionUp();
            //setTimeout(this.moveCaretToEnd.bind(this), 1);
        }
        else 
            if (e.keyCode == downArrow) 
                this.textSuggest.moveSelectionDown();
    },
    /**
     * @method keyupHandler 键盘弹起处理器
     * @param {Event} e 事件,为自动注入
     */
    keyupHandler: function(e){
        if (this.input.length == 0 && !Sky.isOpera) 
            this.textSuggest.hideSuggestions();
        
        if (!this.handledSpecialKeys(e)) 
            this.textSuggest.handleTextInput();
    },
    /**
     * @method handledSpecialKeys 处理特殊按键
     * @param {Event} e 事件,为自动注入
     */
    handledSpecialKeys: function(e){
        var enterKey = 13;
        var upArrow = 38;
        var downArrow = 40;
        
        if (e.keyCode == upArrow || e.keyCode == downArrow) {
            return true;
        }
        else 
            if (e.keyCode == enterKey) {
                this.textSuggest.setInputFromSelection();
                return true;
            }
        
        return false;
    },
    
    moveCaretToEnd: function(){
        var pos = this.input.value.length;
        if (this.input.setSelectionRange) {
            this.input.setSelectionRange(pos, pos);
        }
        else 
            if (this.input.createTextRange) {
                var m = this.input.createTextRange();
                m.moveStart('character', pos);
                m.collapse();
                m.select();
            }
    },
    
    onblurHandler: function(e){
        if (this.textSuggest.suggestionsDiv.style.display == '') 
            this.textSuggest.setInputFromSelection();
        $(this.textSuggest.suggestionsDiv).hide();
    }
});



/*
    Base.js, version 1.1a
    Copyright 2006-2010, Dean Edwards
    License: http://www.opensource.org/licenses/mit-license.php
*/

var Base = function() {
    // dummy
};

Base.extend = function(_instance, _static) { // subclass
    var extend = Base.prototype.extend;
    
    // build the prototype
    Base._prototyping = true;
    var proto = new this;
    extend.call(proto, _instance);
  proto.base = function() {
    // call this method from any other method to invoke that method's ancestor
  };
    delete Base._prototyping;
    
    // create the wrapper for the constructor function
    //var constructor = proto.constructor.valueOf(); //-dean
    var constructor = proto.constructor;
    var klass = proto.constructor = function() {
        if (!Base._prototyping) {
            if (this._constructing || this.constructor == klass) { // instantiation
                this._constructing = true;
                constructor.apply(this, arguments);
                delete this._constructing;
            } else if (arguments[0] != null) { // casting
                return (arguments[0].extend || extend).call(arguments[0], proto);
            }
        }
    };
    
    // build the class interface
    klass.ancestor = this;
    klass.extend = this.extend;
    klass.forEach = this.forEach;
    klass.implement = this.implement;
    klass.prototype = proto;
    klass.toString = this.toString;
    klass.valueOf = function(type) {
        //return (type == "object") ? klass : constructor; //-dean
        return (type == "object") ? klass : constructor.valueOf();
    };
    extend.call(klass, _static);
    // class initialisation
    if (typeof klass.init == "function") klass.init();
    return klass;
};

Base.prototype = {    
    extend: function(source, value) {
        if (arguments.length > 1) { // extending with a name/value pair
            var ancestor = this[source];
            if (ancestor && (typeof value == "function") && // overriding a method?
                // the valueOf() comparison is to avoid circular references
                (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
                /\bbase\b/.test(value)) {
                // get the underlying method
                var method = value.valueOf();
                // override
                value = function() {
                    var previous = this.base || Base.prototype.base;
                    this.base = ancestor;
                    var returnValue = method.apply(this, arguments);
                    this.base = previous;
                    return returnValue;
                };
                // point to the underlying method
                value.valueOf = function(type) {
                    return (type == "object") ? value : method;
                };
                value.toString = Base.toString;
            }
            this[source] = value;
        } else if (source) { // extending with an object literal
            var extend = Base.prototype.extend;
            // if this object has a customised extend method then use it
            if (!Base._prototyping && typeof this != "function") {
                extend = this.extend || extend;
            }
            var proto = {toSource: null};
            // do the "toString" and other methods manually
            var hidden = ["constructor", "toString", "valueOf"];
            // if we are prototyping then include the constructor
            var i = Base._prototyping ? 0 : 1;
            while (key = hidden[i++]) {
                if (source[key] != proto[key]) {
                    extend.call(this, key, source[key]);

                }
            }
            // copy each of the source object's properties to this object
            for (var key in source) {
                if (!proto[key]) extend.call(this, key, source[key]);
            }
        }
        return this;
    }
};

// initialise
Base = Base.extend({
    constructor: function() {
        this.extend(arguments[0]);
    }
}, {
    ancestor: Object,
    version: "1.1",
    
    forEach: function(object, block, context) {
        for (var key in object) {
            if (this.prototype[key] === undefined) {
                block.call(context, object[key], key, object);
            }
        }
    },
        
    implement: function() {
        for (var i = 0; i < arguments.length; i++) {
            if (typeof arguments[i] == "function") {
                // if it's a function, call it
                arguments[i](this.prototype);
            } else {
                // add the interface using the extend method
                this.prototype.extend(arguments[i]);
            }
        }
        return this;
    },
    
    toString: function() {
        return String(this.valueOf());
    }
});

function $A(iterable) {  
      if (!iterable) return [];  
      if (iterable.toArray) return iterable.toArray();  
      var length = iterable.length || 0, results = new Array(length);  
      while (length--) results[length] = iterable[length];  
      return results;  
} 

Function.prototype.bind = function() {  
  var __method = this, args = $A(arguments), object = args.shift();  
  return function() {  
    return __method.apply(object, args.concat($A(arguments)));  
  };  
};  
  
Function.prototype.bindAsEventListener = function() {  
  var __method = this, args = $A(arguments), object = args.shift();  
  return function(event) {  
    return __method.apply(object, [event || window.event].concat(args));  
  };  
};  


var isIE=navigator.userAgent.toLowerCase().indexOf("msie")!=-1;  
var isIE6=navigator.userAgent.toLowerCase().indexOf("msie 6.0")!=-1;  
var isIE7=navigator.userAgent.toLowerCase().indexOf("msie 7.0")!=-1&&!window.XDomainRequest;  
var isIE8=!!window.XDomainRequest;  
var isGecko=navigator.userAgent.toLowerCase().indexOf("gecko")!=-1;  
var isQuirks=document.compatMode=="BackCompat";  
  
  
 function findElement(a){  
    if(typeof(a)=="string"){  
        a=document.getElementById(a);  
        if(!a){  
                return null   
         }  
    }
        return a;     
 }  
  
 function getEvent(a){  
    return window.event||a    
 }  
   
  function stopEvent(a){  
        a=getEvent(a);  
        if(!a){  
                return;   
         }  
        if(isGecko){  
                a.preventDefault();  
                a.stopPropagation()   
         }  
        a.cancelBubble=true;  
        a.returnValue=false   
 }  
  
 function getEventPosition(evt){  
      
    evt = window.event || evt;  
      
    var f={x:evt.clientX, y:evt.clientY};  
    var d, srcEle = (evt.srcElement ? evt.srcElement : evt.target);  
    if(isGecko){  
            d = srcEle.ownerDocument.defaultView      
     }else{  
            d = srcEle.ownerDocument.parentWindow     
     }  
    var a,c;  
    while(d != d.parent){  
        if(d.frameElement){  
                pos2 = $E.getPosition(d.frameElement);  
                f.x += pos2.x;  
                f.y += pos2.y     
         }  
            a = Math.max(d.document.body.scrollLeft, d.document.documentElement.scrollLeft);  
            c = Math.max(d.document.body.scrollTop, d.document.documentElement.scrollTop);  
            f.x -= a;  
            f.y -= c;  
            d = d.parent      
     }  
    return f      
 }  
   
 var $E = {};  
   
 $E.getPosition=function(m){  
        m=m||this;  
        m=findElement(m);  
        var k=m.ownerDocument;  
        if(m.parentNode===null||m.style.display=="none"){  
            return false      
        }  
        var l=null;  
        var j=[];  
        var g;  
        if(m.getBoundingClientRect){  
              
            g=m.getBoundingClientRect();  
            var c=Math.max(k.documentElement.scrollTop,k.body.scrollTop);  
            var d=Math.max(k.documentElement.scrollLeft,k.body.scrollLeft);  
            var b=g.left+d-k.documentElement.clientLeft;  
            var a=g.top+c-k.documentElement.clientTop;  
            if(isIE){  
                    b--;  
                    a--   
             }  
            return {x:b,y:a }  
      
 }else{  
      
    if(k.getBoxObjectFor){  
                g=k.getBoxObjectFor(m);  
                var h=(m.style.borderLeftWidth)?parseInt(m.style.borderLeftWidth):0;  
                var f=(m.style.borderTopWidth)?parseInt(m.style.borderTopWidth):0;  
                j=[g.x-h,g.y-f]   
         }  
      
 }  
    if(m.parentNode){  
        l=m.parentNode    
    }else{  
        l=null    
 }  
    while(l&&l.tagName!="BODY"&&l.tagName!="HTML"){  
            j[0]-=l.scrollLeft;  
            j[1]-=l.scrollTop;  
        if(l.parentNode){  
                l=l.parentNode    
         }else{  
                l=null    
         }  
      
 }  
    return{x:j[0],y:j[1]}  
      
 };  
   
$E.getPositionEx=function(c){  
    c=c||this;  
    c=findElement(c);  
    var f=$E.getPosition(c);  
    var d=window;  
    var a,b;  
    while(d!=d.parent){  
        if(d.frameElement){  
                pos2=$E.getPosition(d.frameElement);  
                f.x+=pos2.x;  
                f.y+=pos2.y   
         }  
        a=Math.max(d.document.body.scrollLeft,d.document.documentElement.scrollLeft);  
        b=Math.max(d.document.body.scrollTop,d.document.documentElement.scrollTop);  
        f.x-=a;  
        f.y-=b;  
        d=d.parent    
     }  
    return f      
 };  
   
$E.getParent=function(a,b){  
        b=b||this;  
        b=findElement(b);  
        while(b){  
                if(b.tagName.toLowerCase()==a.toLowerCase()){  
                        return findElement(b)   
                 }  
                b=b.parentElement     
         }  
        return null   
 };  
  
$E.getParentByAttr= function(a,c,b){  
        b=b||this;  
        b=findElement(b);  
        while(b){  
                if(b.getAttribute(a)==c){  
                        return $(b)   
                 }  
                b=b.parentElement     
         }  
        return null   
 };  
   
$E.getTopLevelWindow= function(){  
        var a=window;  
        while(a!=a.parent){  
                a=a.parent    
         }  
        return a      
};  


<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
.suggestDiv {  
	position: absolute;  
	top: 0px;  
	left: 0px;  
	width: 150px;  
	z-index: 101;  
	background-color: #C0C0C0;  
	border: 1px solid #000000;  
	padding-left: 2px;  
	overflow: visible;  
}  
  
span.spanMatchText {  
	text-decoration: underline;  
	font-weight: bold;  
}  
  
.suggestion {  
	background: #C0C0C0;  
}  
  
span.spanHighElement {  
	background: #000040;  
	color: white;  
	cursor: pointer;  
}  
  
span.noMatchData {  
	font-weight: bold;  
	color: #0000FF;  
}  
</style>

<script type="text/javascript"  src="jquery-1.7.2.min.js"></script>

<script type="text/javascript"  src="Base.js"></script>
<script type="text/javascript"  src="TextSuggest.js"></script>

<script type="text/javascript" >
window.onload = function(){  
	new TextSuggest('txtUserInput', '/textsuggest.do');
}
</script>
</head>
<body>
  
 自动提示:
 <input type="text" id="txtUserInput"/>

</body>
</html>


package com.conch.ark.platform.codegenerator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**   
 * 
 * @author Darkness
 * @date 2014-8-25 上午10:28:12 
 * @version V1.0   
 */
@Controller
public class TestController {

	@ResponseBody
	@RequestMapping("/textsuggest.do")
	public List<Map<String, Object>> textsuggest(String query) {
		List<Map<String, Object>> result = new ArrayList<Map<String,Object>>();
		
		Map<String, Object> topLevelDept = new HashMap<String, Object>();
		topLevelDept.put("name", "总部");
		topLevelDept.put("level", 1);
		topLevelDept.put("id", "topLevelDept");
		result.add(topLevelDept);
		
		Map<String, Object> secondLevelDept = new HashMap<String, Object>();
		secondLevelDept.put("name", "北京分部");
		secondLevelDept.put("level", 2);
		secondLevelDept.put("id", "secondLevelDept");
		result.add(secondLevelDept);
		
		Map<String, Object> secondLevelDept2 = new HashMap<String, Object>();
		secondLevelDept2.put("name", "上海分部");
		secondLevelDept2.put("level", 2);
		secondLevelDept2.put("id", "secondLevelDept2");
		result.add(secondLevelDept2);
		
		return result;
	}
	
}

分享到:
评论

相关推荐

    cocos2d-js弹窗组件

    "cocos2d-js弹窗组件"是这个框架的一个重要组成部分,它提供了方便易用的弹窗功能,极大地简化了在游戏或应用中创建对话框、提示信息等交互元素的过程。 在Cocos2d-js中,弹窗组件通常用于显示临时的通知、用户确认...

    WXA-ToolTip-微信小程序-ToolTip信息提示组件.zip

    微信小程序-ToolTip信息提示组件导入将ToolTip文件夹复制到pages文件夹内使用在需要使用ToolTip的页面对应的.wxml文件中添加: src="../ToolTip/toolTip.wxml"/&gt; &lt;!-- 引入toolTip模板 --&gt;  is=...

    VueAutosuggest一个很棒的Vuejs自动提示组件

    Vue Autosuggest 是一款基于 Vue.js 的高效且可定制的自动提示组件,专为构建智能输入框而设计。这款组件能够帮助开发者轻松实现搜索建议、自动完成等交互功能,提升用户体验。在JavaScript开发中,尤其是在使用Vue....

    uni-load-more.zip

    7. **使用方法**:在uni-app项目中,首先需要通过npm或 yarn 安装插件,然后在需要使用该组件的页面中导入,并在模板中添加相应的标签,同时在对应的js文件中进行事件绑定和状态管理。 通过深入理解和正确使用"uni-...

    MyEclipse中JS自动提示

    本篇文章将详细探讨MyEclipse中JS(JavaScript)的自动提示功能,帮助开发者提升编码速度和准确性。 **1. JS自动提示的基本概念** JS自动提示,也称为代码补全或智能感知,是IDE为提高编程效率提供的一项功能。当...

    html-overlay-master组件,包含覆盖功能

    HTML Overlay 是一种常见的前端开发技术,用于在网页上创建覆盖层,实现如提示框、弹窗、模态对话框等功能。在"html-overlay-master"组件中,它提供了一种高效且灵活的方式来实现这些效果。这个组件的核心是通过HTML...

    js-ui-autocompelete

    总的来说,"js-ui-autocomplete"是一个集成在jQuery UI框架中的自动补全组件,通过结合使用这些JavaScript和CSS资源,开发者能够轻松地在网页中实现智能提示和自动完成功能,提升用户的交互体验。这个包不仅提供了...

    vant的field组件(autocomplete)

    - `index.js`: 这个JavaScript文件通常包含了组件的主要逻辑,包括组件的定义、属性绑定、事件处理等。在这里,你会看到`AutoCompleteField`组件的实现代码。 - `index.json`: JSON配置文件,定义了组件的对外属性...

    更强的自动补全提示:提示补全组件:Kissy Suggest(转)

    本文将重点介绍一种强大的自动补全提示组件——Kissy Suggest,它是一个基于JavaScript的开源工具,适用于各种前端项目。 Kissy Suggest是Kissy框架的一部分,Kissy是一个轻量级的前端JavaScript库,旨在简化Web...

    Jquery-Ui自动提示

    在这些组件中,"自动提示"功能是实现类似百度搜索框下拉框提示的关键功能,极大地提升了用户体验。 ### 1. 自动提示的基本概念 自动提示,也被称为自动完成或自动填充,是一种用户输入辅助技术。当用户在输入框中...

    element-plus打包

    Element Plus 是一个基于 Vue.js 的开源 UI 组件库,提供了丰富的界面组件,如表格、按钮、下拉菜单等,用于快速构建现代化的前端应用。"element-plus打包" 指的是将 Element Plus 项目进行构建和打包的过程,以便在...

    一个angular的消息提示组件

    在本文中,我们将深入探讨这个Angular消息提示组件的特性、使用方法以及其在JavaScript开发中的重要性。 首先,这个组件的核心是基于Angular框架构建的,这意味着它可以无缝集成到任何Angular应用中,利用Angular的...

    vue2autocomplete一个Vue2的文本输入自动完成组件

    Vue2Autocomplete是针对Vue 2框架设计的一个高效、可定制化的文本输入自动完成组件。这个组件主要用于提高用户在输入时的效率,通过提供实时的建议列表,帮助用户快速找到并选择所需的内容。Vue.js是一个轻量级但...

    cui组件 jquery组件库

    通过结合jQuery,CUI组件库简化了JavaScript代码编写,使得开发者可以更快速地实现复杂的交互效果。 1. **组件介绍**: - **按钮(Buttons)**:提供基础的按钮样式,支持不同状态(如禁用、加载中)和类型(如...

    封装一个Ext消息提示框,显示几秒后自动消失

    该资源主要涵盖了 CSS 和 JavaScript 代码,旨在实现一个可以自动消失的消息提示框。 1. CSS 代码分析 在 CSS 代码中,我们可以看到一些重要的样式定义。例如,`.msg-box-mc` 类定义了字体大小为 14px 的样式,`....

    JavaScript实现日历组件-源代码

    1. **显示当前月份**:组件应能自动显示系统当前的月份和年份,允许用户向前或向后翻页选择其他月份。 2. **日期选择**:用户可以通过点击日期单元格选择特定日期,选中的日期应该有明显的视觉反馈,如改变背景色。...

    js组件操作手册

    "js组件操作手册"聚焦于如何利用JavaScript来创建和操作组件,以提高代码复用性和开发效率。本手册可能包含了如何设计、实现以及在JSP(JavaServer Pages)环境中集成这些组件的方法。 JSP组件是JavaWeb开发中的一...

    基于vue的消息提示队列组件

    Vue.js是一种流行的前端JavaScript框架,由尤雨溪开发,它以简洁的API和组件化设计赢得了开发者们的喜爱。在Web应用开发中,消息提示队列组件是不可或缺的一部分,它能够帮助用户更好地理解程序运行状态,提供操作...

    前端项目-JavaScript-autoComplete.zip

    总的来说,这个项目提供了一个基础的JavaScript自动完成解决方案,对于学习和实践JavaScript DOM操作、事件处理以及简单前端组件的构建是非常有价值的。开发者可以通过阅读和修改源代码,了解其工作原理,并将其定制...

    Vue.js-devtools_v5_new.rar

    Vue.js 是一款流行的前端JavaScript框架,它以组件化开发、数据双向绑定和简易上手而闻名。Vue.js-devtools 是一个强大的开发者工具,专为Vue应用程序的调试设计,它可以集成到Chrome浏览器中,帮助开发者更好地理解...

Global site tag (gtag.js) - Google Analytics