經過一個星期的煎熬,終于把基于Ajax的輸入提示功能實現了。太痛苦了,寫Javascript的感覺就跟用NotePad來寫代碼一樣,沒有智能提示、弱類型、難調試……總之是太折磨人了。
本來自己寫了一個比較簡單的,但是由于我的頁面上需要多個輸入框,還要可以動態增加輸入框,要把傳回來的結果set入多個輸入框,由于是使用的Struts標簽庫,<html:text>還沒有id屬性,讓這個問題復雜了不少。
需求是這樣的:
有一個頁面,需要錄入貨品信息,貨品有id,編號,名稱,單位,單價,規格等屬性,每個貨品信息在表格中有一行,一行中有多個輸入框,用于輸入貨品信息。在輸入貨品編號的時候,應該訪問后臺的商品信息庫,尋找以前是否有輸入過該編號的貨品,如果有,把編號返回。支持用鼠標點擊,鍵盤回車等方法選擇貨品,選擇后應該把貨品信息顯示到各個輸入框中供用戶修改。如果該貨品在商品信息庫中標記為敏感商品,要作出提示。一個編號可以有多個貨品。
改了3天代碼,終于決定破釜沉舟,刪掉重做。修改了《Ajax in Action》中的代碼,Ajax in Action中的代碼有些地方有錯誤,不仔細檢查一遍還真不太容易發現。書中后臺使用C#,前臺是使用Rico來向某個url傳參數的形式進行Ajax通信。返回的response類似:
<ajax-response>
??<response?type='object'?id='field1_updater'>
????<matches>
??????<text>XXX</text>
??????<value>XXX</value>
????</matches>
??</response>
</ajax-response>不過我不想寫JSP或者Servlet,所以用了DWR,直接用spring中的BO把結果傳回來:

cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse)
{
//
});cargobaseService是使用DWR創建的Javascript對象:
dwr.xml:
<dwr>
??<allow>
????<create?creator="spring"?javascript?=?"cargobaseService">
????????<param?name="beanName"?value="cargobaseService"/>
????</create>
????<convert?match="com.gdnfha.atcs.cargobase.model.*"?converter="bean"></convert>
??</allow>
</dwr>
返回為下面對象的數組

var?cargoModel?=?
{
??cargoCompany:?XXX,
??cargoCurrency:?XXX,
??cargoDestination:?XXX,
??cargoId:?XXX,
??cargoImpose:?XXX,
??cargoName:?XXX,
??cargoNumber:?XXX,
??cargoSize:?XXX,
??cargoUnit:?XXX,
??cargoUnitPrice:?XXX,
??sensitive:?true|false
}?Javascript代碼如下:
TextSuggest?=?Class.create();


