|
Posted on 2007-02-01 22:39 Zou Ang 閱讀(3937) 評論(7) 編輯 收藏 所屬分類:
經過一個星期的煎熬,終于把基于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();
???},使用這個方法把結果set到輸入框中 其他幾個地方比較麻煩的就是原書的代碼有錯了,我是自己手打進去的才發現 頁面上使用下面方法為輸入框增加ajax的自動提示功能:
 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得緣故吧,還是有很大得差距,要努力!基本按照上面的代碼,把獲取輸入的方式和設置結果的方式重寫一下就可以重用了,還很容易配置,真是很精致的類啊
評論
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置 回復 更多評論
2007-02-01 22:45 by
本來也不是什么很難的東西,可是寫著寫著就頭大,刪了又寫,寫了又刪,太煩人了
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置 回復 更多評論
2007-02-02 08:37 by
<html:text>還沒有id屬性???styleId是啥
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置[未登錄] 回復 更多評論
2007-02-02 08:45 by
@啊啊啊啊
多謝指導!一直不知道呵呵……謝謝啊
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置[未登錄] 回復 更多評論
2007-02-02 13:46 by
不明白這種功夫有多大的意義,用dwr一切都可以解決,做事要動腦子阿。
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置 回復 更多評論
2007-02-05 11:46 by
呵呵,樓上的,DWR確實是牛
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置 回復 更多評論
2007-02-06 23:35 by
老弟,你寫這個東西就叫煩人啊,我每個星期都要寫幾千行的JS代碼,那我不早就跳樓算了。還有像“啊啊啊啊 ”說的一樣,struts中把styleId屬性做為html標簽的id屬性使用,這個在reference中就可以查到的了。
# re: Javascript噩夢-Ajax實現輸入提示的調整與配置 回復 更多評論
2007-02-07 07:58 by
@errorfun
呵呵,不常寫嘛,別見怪~
|