前言:JS输入框模糊匹配插件以前在工作写过一个类似的 所以这次写轻松很多,这次写优化了几个方面:
1. 添加动态加载css文件 不需要引入css css全部在JS动态生成。
2. 不需要额外的标签 只需要一个input输入框 并且默认指定一个class类名为 "inputElem" 当然也可以自己配置参数 还需要一个当前父级容器增加一个默认类名 parentCls(也可以自己配置),因为输入框匹配值后需要一个隐藏域 所以需要隐藏域增加一个class "hiddenCls" 当然也支持自己配置参数。
如下代码:
<div class="parentCls"> <div style="200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div> <div class="parentCls"> <div style="200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div>
3. 支持页面上有多个输入框。
4. 支持鼠标点击和 键盘 上移和下移键操作 类似于百度输入框。
页面上所有的标签都是动态的生成的,不需要额外的标签。如上面的 只需要input标签 其他的div标签不依赖 只需要父级元素增加class "parentCls"(当然可以自己配置类名),
及要传给后台开发人员的隐藏域输入框 增加一个class "hiddenCls" 当然也可以自动配置参数。
我的模糊查询匹配的需求是这样的:
1. 每keyup时候 点击或者键盘上移下移操作 输入框填充用户名/工号 隐藏域填充工号 发请求 服务器返回数据 渲染出来。当然 隐藏域填充工号 值是form表单提交时候 后台要拿到提交过来的工号 所以需要这么一个隐藏域。
2. 当用户直接在输入框输入值时候 并没有键盘上移下移 或者 点击下拉框某一项时候 当鼠标失去焦点时候(blur) 当前输入框值为空 隐藏域值为空,这样做的目的 是为了防止上次form提交过后的数据仍然保存在隐藏域里面 当用户重新输入的时候 用户也并没有操作键盘上下移操作或者点击操作 再点击提交按钮时(失去焦点),那么值为空 隐藏域值为空 这样防止搜索出来不是用户输入的那个东东。
3. 当用户点击某一项时候 或者 上移下移时候 输入框动态的生成值 且输入框值现在不能重新输入 只有当点击输入框x的时候 才可以重新输入。
4. 已经遗留输入框可以多选的接口 目前还未完善输入框多选的操作。
5. 禁止ctrl+v 或者右键 粘贴操作。
下面我们来具体看看效果图是个什么样的吧 如下图所示:
下面HTML代码如下:
<div class="parentCls"> <div style="200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div> <div class="parentCls"> <div style="200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div> <input type="button" value="提交"/>
JS代码如下:
1 /** 2 * JS 模糊查询 3 * @author tugenhua 4 * @date 2013-11-19 5 * @param 1.当前的input add targetCls 6 * 2. 隐藏域里面统一增加同类名 叫 hiddenCls 7 * 3. 在各个父级元素上 添加类名 parentCls 8 */ 9 10 function AutoComplete (options) { 11 this.config = { 12 targetCls : '.inputElem', // 输入框目标元素 13 parentCls : '.parentCls', // 父级类 14 hiddenCls : '.hiddenCls', // 隐藏域input 15 searchForm :'.jqtransformdone', //form表单 16 hoverBg : 'hoverBg', // 鼠标移上去的背景 17 outBg : 'outBg', // 鼠标移下拉的背景 18 isSelectHide : true, // 点击下拉框 是否隐藏 19 url : '', // url接口 20 height : 0, // 默认为0 不设置的话 那么高度自适应 21 manySelect : false, // 输入框是否多选 默认false 单选 22 renderHTMLCallback : null, // keyup时 渲染数据后的回调函数 23 callback : null, // 点击某一项 提供回调 24 closedCallback : null // 点击输入框某一项x按钮时 回调函数 25 }; 26 27 this.cache = { 28 currentIndex : -1, 29 oldIndex : -1, 30 inputArrs : [] // 多选时候 输入框值放到数组里面去 31 }; 32 this.init(options); 33 } 34 35 AutoComplete.prototype = { 36 37 constructor: AutoComplete, 38 init: function(options) { 39 40 this.config = $.extend(this.config, options || {}); 41 var self = this, 42 _config = self.config, 43 _cache = self.cache; 44 45 46 47 // 鼠标点击输入框时候 48 $(_config.targetCls).each(function(index,item) { 49 50 // 点击搜索回来后判断 隐藏域有没有值 51 52 $(item).click(function(){ 53 var callVal = $.trim($(this).val()), 54 curP = $(this).closest(_config.parentCls), 55 hiddenVal = $.trim($(_config.hiddenCls,curP).val()), 56 targetParent = $(this).parent(); 57 if(callVal != '' && hiddenVal != '') { 58 self._createDiv(targetParent,callVal); 59 !$(this).hasClass('hidden') && $(this).addClass('hidden'); 60 self._hover(); 61 self._closed(); 62 } 63 }); 64 65 66 /* 67 * 禁止 ctrl+v 和 黏贴事件 68 */ 69 $(item).unbind('paste'); 70 $(item).bind('paste',function(e){ 71 e.preventDefault(); 72 var target = e.target, 73 targetParent = $(target).closest(_config.parentCls); 74 $(this).val(''); 75 $(_config.hiddenCls,targetParent) && $(_config.hiddenCls,targetParent).val(''); 76 }); 77 78 79 $(item).keyup(function(e){ 80 _cache.inputArrs = []; 81 var targetVal = $.trim($(this).val()), 82 keyCode = e.keyCode, 83 elemHeight = $(this).outerHeight(), 84 elemWidth = $(this).outerWidth(); 85 86 // 如果输入框值为空的话 那么隐藏域的value清空掉 87 if(targetVal == '') { 88 var curParents = $(this).closest(_config.parentCls); 89 $(_config.hiddenCls,curParents).val(''); 90 } 91 var targetParent = $(this).parent(); 92 $(targetParent).css({'position':'relative'}); 93 94 if($('.auto-tips',targetParent).length == 0) { 95 // 初始化时候 动态创建下拉框容器 96 $(targetParent).append($('<div class="auto-tips hidden"></div>')); 97 $('.auto-tips',targetParent).css({'position':'absolute','top':elemHeight,'left':'0px','z-index':999,'width':elemWidth,'border':'1px solid #ccc','overflow':'hidden'}); 98 } 99 100 101 var curIndex = self._keyCode(keyCode); 102 if(curIndex > -1){ 103 self._keyUpAndDown(targetVal,e,targetParent); 104 }else { 105 var isHasClass = _config.targetCls.replace(/^./,''); 106 107 if(targetVal != '' && $(this).hasClass(isHasClass)) { 108 self._doPostAction(targetVal,targetParent); 109 } 110 111 } 112 }); 113 114 // 失去焦点时 如果没有点击 或者上下移时候 直接输入 那么当前输入框值情况 隐藏域值情况 115 $(item).blur(function(e){ 116 var target = e.target, 117 targetParent = $(target).closest(_config.parentCls), 118 hiddenVal = $(_config.hiddenCls,targetParent).val(); 119 120 if($(this).attr('up') || $(this).attr('down') || hiddenVal != '') { 121 return; 122 }else { 123 $(this).val(''); 124 $(_config.hiddenCls,targetParent).val(''); 125 } 126 }); 127 }); 128 129 // 阻止form表单默认enter键提交 130 $(_config.searchForm).each(function(index,item) { 131 $(item).keydown(function(e){ 132 var keyCode = e.keyCode; 133 if(keyCode == 13) { 134 return false; 135 } 136 }); 137 }); 138 139 // 点击文档 140 $(document).click(function(e){ 141 e.stopPropagation(); 142 var target = e.target, 143 tagParent = $(target).parent(), 144 attr = $(target,tagParent).closest('.auto-tips'); 145 146 var tagCls = _config.targetCls.replace(/^./,''); 147 148 if(attr.length > 0 || $(target,tagParent).hasClass(tagCls)) { 149 return; 150 }else { 151 $('.auto-tips').each(function(index,item){ 152 !$(item,tagParent).hasClass('hidden') && $(item,tagParent).addClass('hidden'); 153 }); 154 155 } 156 }); 157 158 var stylesheet = '.auto-tips { margin: 0 1px; list-style: none;height:auto !important;padding: 0px;position:absolute; border:1px solid #ccc; top:27px; left:0; z-index:999; 100%;background:#fff !important;}' + 159 '.auto-tips p {overflow: hidden;margin: 1px 0 !important;padding: 5px 5px;border-bottom: 1px solid #e7e7e7;color: #666;text-decoration: none;line-height: 23px;white-space: nowrap;cursor: pointer;zoom: 1;}' + 160 '.auto-tips p img{ vertical-align:middle;float:left;}' + 161 '.create-input{line-height:26px,padding-left:3px;}' + 162 '.create-input span{margin-top:1px;padding-left:3px;height:24px;line-height:24px;float:left;}' + 163 '.create-input span i,.auto-tips span a{font-style:normal;float:left;cursor:default;}' + 164 '.create-input span a{padding:0 0px 0 3px;margin-top:1px;cursor:pointer;}' + 165 '.auto-tips p.hoverBg {background-color: #669cb6;color: #fff;cursor: pointer;}' + 166 '.hidden {display:none;}'; 167 168 this._addStyleSheet(stylesheet); 169 170 }, 171 /** 172 * 键盘上下键操作 173 */ 174 _keyUpAndDown: function(targetVal,e,targetParent) { 175 var self = this, 176 _cache = self.cache, 177 _config = self.config; 178 179 // 如果请求成功后 返回了数据(根据元素的长度来判断) 执行以下操作 180 if($('.auto-tips p',targetParent) && $('.auto-tips p',targetParent).length > 0) { 181 182 var plen = $('.auto-tips p',targetParent).length, 183 keyCode = e.keyCode; 184 _cache.oldIndex = _cache.currentIndex; 185 186 // 上移操作 187 if(keyCode == 38) { 188 if(_cache.currentIndex == -1) { 189 _cache.currentIndex = plen - 1; 190 }else { 191 _cache.currentIndex = _cache.currentIndex - 1; 192 if(_cache.currentIndex < 0) { 193 _cache.currentIndex = plen - 1; 194 } 195 } 196 if(_cache.currentIndex !== -1) { 197 198 !$('.auto-tips .p-index'+_cache.currentIndex,targetParent).hasClass(_config.hoverBg) && 199 $('.auto-tips .p-index'+_cache.currentIndex,targetParent).addClass(_config.hoverBg).siblings().removeClass(_config.hoverBg); 200 var curAttr = $('.auto-tips .p-index'+_cache.currentIndex,targetParent).attr('data-html'), 201 embId = $('.auto-tips .p-index'+_cache.currentIndex,targetParent).attr('embId'); 202 203 // 判断是否是多选操作 204 if(_config.manySelect) { 205 _cache.inputArrs.push(curAttr); 206 _cache.inputArrs = self._unique(_cache.inputArrs); 207 self._manySelect(targetParent); 208 }else { 209 $(_config.targetCls,targetParent).val(curAttr); 210 // 上移操作增加一个属性 当失去焦点时候 判断有没有这个属性 211 if(!$(_config.targetCls,targetParent).attr('up')){ 212 $(_config.targetCls,targetParent).attr('up','true'); 213 } 214 var pCls = $(_config.targetCls,targetParent).closest(_config.parentCls); 215 $(_config.hiddenCls,pCls).val(embId); 216 217 self._createDiv(targetParent,curAttr); 218 self._closed(targetParent); 219 // hover 220 self._hover(targetParent); 221 } 222 223 } 224 225 }else if(keyCode == 40) { //下移操作 226 if(_cache.currentIndex == plen - 1) { 227 _cache.currentIndex = 0; 228 }else { 229 _cache.currentIndex++; 230 if(_cache.currentIndex > plen - 1) { 231 _cache.currentIndex = 0; 232 } 233 } 234 if(_cache.currentIndex !== -1) { 235 236 !$('.auto-tips .p-index'+_cache.currentIndex,targetParent).hasClass(_config.hoverBg) && 237 $('.auto-tips .p-index'+_cache.currentIndex,targetParent).addClass(_config.hoverBg).siblings().removeClass(_config.hoverBg); 238 var curAttr = $('.auto-tips .p-index'+_cache.currentIndex,targetParent).attr('data-html'), 239 embId = $('.auto-tips .p-index'+_cache.currentIndex,targetParent).attr('embId'); 240 241 242 // 判断是否是多选操作 243 if(_config.manySelect) { 244 _cache.inputArrs.push(curAttr); 245 _cache.inputArrs = self._unique(_cache.inputArrs); 246 self._manySelect(targetParent); 247 }else { 248 $(_config.targetCls,targetParent).val(curAttr); 249 // 下移操作增加一个属性 当失去焦点时候 判断有没有这个属性 250 if(!$(_config.targetCls,targetParent).attr('down')){ 251 $(_config.targetCls,targetParent).attr('down','true'); 252 } 253 254 var pCls = $(_config.targetCls,targetParent).closest(_config.parentCls); 255 $(_config.hiddenCls,pCls).val(embId); 256 self._createDiv(targetParent,curAttr); 257 self._closed(targetParent); 258 // hover 259 self._hover(targetParent); 260 261 262 } 263 264 } 265 }else if(keyCode == 13) { //回车操作 266 var curVal = $('.auto-tips .p-index'+_cache.oldIndex,targetParent).attr('data-html'); 267 268 if($(_config.targetCls,targetParent).attr('up') || $(_config.targetCls,targetParent).attr('down')) { 269 $(_config.targetCls,targetParent).val(curVal); 270 if(_config.isSelectHide) { 271 !$(".auto-tips",targetParent).hasClass('hidden') && $(".auto-tips",targetParent).addClass('hidden'); 272 !$(_config.targetCls,targetParent).hasClass('hidden') && $(_config.targetCls,targetParent).addClass('hidden'); 273 } 274 } 275 _cache.currentIndex = -1; 276 _cache.oldIndex = -1; 277 278 } 279 } 280 }, 281 _keyCode: function(code) { 282 var arrs = ['17','18','38','40','37','39','33','34','35','46','36','13','45','44','145','19','20','9']; 283 for(var i = 0, ilen = arrs.length; i < ilen; i++) { 284 if(code == arrs[i]) { 285 return i; 286 } 287 } 288 return -1; 289 }, 290 _doPostAction: function(targetVal,targetParent) { 291 292 var self = this, 293 _cache = self.cache, 294 _config = self.config, 295 url = _config.url; 296 $.get(url+"?keyword="+targetVal+"×tamp="+new Date().getTime(),function(data){ 297 var ret = $.parseJSON(data.content), 298 results = ret.results; 299 if(results.length > 0) { 300 self._renderHTML(results,targetParent); 301 self._executeClick(results,targetParent); 302 }else { 303 !$('.auto-tips',targetParent).hasClass('hidden') && $('.auto-tips',targetParent).addClass("hidden"); 304 $('.auto-tips',targetParent).html(''); 305 306 } 307 }); 308 }, 309 _renderHTML: function(ret,targetParent) { 310 var self = this, 311 _config = self.config, 312 _cache = self.cache, 313 html = ''; 314 315 for(var i = 0, ilen = ret.length; i < ilen; i+=1) { 316 html += '<p data-html = "'+ret[i].lastName+'" embId="'+ret[i].emplId+'" class="p-index'+i+'">' + 317 '<img src="'+ret[i].image+'" style="margin-right:5px;" height="25" width="25" title="" alt="">' + 318 '<span>'+ret[i].lastName+'('+ret[i].emplId+')</span>' + 319 '</p>'; 320 } 321 // 渲染值到下拉框里面去 322 $('.auto-tips',targetParent).html(html); 323 $('.auto-tips',targetParent).hasClass('hidden') && $('.auto-tips',targetParent).removeClass('hidden'); 324 $('.auto-tips p:last',targetParent).css({"border-bottom":'none'}); 325 326 _config.renderHTMLCallback && $.isFunction(_config.renderHTMLCallback) && _config.renderHTMLCallback(); 327 328 // 出现滚动条 计算p的长度 * 一项p的高度 是否大于 设置的高度 如是的话 出现滚动条 反之 329 var plen = $('.auto-tips p',targetParent).length, 330 pheight = $('.auto-tips p',targetParent).height(); 331 332 if(_config.height > 0) { 333 if(plen*pheight > _config.height) { 334 $('.auto-tips',targetParent).css({'height':_config.height,'overflow':'auto'}); 335 }else { 336 $('.auto-tips',targetParent).css({'height':'auto','overflow':'auto'}); 337 } 338 } 339 }, 340 /** 341 * 当数据相同的时 点击对应的项时 返回数据 342 */ 343 _executeClick: function(ret,targetParent) { 344 var self = this, 345 _config = self.config, 346 _cache = self.cache; 347 $('.auto-tips p',targetParent).unbind('click'); 348 $('.auto-tips p',targetParent).bind('click',function(e){ 349 var dataAttr = $(this).attr('data-html'), 350 embId = $(this).attr('embId'); 351 352 // 判断是否多选 353 if(_config.manySelect) { 354 _cache.inputArrs.push(dataAttr); 355 _cache.inputArrs = self._unique(_cache.inputArrs); 356 self._manySelect(targetParent); 357 }else { 358 $(_config.targetCls,targetParent).val(dataAttr); 359 var parentCls = $(_config.targetCls,targetParent).closest(_config.parentCls), 360 hiddenCls = $(_config.hiddenCls,parentCls); 361 $(hiddenCls).val(embId); 362 363 self._createDiv(targetParent,dataAttr); 364 self._hover(targetParent); 365 366 !$(_config.targetCls,targetParent).hasClass('hidden') && $(_config.targetCls,targetParent).addClass('hidden'); 367 } 368 self._closed(targetParent); 369 if(_config.isSelectHide) { 370 !$('.auto-tips',targetParent).hasClass('hidden') && $('.auto-tips',targetParent).addClass('hidden'); 371 } 372 _config.callback && $.isFunction(_config.callback) && _config.callback(); 373 }); 374 375 // 鼠标移上效果 376 $('.auto-tips p',targetParent).hover(function(e){ 377 !$(this,targetParent).hasClass(_config.hoverBg) && 378 $(this,targetParent).addClass(_config.hoverBg).siblings().removeClass(_config.hoverBg); 379 }); 380 }, 381 _hover: function(targetParent){ 382 $('.create-input span',targetParent).hover(function(){ 383 $(this).css({'padding-left':'3px'}); 384 },function(){ 385 386 }); 387 $('.create-input .alink',targetParent).hover(function(){ 388 $(this).css({'color':'#D96500'}); 389 },function(){ 390 $(this).css({'color':''}); 391 }); 392 }, 393 // 动态的创建div标签 遮住input输入框 394 _createDiv: function(targetParent,dataAttr){ 395 var self = this, 396 _config = self.config; 397 var iscreate = $('.create-input',targetParent); 398 399 // 确保只创建一次div 400 if(iscreate.length > 0) { 401 $('.create-input',targetParent).remove(); 402 } 403 $(targetParent).prepend($('<div class="create-input"><span><i></i></span></div>')); 404 405 406 $('.create-input span i',targetParent).html(dataAttr); 407 $(_config.targetCls,targetParent).val(dataAttr); 408 $('.create-input span',targetParent).append('<a class="alink">X</a>'); 409 $('.alink',targetParent).css({'float':'left','background':'none'}); 410 var parentWidth = $(targetParent).outerWidth(), 411 parentheight = $(targetParent).outerHeight(), 412 iwidth = $('.create-input i',targetParent).outerWidth(), 413 closedWidth = $('.create-input .alink').outerWidth(); 414 if(iwidth >= parentWidth - closedWidth - 3) { 415 $('.create-input span i',targetParent).css({'width':(parentWidth - closedWidth - 3)+'px','overflow':'hidden','float':'left','height':parentheight}); 416 } 417 418 }, 419 // X 关闭事件 420 _closed: function(targetParent){ 421 var self = this, 422 _config = self.config; 423 /* 424 * 点击X 关闭按钮 425 * 判断当前输入框有没有up和down属性 有的话 删除掉 否则 什么都不做 426 */ 427 $('.alink',targetParent).click(function(){ 428 $('.create-input',targetParent) && $('.create-input',targetParent).remove(); 429 $(_config.targetCls,targetParent) && $(_config.targetCls,targetParent).hasClass('hidden') && 430 $(_config.targetCls,targetParent).removeClass('hidden'); 431 $(_config.targetCls,targetParent).val(''); 432 //清空隐藏域的值 433 var curParent = $(_config.targetCls,targetParent).closest(_config.parentCls); 434 $(_config.hiddenCls,curParent).val(''); 435 436 var targetInput = $(_config.targetCls,targetParent); 437 if($(targetInput).attr('up') || $(targetInput).attr('down')) { 438 $(targetInput).attr('up') && $(targetInput).removeAttr('up'); 439 $(targetInput).attr('down') && $(targetInput).removeAttr('down'); 440 } 441 _config.closedCallback && $.isFunction(_config.closedCallback) && _config.closedCallback(); 442 }); 443 }, 444 /* 445 * 数组去重复 446 */ 447 _unique: function(arrs) { 448 var obj = {}, 449 newArrs = []; 450 for(var i = 0, ilen = arrs.length; i < ilen; i++) { 451 if(obj[arrs[i]] != 1) { 452 newArrs.push(arrs[i]); 453 obj[arrs[i]] = 1; 454 } 455 } 456 return newArrs; 457 }, 458 /* 459 * 输入框多选操作 460 */ 461 _manySelect: function(targetParent) { 462 var self = this, 463 _config = self.config, 464 _cache = self.cache; 465 if(_cache.inputArrs.length > 0) { 466 $(_config.targetCls,targetParent).val(_cache.inputArrs.join(',')); 467 } 468 }, 469 /* 470 * 判断是否是string 471 */ 472 _isString: function(str) { 473 return Object.prototype.toString.apply(str) === '[object String]'; 474 }, 475 /* 476 * JS 动态添加css样式 477 */ 478 _addStyleSheet: function(refWin, cssText, id){ 479 480 var self = this; 481 if(self._isString(refWin)) { 482 id = cssText; 483 cssText = refWin; 484 refWin = window; 485 } 486 refWin = $(refWin); 487 var doc = document; 488 var elem; 489 490 if (id && (id = id.replace('#', ''))) { 491 elem = $('#' + id, doc); 492 } 493 494 // 仅添加一次,不重复添加 495 if (elem) { 496 return; 497 } 498 499 //elem = $('<style></style>'); 不能这样创建 IE8有bug 500 elem = document.createElement("style"); 501 502 // 先添加到 DOM 树中,再给 cssText 赋值,否则 css hack 会失效 503 $('head', doc).append(elem); 504 505 if (elem.styleSheet) { // IE 506 elem.styleSheet.cssText = cssText; 507 } else { // W3C 508 $(elem).append(doc.createTextNode(cssText)); 509 } 510 }, 511 /* 512 * 销毁操作 释放内存 513 */ 514 destory: function() { 515 var self = this, 516 _config = self.config, 517 _cache = self.cache; 518 _cache.ret = []; 519 _cache.currentIndex = 0; 520 _cache.oldIndex = 0; 521 _cache.inputArrs = []; 522 _config.targetCls = null; 523 } 524 }; 525 // 初始化 526 $(function(){ 527 var auto = new AutoComplete({ 528 url: '/rocky/commonservice/user/find.json' 529 }); 530 });