TextSuggest.prototype?=?
{
????//構造函數

???initialize:?function(anId,company,?url,?options)?
{
??????this.id??????????=?anId;
??????this.company?=?company;
??????var?browser?=?navigator.userAgent.toLowerCase();
??????this.isIE????????=?browser.indexOf("msie")?!=?-1;
??????this.isOpera?????=?browser.indexOf("opera")!=?-1;
??????this.textInput???=?$(this.id);
??????this.suggestions?=?new?Array();
??????this.setOptions(options);
??????this.injectSuggestBehavior();
???},
????//設置參數

???setOptions:?function(options)?
{

??????this.options?=?
{
?????????suggestDivClassName:?'suggestDiv',
?????????suggestionClassName:?'suggestion',
?????????matchClassName?????:?'match',
?????????matchTextWidth?????:?true,
?????????selectionColor?????:?'#b1c09c',
?????????matchAnywhere??????:?false,
?????????ignoreCase?????????:?false,
?????????count??????????????:?10

??????}.extend(options?||?
{});
???},
????//注入輸入提示行為

???injectSuggestBehavior:?function()?
{

??????if?(?this.isIE?)
?????????this.textInput.autocomplete?=?"off";
????//創建控制器
??????var?keyEventHandler?=?new?TextSuggestKeyHandler(this);
??????//主要是為了避免在按回車的時候把表單提交
??????new?Insertion.After(?this.textInput,
???????????????????????????'<input?type="text"?id="'+this.id+'_preventtsubmit'+'"?style="display:none"/>'?);
??????new?Insertion.After(?this.textInput,
???????????????????????????'<input?type="hidden"?name="'+this.id+'_hidden'+'"?id="'+this.id+'_hidden'+'"/>'?);
????//創建div層
??????this.createSuggestionsDiv();
???},
????//處理輸入信息

???handleTextInput:?function()?
{
?????var?previousRequest????=?this.lastRequestString;
?????this.lastRequestString?=?this.textInput.value;
?????if?(?this.lastRequestString?==?""?)
????????this.hideSuggestions();

?????else?if?(?this.lastRequestString?!=?previousRequest?)?
{
?????????//訪問數據源
????????this.sendRequestForSuggestions();
?????}
???},
????//選擇框上移

???moveSelectionUp:?function()?
{

??????if?(?this.selectedIndex?>?0?)?
{
?????????this.updateSelection(this.selectedIndex?-?1);
??????}
???},
????//選擇框下移

???moveSelectionDown:?function()?
{

??????if?(?this.selectedIndex?<?(this.suggestions.length?-?1)??)?
{
?????????this.updateSelection(this.selectedIndex?+?1);
??????}
???},
????//更新當前選擇信息

???updateSelection:?function(n)?
{
??????var?span?=?$(?this.id?+?"_"?+?this.selectedIndex?);

??????if?(?span?)
{
??????????//消除以前的樣式
?????????span.style.backgroundColor?=?"";
??????}
??????this.selectedIndex?=?n;
??????var?span?=?$(?this.id?+?"_"?+?this.selectedIndex?);

??????if?(?span?)
{
??????????//更新新樣式
?????????span.style.backgroundColor?=?this.options.selectionColor;
??????}
???},
????//發送請求

???sendRequestForSuggestions:?function()?
{

?????if?(?this.handlingRequest?)?
{
????????this.pendingRequest?=?true;
????????return;
?????}

?????this.handlingRequest?=?true;
?????this.callDWRAjaxEngine();
???},

????//使用DWR訪問后臺

???callDWRAjaxEngine:?function()?
{
???????????//保存當前對象指針
???????????var?tempThis?=?this;

???????????cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse)
{
????????????tempThis.suggestions?=?ajaxResponse;

??????????????if?(?tempThis.suggestions.length?==?0?)?
{
?????????????????tempThis.hideSuggestions();
?????????????????$(?tempThis.id?+?"_hidden"?).value?=?"";

??????????????}else?
{
?????????????????tempThis.updateSuggestionsDiv();
?????????????????tempThis.showSuggestions();
?????????????????tempThis.updateSelection(0);
??????????????}
??????????????tempThis.handlingRequest?=?false;

????????????if?(?tempThis.pendingRequest?)?
{
????????????????tempThis.pendingRequest????=?false;
?????????????????tempThis.lastRequestString?=?this.textInput.value;
?????????????????tempThis.sendRequestForSuggestions();
??????????????}
???????????});
???},
???//顯示信息

???setInputFromSelection:?function()?
{
???????var?index?=?this.id.split("_");
???????var?trId?=?"cargoTr_"?+?index[1];
???????var?trElement?=?$(trId);
???????var?cellNodes?=?trElement.childNodes;
?????var?suggestion??=?this.suggestions[?this.selectedIndex?];

????for(var?i?=?0;?i?<?cellNodes.length;?i++)
{
????????var?cargo?=?cellNodes[i].firstChild;

????????if(cargo.name?==?"cargoName")
{
????????????cargo.value?=?suggestion.cargoName;
????????}

????????if(cargo.name?==?"cargoSize")
{
????????????cargo.value?=?suggestion.cargoSize;
????????}

????????if(cargo.name?==?"cargoUnit")
{
????????????cargo.value?==?suggestion.cargoUnit;
????????}

????????if(cargo.name?==?"cargoDestination")
{
????????????cargo.value?=?suggestion.cargoDestination;
????????}

????????if(cargo.name?==?"cargoUnitPrice")
{
????????????cargo.value?=?suggestion.cargoUnitPrice;
????????}
????}
?????this.textInput.value?=?suggestion.cargoNumber;
?????//敏感提示

?????if(suggestion.sensitive)
{
?????????var?warnStr?=?"注意!\n編號:"+suggestion.cargoNumber
?????????????????????????????????+"\n名稱:"?+?suggestion.cargoName
?????????????????????????????????+"\n為敏感商品!";
?????????alert(warnStr);?
?????}
?????this.hideSuggestions();
???},
????//顯示層

