posts - 32, comments - 153, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Javascript噩梦-Ajax实现输入提示的调整与配置

Posted on 2007-02-01 22:39 Zou Ang 阅读(3741) 评论(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 Zou Ang
本来也不是什么很难的东西,可是写着写着就头大,删了又写,写了又删,太烦人了

# re: Javascript噩梦-Ajax实现输入提示的调整与配置  回复  更多评论   

2007-02-02 08:37 by 啊啊啊啊
<html:text>还没有id属性???styleId是啥

# re: Javascript噩梦-Ajax实现输入提示的调整与配置[未登录]  回复  更多评论   

2007-02-02 08:45 by Zou Ang
@啊啊啊啊
多谢指导!一直不知道呵呵……谢谢啊

# re: Javascript噩梦-Ajax实现输入提示的调整与配置[未登录]  回复  更多评论   

2007-02-02 13:46 by allen
不明白这种功夫有多大的意义,用dwr一切都可以解决,做事要动脑子阿。

# re: Javascript噩梦-Ajax实现输入提示的调整与配置  回复  更多评论   

2007-02-05 11:46 by itVincent
呵呵,楼上的,DWR确实是牛

# re: Javascript噩梦-Ajax实现输入提示的调整与配置  回复  更多评论   

2007-02-06 23:35 by errorfun
老弟,你写这个东西就叫烦人啊,我每个星期都要写几千行的JS代码,那我不早就跳楼算了。还有像“啊啊啊啊 ”说的一样,struts中把styleId属性做为html标签的id属性使用,这个在reference中就可以查到的了。

# re: Javascript噩梦-Ajax实现输入提示的调整与配置  回复  更多评论   

2007-02-07 07:58 by Zou Ang
@errorfun
呵呵,不常写嘛,别见怪~

只有注册用户登录后才能发表评论。


网站导航: