注:源自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;
}
}