autoComplete.js 升级版本

话说世界末日前先写一遍博客吧,万一到时候没机会写,也总算没了遗憾。。。

一晃又是大半年过去了,寥寥几篇博客煞是寒碜,不过这会儿居然还能记得当初说过要完善autoComplete.js的,这不花了一天时间来改进。开始正文前还是得说一下比较有趣的引子。

引子

由于一直使用extjs来开发项目,众所周知这玩意的功能体系是比较庞大的,大部分常见组件都是顺手拈来,不过这次遇到点麻烦事,需要同时在一个combox里实现右侧按钮点击出现下拉树,input框输入则实现自动完成功能。实在没好的办法整合只好在网上搜索一下autoComplete组件,而且是不要依赖jquery的,主要是不想为了一个组件就引入一个框架,以后维护起来也很麻烦。百度了一下autoComplete.js,在第一页的结果里只有一个符合我的要求,点进去一看,吓了一跳。这篇文章居然是如此的熟悉,熟悉到有点不正常,仔细扫描代码到最后一行,哥微微一笑,扶了扶眼镜,果断关了标签页。默默打开我的博客,找到autoComplete.js那篇博客copy了下来,然后。。。    后面事你懂的

 

正文

代码里穿插了大量的注释,代码应该不算复杂,总的思路就是绑定到一个type为text的input输入框,然后生成对应的dom元素,用来展示提示的结果,然后在动态生成的li元素上绑定点击事件用来赋值到input输入框还有隐藏的input框,隐藏的输入框是用来传递值到后端。在输入框里绑定上下按键事件,也就是说可以直接用键盘操作来选择相应的值。这次升级之后主要变动有三点。第一,现在组件是可以多次实例化的;第二,增加了json数据格式来绑定数据源(可在ajax回调里直接使用);第三,增加了getVlaue,setValue方法;