???showSuggestions:?function()?
{
??????var?divStyle?=?this.suggestionsDiv.style;
??????if?(?divStyle.display?==?''?)
?????????return;
??????this.positionSuggestionsDiv();
??????divStyle.display?=?'';
???},
????//定位層

???positionSuggestionsDiv:?function()?
{
??????var?textPos?=?RicoUtil.toDocumentPosition(this.textInput);
??????var?divStyle?=?this.suggestionsDiv.style;
??????divStyle.top??=?(textPos.y?+?this.textInput.offsetHeight)?+?"px";
??????divStyle.left?=?textPos.x?+?"px";

??????if?(?this.options.matchTextWidth?)
?????????divStyle.width?=?(this.textInput.offsetWidth-?this.padding())?+?"px";
???},
????//計算間隔

???padding:?function()?
{

?????try
{
??????var?styleFunc?=?RicoUtil.getElementsComputedStyle;
??????var?lPad????=?styleFunc(?this.suggestionsDiv,?"paddingLeft",??????"padding-left"?);
??????var?rPad????=?styleFunc(?this.suggestionsDiv,?"paddingRight",?????"padding-right"?);
??????var?lBorder?=?styleFunc(?this.suggestionsDiv,?"borderLeftWidth",??"border-left-width"?);
??????var?rBorder?=?styleFunc(?this.suggestionsDiv,?"borderRightWidth",?"border-right-width"?);

??????lPad????=?isNaN(lPad)??????0?:?lPad;
??????rPad????=?isNaN(rPad)??????0?:?rPad;
??????lBorder?=?isNaN(lBorder)???0?:?lBorder;
??????rBorder?=?isNaN(rBorder)???0?:?rBorder;

??????return?parseInt(lPad)?+?parseInt(rPad)?+?parseInt(lBorder)?+?parseInt(rBorder);

?????}catch?(e)
{
??????return?0;
?????}
???},
????//隱藏層

???hideSuggestions:?function()?
{
??????this.suggestionsDiv.style.display?=?'none';
???},
????//創建層

???createSuggestionsDiv:?function()?
{
??????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);
???},
????//更新層

???updateSuggestionsDiv:?function()?
{
??????this.suggestionsDiv.innerHTML?=?"";
??????var?suggestLines?=?this.createSuggestionSpans();
??????for?(?var?i?=?0?;?i?<?suggestLines.length?;?i++?)
?????????this.suggestionsDiv.appendChild(suggestLines[i]);
???},
????//創建層中的選項span

???createSuggestionSpans:?function()?
{
??????var?regExpFlags?=?"";
??????if?(?this.options.ignoreCase?)
?????????regExpFlags?=?'i';
??????var?startRegExp?=?"^";
??????if?(?this.options.matchAnywhere?)
?????????startRegExp?=?'';
?????????//正則表達式匹配
??????var?regExp??=?new?RegExp(?startRegExp?+?this.lastRequestString,?regExpFlags?);
??????var?suggestionSpans?=?new?Array();
??????for?(?var?i?=?0?;?i?<?this.suggestions.length?;?i++?)
?????????suggestionSpans.push(?this.createSuggestionSpan(?i,?regExp?)?)

??????return?suggestionSpans;
???},
????//創建單個選項span

???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.cargoNumber+"",
?????????????????????????????????????????????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)?);

??????suggestionSpan.appendChild(?document.createTextNode(?textValues.start?)?);
??????suggestionSpan.appendChild(?textMatchSpan?);
??????suggestionSpan.appendChild(?document.createTextNode(?textValues.end?)?);

??????return?suggestionSpan;
???},
????//鼠標經過處理

