前言:JS輸入框模糊匹配插件之前在工做寫過一個相似的 因此此次寫輕鬆不少,此次寫優化了幾個方面:css
1. 添加動態加載css文件 不須要引入css css所有在JS動態生成。html
2. 不須要額外的標籤 只須要一個input輸入框 而且默認指定一個class類名爲 "inputElem" 固然也能夠本身配置參數 還須要一個當前父級容器增長一個默認類名 parentCls(也能夠本身配置),由於輸入框匹配值後須要一個隱藏域 因此須要隱藏域增長一個class "hiddenCls" 固然也支持本身配置參數。json
以下代碼:數組
<div class="parentCls"> <div style="width:200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="width:200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div> <div class="parentCls"> <div style="width:200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="width:200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div>
3. 支持頁面上有多個輸入框。服務器
4. 支持鼠標點擊和 鍵盤 上移和下移鍵操做 相似於百度輸入框。app
頁面上全部的標籤都是動態的生成的,不須要額外的標籤。如上面的 只須要input標籤 其餘的div標籤不依賴 只須要父級元素增長class "parentCls"(固然能夠本身配置類名),ide
及要傳給後臺開發人員的隱藏域輸入框 增長一個class "hiddenCls" 固然也能夠自動配置參數。函數
個人模糊查詢匹配的需求是這樣的:優化
1. 每keyup時候 點擊或者鍵盤上移下移操做 輸入框填充用戶名/工號 隱藏域填充工號 發請求 服務器返回數據 渲染出來。固然 隱藏域填充工號 值是form表單提交時候 後臺要拿到提交過來的工號 因此須要這麼一個隱藏域。this
2. 當用戶直接在輸入框輸入值時候 並無鍵盤上移下移 或者 點擊下拉框某一項時候 當鼠標失去焦點時候(blur) 當前輸入框值爲空 隱藏域值爲空,這樣作的目的 是爲了防止上次form提交事後的數據仍然保存在隱藏域裏面 當用戶從新輸入的時候 用戶也並無操做鍵盤上下移操做或者點擊操做 再點擊提交按鈕時(失去焦點),那麼值爲空 隱藏域值爲空 這樣防止搜索出來不是用戶輸入的那個東東。
3. 當用戶點擊某一項時候 或者 上移下移時候 輸入框動態的生成值 且輸入框值如今不能從新輸入 只有當點擊輸入框x的時候 才能夠從新輸入。
4. 已經遺留輸入框能夠多選的接口 目前還未完善輸入框多選的操做。
5. 禁止ctrl+v 或者右鍵 粘貼操做。
下面咱們來具體看看效果圖是個什麼樣的吧 以下圖所示:
下面HTML代碼以下:
<div class="parentCls"> <div style="width:200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="width:200px;height:26px;line-height:26px;"/> </div> <input type="hidden" class="hiddenCls"/> </div> <div class="parentCls"> <div style="width:200px;height:26px;border:1px solid #ccc;"> <input type="text" class="inputElem" style="width: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; width: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 });