首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > JavaScript >

JS-自动揭示组件

2012-10-26 
JS-自动提示组件注:源自Ajax实战实现自动提示功能:!DOCTYPE HTML PUBLIC -//W3C//DTD HTML 4.0 Transiti

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;            }

热点排行