???mouseoverHandler:?function(e)?
{
??????var?src?=?e.srcElement???e.srcElement?:?e.target;
??????var?index?=?parseInt(src.id.substring(src.id.lastIndexOf('_')+1));
??????this.updateSelection(index);
???},
????//鼠標點擊處理

???itemClickHandler:?function(e)?
{
??????this.mouseoverHandler(e);
??????//原書沒有下面一句,也就是說鼠標點擊不會把數據set入輸入框!
??????this.setInputFromSelection();
??????this.hideSuggestions();
???},
????//分拆字符串

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


???getElementContent:?function(element)?
{
??????return?element.firstChild.data;
???}
}
//控制器類
TextSuggestKeyHandler?=?Class.create();


TextSuggestKeyHandler.prototype?=?
{
????//構造方法

???initialize:?function(?textSuggest?)?
{
???????????//TextSuggest類的引用
??????this.textSuggest?=?textSuggest;
??????//輸入框的引用
??????this.input???????=?this.textSuggest.textInput;
??????//為輸入框增加事件響應機制
??????this.addKeyHandling();
???},


???addKeyHandling:?function()?
{
??????this.input.onkeyup????=?this.keyupHandler.bindAsEventListener(this);
??????this.input.onkeydown??=?this.keydownHandler.bindAsEventListener(this);
??????this.input.onblur?????=?this.onblurHandler.bindAsEventListener(this);
??????//原書有錯,原文是this.isOpera,但是TextSuggestKeyHandler沒有isOpera屬性
??????if?(?this.textSuggest.isOpera?)
?????????this.input.onkeypress?=?this.keyupHandler.bindAsEventListener(this);
???},
????//按鍵按下事件響應

???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();
??????}
???},
????//放開按鍵事件響應

???keyupHandler:?function(e)?
{
??????if?(?this.input.length?==?0?&&?!this.isOpera?)
?????????this.textSuggest.hideSuggestions();

?????if?(?!this.handledSpecialKeys(e)?)
????????this.textSuggest.handleTextInput();
???},
????//處理特殊按鍵

???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?)?
{
??????????//回車則set入數據
?????????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.hideSuggestions();
???}

};

有幾個地方是需要特別注意的:
用下面得方法訪問后臺,并把結果放到當前對象得suggestions數組中:
????//使用DWR訪問后臺

???callDWRAjaxEngine:?function()?
{
???????????//保存當前對象指針
???????????var?tempThis?=?this;

???????????cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse)
{
????????????tempThis.suggestions?=?ajaxResponse;

??????????????if?(?tempThis.suggestions.length?==?0?)?
{
?????????????????tempThis.hideSuggestions();
?????????????????$(?tempThis.id?+?"_hidden"?).value?=?"";

??????????????}else?
{
?????????????????tempThis.updateSuggestionsDiv();
?????????????????tempThis.showSuggestions();
?????????????????tempThis.updateSelection(0);
??????????????}
??????????????tempThis.handlingRequest?=?false;

????????????if?(?tempThis.pendingRequest?)?
{
????????????????tempThis.pendingRequest????=?false;
?????????????????tempThis.lastRequestString?=?this.textInput.value;
?????????????????tempThis.sendRequestForSuggestions();
??????????????}
???????????});
???},這個方法中一定要用tempThis保存當前對象的引用,原文中直接用this的話會產生object Error
???//顯示信息