下面是demo页面

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 5 <title>无标题文档</title>
 6 <style>
 7 body{width:100%;height:100%;margin:0;padding:0;}
 8 .easy{width:200px;height:200px;border:0 none;position:absolute;left:0;top:0;background:blue;}
 9 .autoComplete-autoDis{border:1px solid #98C0F4;display:none;margin:0;padding:0;background-color:#FFF;}
10 .autoComplete-autoDis ul{list-style:none;margin:0;padding:0;}
11 .autoComplete-autoDis li{padding:2px;cursor:pointer;border:1px dotted #FFFFFF;text-overflow: ellipsis;white-space: nowrap;overflow: hidden;font-size: 12px;}
12 .autoComplete-cur{background:#DFE8F6;border-color: #A3BAE9 !important;border: 1px dotted !important;}
13 </style>
14 </head>
15 <script src="autoComplete.js" type="text/javascript"></script>
16 <script language="javascript" type="text/javascript">
17 window.onload=function(){
18     at1=autoComplete({
19         input:document.getElementById('testInput'),
20         showValue:'name',
21         //hiddenValue:'name',
22         data:[{'name':'13612345564'},{'name':'13825646464'},{'name':'13412236054'},{'name':'13012348564'},{'name':'13012345564'},{'name':'13012365564'}]
23     });
24     at2=autoComplete({
25         showValue:'name',
26         hiddenValue:'key',
27         input:document.getElementById('testInput2')
28     });
29     at2.loadData([{'name':'中国银行','key':'ICBC'},{'name':'工商银行','key':'IBA'},{'name':'招商银行','key':'ABCD'}]);
30     at1.setValue('13012348564');
31     at2.setValue('IBA');
32 }
33 </script>
34 <body>
35 <div id="move" class="easy"></div>
36 <div style="padding:200px 0 0 500px;">
37     <input id="testInput" type="text" name="xo" style="width:180px;border:1px solid #A3BAE9;"/><br/>
38     <select name="sel">
39         <option value="1">能遮住我吗???</option>
40         <option value="2">能遮住我吗???</option>
41         <option value="3">能遮住我吗???</option>
42     </select>
43     <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
44 
45     <input id="testInput2" type="text" name="xo2" style="width:280px;border:1px solid #A3BAE9;"/>
46 </div>
47 </body>
48 </html>

这里是js代码

  1 /*
  2 author:hot
  3 createDate:2010-9-30
  4 update:2012-11-19
  5 JavaScript autoComplete
  6 */
  7 
  8 
  9 (function(win){
 10 var autoComplete=function(obj){//省掉开发者用new来实例化
 11     if(!(this instanceof autoComplete))
 12         return new autoComplete(obj);
 13     if(obj.input.getAttribute('type') != 'text' || obj.input.nodeName != 'INPUT')return null;
 14     this.init(obj);
 15     return this;
 16 }
 17 autoComplete.prototype={
 18     init:function(con){
 19         this.config={
 20             input:con.input,
 21             hiddenInput:con.hiddenInput,
 22             showValue:con.showValue?con.showValue:'',
 23             hiddenValue:con.hiddenValue?con.hiddenValue:con.showValue,
 24             pop_len:con.popLen&&/^[0-9]+$/.test(con.popLen)?parseInt(con.popLen):10, //自动提示显示的结果默认为10条数
 25           pop_cn:con.popCls?con.popCls:'autoComplete-autoDis', //提示层样式
 26           hover_cn:con.popCurCls?con.popCurCls:'autoComplete-cur',//鼠标滑过提示层样式
 27           source:Object.prototype.toString.call(con.data) === '[object Array]'?con.data:[]
 28         };
 29         this.setDom();
 30         this.bind(this.config.input);
 31     },
 32     loadData:function(data){//支持动态加载数据
 33         this.config.source=data;
 34     },
 35     bind:function(x){
 36         var addEvent=function(el,type,fn){ //绑定事件
 37             var self = el;
 38             if(self.attachEvent) {
 39                 self['e'+type+fn] = fn; //IE下拷贝元素引用,使this指向el对象而不是window
 40                 self[type+fn] = function(){self['e'+type+fn](window.event);}
 41                 self.attachEvent('on'+type, self[type+fn]);
 42             }else if(self.addEventListener)
 43                 self.addEventListener(type, fn, false);
 44             else
 45                 self['on'+type]=fn;
 46         }
 47         var self = this;
 48         if(!self.config.hiddenInput || self.config.hiddenInput.getAttribute('type') != 'text' || self.config.hiddenInput.nodeName != 'INPUT'){
 49             self.config.hiddenInput=document.createElement('input');
 50             self.config.hiddenInput.name=x.name;
 51             self.config.hiddenInput.type='hidden';
 52             x.parentNode.insertBefore(self.config.hiddenInput,x);
 53             x.removeAttribute('name');
 54         }
 55         addEvent(x,'keyup',function(e){
 56             e = e || window.event;
 57             var lis = self.pop.getElementsByTagName('li'),lens = self.pop.getElementsByTagName('li').length,n=lens,temp;
 58             if(e.keyCode == 38){                                        //键盘up键被按下
 59                 if(self.pop.style.display != 'none'){
 60                     for(var i=0;i<lens;i++){                            //遍历结果数据,判断是否被选中
 61                         if(lis[i].className)
 62                             temp = i;
 63                         else
 64                             n--;
 65                     }
 66                     if(n==0){                                                //如果没有被选中的li元素,则选中最后一个
 67                         lis[lens-1].className = self.config.hover_cn;
 68                         self.setValue(lis[lens-1].getAttribute('h_value'));
 69                     }else{                                                    //如果有被选中的元素,则选择上一个元素并赋值给input
 70                         if(lis[temp] == lis[0]){                        //如果选中的元素是第一个孩子节点则跳到最后一个选中
 71                             lis[lens-1].className = self.config.hover_cn;
 72                             self.setValue(lis[lens-1].getAttribute('h_value'));
 73                             lis[temp].className = '';
 74                         }else{                                                
 75                             lis[temp-1].className = self.config.hover_cn;
 76                             self.setValue(lis[temp-1].getAttribute('h_value'));
 77                             lis[temp].className = '';
 78                         }
 79                     }
 80                 }else                                                //如果弹出层没有显示则执行插入操作,并显示弹出层
 81                     self.insert(this);
 82             }else if(e.keyCode == 40){                     //down键被按下,原理同up键
 83                 if(self.pop.style.display != 'none'){
 84                     for(var i=0;i<lens;i++){
 85                         if(lis[i].className)
 86                             temp = i;
 87                         else
 88                             n--;
 89                     }
 90                     if(n==0){
 91                         lis[0].className = self.config.hover_cn;
 92                         self.setValue(lis[0].getAttribute('h_value'));
 93                     }else{
 94                         if(lis[temp] == lis[lens-1]){
 95                             lis[0].className = self.config.hover_cn;
 96                             self.setValue(lis[0].getAttribute('h_value'));
 97                             lis[temp].className = '';
 98                         }else{
 99                             lis[temp+1].className = self.config.hover_cn;
100                             self.setValue(lis[temp+1].getAttribute('h_value'));
101                             lis[temp].className = '';
102                         }
103                     }
104                 }else
105                     self.insert(this);
106             }else                                    //如果按下的键既不是up又不是down那么直接去匹配数据并插入
107                 self.insert(this);
108         });
109         addEvent(x,'blur',function(){                //这个延迟处理是因为如果失去焦点的时候是点击选中数据的时候会发现先无法触发点击事件
110             setTimeout(function(){self.pop.style.display='none';},300);
111         });
112         return this;
113     },
114     setDom:function(){
115         var self = this;
116         var dom = document.createElement('div'),frame=document.createElement('iframe'),ul=document.createElement('ul');
117         document.body.appendChild(dom);
118         with(frame){                                    //用来在ie6下遮住select元素
119             setAttribute('frameborder','0',0);            //注意这里的用法 最后一个参数0 用来强调不用区分属性的大小写,否则ie6下无效
120             setAttribute('scrolling','no');
121             style.cssText='z-index:-1;position:absolute;left:0;top:0;'
122         }
123         with(dom){                                        //对弹出层li元素绑定onmouseover,onmouseout
124             className = self.config.pop_cn;
125             !win.XMLHttpRequest&&appendChild(frame);
126             appendChild(ul);
127             onmouseover  = function(e){            //在li元素还没有加载的时候就绑定这个方法,通过判断target是否是li元素进行处理
128                 e = e || window.event;
129                 var target = e.srcElement || e.target;
130                 if(target.tagName == 'LI'){            //添加样式前先把所有的li样式去掉,这里用的是一种偷懒的方式,没有单独写removeClass方法
131                     for(var i=0,lis=target.parentNode.getElementsByTagName('li');i<lis.length;i++)
132                         lis[i].className = '';
133                     target.className=self.config.hover_cn;        //也没有写addClass方法,直接赋值了
134                 }
135             };
136             onmouseout = function(e){
137                 e = e || window.event;
138                 var target = e.srcElement || e.target;
139                 if(target.tagName == 'LI')
140                     target.className='';
141             };
142         }
143         this.pop = dom;
144     },
145     insert:function(self){
146         var bak = [],s,li=[],left,top,val=self.value,_this=this,bdLeft=0,bdRight=0;
147         var getStyle=function(el,styleName){//获取当前样式属性
148             if(el.currentStyle)//ie
149                 return styleName?el.currentStyle[styleName]:el.currentStyle;
150             else{ //webkit
151                 var arr=el.ownerDocument.defaultView.getComputedStyle(el, null);
152                 return styleName?arr[styleName]:arr;
153             }
154         }
155         for(var i=0,leng=this.config.source.length;i<leng;i++){         //判断input的数据是否与数据源里的数据一致,从左匹配
156             if(!!val&&val.length<=this.config.source[i][this.config.showValue].length&& this.config.source[i][this.config.showValue].substr(0,val.length) == val){
157                 bak.push(this.config.source[i]);
158             }
159         }
160         if(bak.length == 0){                                                    //如果没有匹配的数据则隐藏弹出层
161             this.pop.style.display='none';
162             return false;
163         }
164         bdLeft=getStyle(self,'borderLeftWidth').match(/\d/g).join('');
165         bdRight=getStyle(self,'borderRightWidth').match(/\d/g).join('');
166         //这个弹出层定位方法之前也是用循环offsetParent,但发现ie跟ff下差别很大(可能是使用方式不当),所以改用这个getBoundingClientRect,该属性在ie6,7下有两像素误差
167         left=self.getBoundingClientRect().left+document.documentElement.scrollLeft-document.documentElement.clientLeft;//展示层定位
168         top=self.getBoundingClientRect().top+document.documentElement.scrollTop+self.offsetHeight-document.documentElement.clientTop;
169         var totalWidth=self.offsetWidth-bdLeft-bdRight;
170         with(this.pop){
171             style.cssText = 'width:'+totalWidth+'px;'+'position:absolute;left:'+left+'px;top:'+top+'px;display:none;';
172             if(!win.XMLHttpRequest){
173                 getElementsByTagName('iframe')[0].setAttribute('width',(totalWidth-2)+'px');
174             }
175             onclick = function(e){
176                 e = e || window.event;
177                 var target = e.srcElement || e.target;
178                 if(target.tagName == 'LI'){
179                     _this.setValue(target.getAttribute('h_value'));
180                 }
181                 this.style.display='none';
182             };
183         }
184         s = bak.length>this.config.pop_len?this.config.pop_len:bak.length;
185         for(var i=0;i<s;i++){
186             li.push( '<li h_value='+bak[i][this.config.hiddenValue]+'>' + bak[i][this.config.showValue] +'</li>');
187         }
188         this.pop.getElementsByTagName('ul')[0].innerHTML = li.join('');
189         this.pop.style.display='block';
190         !win.XMLHttpRequest&&this.pop.getElementsByTagName('iframe')[0].setAttribute('height',(this.pop.offsetHeight-2)+'px');
191     },
192     setValue:function(v){//赋值,参数为字符串或者数字,用来与后端交互传输的值
193         var showVal='';
194         if(typeof v == 'string'||typeof v == 'number'){
195             if(!this.config.hiddenValue||this.config.hiddenValue==this.config.showValue){
196                 this.config.input.value=v;
197             }else{
198                 for(var i=0;i<this.config.source.length;i++){
199                     if(this.config.source[i][this.config.hiddenValue]==v){
200                         showVal=this.config.source[i][this.config.showValue];
201                         break;
202                     }
203                 }
204                 this.config.input.value=showVal;
205             }
206             this.config.hiddenInput.value=v;
207         }
208     },
209     getValue:function(){//获取隐藏的值,用来与后端交互传输的值
210         return this.config.hiddenInput.value;
211     },
212     getRawValue:function(){ //获取原生的值,也就是对普通用户显示的值
213         return this.config.input.value;
214     }
215 }
216 
217 win.autoComplete=autoComplete;
218 })(window);
posted @ 2012-11-19 17:46  冰封e族  阅读(926)  评论(0编辑  收藏  举报