|
Posted on 2007-02-01 22:39 Zou Ang 阅读(3918) 评论(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
呵呵,不常写嘛,别见怪~
|