???setInputFromSelection:?function()?
{
???????var?index?=?this.id.split("_");
???????var?trId?=?"cargoTr_"?+?index[1];
???????var?trElement?=?$(trId);
???????var?cellNodes?=?trElement.childNodes;
?????var?suggestion??=?this.suggestions[?this.selectedIndex?];

????for(var?i?=?0;?i?<?cellNodes.length;?i++)
{
????????var?cargo?=?cellNodes[i].firstChild;

????????if(cargo.name?==?"cargoName")
{
????????????cargo.value?=?suggestion.cargoName;
????????}

????????if(cargo.name?==?"cargoSize")
{
????????????cargo.value?=?suggestion.cargoSize;
????????}

????????if(cargo.name?==?"cargoUnit")
{
????????????cargo.value?==?suggestion.cargoUnit;
????????}

????????if(cargo.name?==?"cargoDestination")
{
????????????cargo.value?=?suggestion.cargoDestination;
????????}

????????if(cargo.name?==?"cargoUnitPrice")
{
????????????cargo.value?=?suggestion.cargoUnitPrice;
????????}
????}
?????this.textInput.value?=?suggestion.cargoNumber;
?????//敏感提示

?????if(suggestion.sensitive)
{
?????????var?warnStr?=?"注意!\n編號:"+suggestion.cargoNumber
?????????????????????????????????+"\n名稱:"?+?suggestion.cargoName
?????????????????????????????????+"\n為敏感商品!";
?????????alert(warnStr);?
?????}
?????this.hideSuggestions();
???},
function?prepareTypeAhead()
{
????var?companyArray?=?document.getElementsByName("bill.clientName");
????var?company?=?companyArray[0].value;
????var?cargoArray?=?document.getElementsByName("cargoNumber");

????var?suggestOptions?=?
{};

????for(var?i?=?0;?i?<?cargoArray.length;?i++)
{
????????cargoArray[i].id?=?"cargo_"?+?i;
????????suggest?=?createTextSuggest("cargo_"+i,company,suggestOptions);
????}
}

function?createTextSuggest(id,company,suggestOptions)
{
????//為輸入框增加輸入提示功能
????suggest?=?new?TextSuggest(id,company,suggestOptions);
????return?suggest;
}可以自己配置選項:

???setOptions:?function(options)?
{

??????this.options?=?
{
??????????//層樣式
?????????suggestDivClassName:?'suggestDiv',
?????????//選項樣式
?????????suggestionClassName:?'suggestion',
?????????//匹配樣式
?????????matchClassName?????:?'match',
?????????//是否匹配輸入框寬度
?????????matchTextWidth?????:?true,
?????????//選項顏色
?????????selectionColor?????:?'#b1c09c',
?????????//是否從頭匹配
?????????matchAnywhere??????:?false,
?????????//是否忽略大小寫
?????????ignoreCase?????????:?false,
?????????//顯示數目,暫時沒用上
?????????count??????????????:?10

??????}.extend(options?||?
{});
???},至此就差不多了,其實全都是Ajax in Action上的代碼,可是他的代碼寫得太繁復,很難看明白,而且書上的解釋也不太清楚(我這么覺得),可能是由于我不熟悉javascript得緣故吧,還是有很大得差距,要努力!基本按照上面的代碼,把獲取輸入的方式和設置結果的方式重寫一下就可以重用了,還很容易配置,真是很精致的類啊
本來自己寫了一個比較簡單的,但是由于我的頁面上需要多個輸入框,還要可以動態增加輸入框,要把傳回來的結果set入多個輸入框,由于是使用的Struts標簽庫,<html:text>還沒有id屬性,讓這個問題復雜了不少。
需求是這樣的:
有一個頁面,需要錄入貨品信息,貨品有id,編號,名稱,單位,單價,規格等屬性,每個貨品信息在表格中有一行,一行中有多個輸入框,用于輸入貨品信息。在輸入貨品編號的時候,應該訪問后臺的商品信息庫,尋找以前是否有輸入過該編號的貨品,如果有,把編號返回。支持用鼠標點擊,鍵盤回車等方法選擇貨品,選擇后應該把貨品信息顯示到各個輸入框中供用戶修改。如果該貨品在商品信息庫中標記為敏感商品,要作出提示。一個編號可以有多個貨品。
改了3天代碼,終于決定破釜沉舟,刪掉重做。修改了《Ajax in Action》中的代碼,Ajax in Action中的代碼有些地方有錯誤,不仔細檢查一遍還真不太容易發現。書中后臺使用C#,前臺是使用Rico來向某個url傳參數的形式進行Ajax通信。返回的response類似:














dwr.xml:























































































































































































































































































































































































































































































































用下面得方法訪問后臺,并把結果放到當前對象得suggestions數組中:



















































































使用這個方法把結果set到輸入框中
其他幾個地方比較麻煩的就是原書的代碼有錯了,我是自己手打進去的才發現
頁面上使用下面方法為輸入框增加ajax的自動提示功能:
















































