话说世界末日前先写一遍博客吧,万一到时候没机会写,也总算没了遗憾。。。
一晃又是大半年过去了,寥寥几篇博客煞是寒碜,不过这会儿居然还能记得当初说过要完善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);