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