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