在web开发中,为了提高用户体验,会经常用到输入框的自动完成功能,不仅帮助用户进行快速输入,最重要的是帮助那些“记不全要输入什么”的用户进行选择。这个功能有很多插件已经实现了,为了适应项目的特殊需求,决定自己编写一个具备通用性、扩展性和灵活性的自动完成类,就当是边写边学习了,一举两得。该功能是比较简单的,核心是数据获取方式和导航的实现,简单写了一个,经测试非常好用,还有很多地方需要修改和改进,例如:在原型中只暴露init方法即可,其他方法都需要放到私有空间内,不让用户访问到,这个以后再完善吧。啥也不说了,小二,上菜:
代码如下:(已更新,最新代码请参考:https://github.com/zjh-neverstop/AutoCompleteMulti)
1 /** 2 * 实现自动完成功能的js类 3 * 1、数据获取方式:设置静态数据集、ajax方式、自定义数据获取函数 4 * 2、可以控制是否启用匹配项的循环选择 5 * 3、可以控制是否使用默认静态数据集,在获取动态数据失败的情况下会显示
7 */ 8 9 (function(){ 10 11 //封装使用频繁的变量,在构造函数中进行初始化 12 var commonObj = {}; 13 14 /** 15 * 构造函数 16 * @param option 17 * @constructor 18 */ 19 function AutoComplete(option) { 20 //需要实现自动完成功能的页面控件ID 21 this.controlId = option.controlId; 22 23 //匹配结果div的id 24 this.resultDivId = option.resultDivId; 25 26 //当前选中的项索引,第一项索引为0 27 this.index = -1; 28 29 //静态数据集 30 this.datas = option.datas; 31 32 //动态获取的数据集 33 this.dynamicDatas = null; 34 35 //服务器端地址 36 this.serverUrl = option.serverUrl; 37 38 //匹配结果集合 39 this.resultDatas = null; 40 41 //ajax请求数据 42 this.ajaxRequestData = option.ajaxRequestData; 43 44 //主要用在一个后台页面处理多个前端自动完成请求的情况,根据此字段调用具体的后台方法 45 this.actionName = option.actionName; 46 47 //是否可以循环选择 48 this.circleChoose = option.circleChoose || "true"; 49 50 //是否从服务器端获取数据 51 this.serverEnabled = option.serverEnabled || "false"; 52 53 //是否使用静态数据 54 //一般情况下,自动完成都是获取动态数据的,开启这个标志后,在获取动态数据失败的情况下会使用静态数据,默认为false 55 this.useStaticDatas = option.useStaticDatas||"false"; 56 57 //驱动函数 58 this.drivenFuc = null; 59 60 //自定义数据获取方法 61 this.getCompleteDatas = function (){ 62 if( typeof option.getCompleteDatas === 'function' ){ 63 return option.getCompleteDatas(); 64 } 65 else{ 66 return null; 67 } 68 69 }; 70 71 72 //私有变量,封装后面常用的4个变量 73 //var commonObj = {}; 74 75 //通过匿名函数来构造一个类似面向对象语言中的只读属性 76 //初始化commonObj变量 77 //注意:匿名函数中的this指向windows对象,这里需要将AutoComplete对象的引用赋值给that 78 (function(that){ 79 commonObj = { 80 control : document.getElementById(that.controlId), 81 results : document.getElementById(that.resultDivId), 82 jControl : $(document.getElementById(that.controlId)), 83 jResults : $(document.getElementById(that.resultDivId)) 84 }; 85 })(this); 86 87 //只读属性 88 /*this.getCommonObj = function(){ 89 return commonObj; 90 }*/ 91 92 } 93 94 /** 95 * 原型方法,除了init方法和构造函数外,其他方法均需要私有化,待完善... 96 */ 97 AutoComplete.prototype = { 98 99 //指定构造函数 100 constructor: AutoComplete, 101 102 //获取事件对象 103 getEvent: function() { 104 return window.event || arguments[0]; //event ? event : window.event; 105 }, 106 107 //获取事件源 108 getTarget: function(event) { 109 return event.target || event.srcElement; 110 }, 111 112 /** 113 * 计算div的偏移量 114 * @param obj 115 * @returns {{left: (Number|number), top: (Number|number)}} 116 */ 117 getOffset: function(obj) { 118 var x = obj.offsetLeft || 0; 119 var y = obj.offsetTop || 0; 120 var temp = obj; 121 while (temp.offsetParent) { 122 temp = temp.offsetParent; 123 x += temp.offsetLeft; 124 y += temp.offsetTop; 125 } 126 //alert("x:"+x+" y:"+y); 127 return { left: x, top: y }; 128 }, 129 130 /** 131 * 将tagetDiv定位到sourceDiv下方,与sourceDic左对齐,宽度一致 132 * @param sourceDiv 133 * @param targetDiv 134 */ 135 positionDiv: function(sourceDiv, targetDiv) { 136 var obj = document.getElementById(sourceDiv); 137 var xy = this.getOffset(obj); 138 $("#" + targetDiv).css("left", xy.left); 139 $("#" + targetDiv).css("width", $("#" + sourceDiv).outerWidth()); 140 $("#" + targetDiv).css("top", (xy.top + $("#" + sourceDiv).outerHeight())); 141 142 }, 143 144 init: function() { 145 var control = document.getElementById(this.controlId); 146 var results = document.getElementById(this.resultDivId); 147 var jControl = $(control); 148 var jResults = $(results); 149 var autoThisObj = this; 150 151 document.onclick = function(event) { 152 //$("#"+resultDivId).hide(); 153 var target = autoThisObj.getTarget(autoThisObj.getEvent(event)); 154 //alert(target.id); 155 if (target.id == autoThisObj.controlId) { 156 return false; 157 } 158 autoThisObj.clearResults(); 159 } 160 161 //兼容ie(ie浏览器下,当按下up与down键时,输入框会失去焦点,导致up与down键不起作用) 162 jResults.bind("keydown", function(event) { 163 jControl.keydown(); 164 return false; 165 }); 166 167 168 //给指定控件绑定keyup事件 169 $("#" + autoThisObj.controlId).bind("keyup", function(event) { 170 var e = autoThisObj.getEvent(event); 171 var keyCode = e.keyCode; 172 if ((keyCode == '40' || keyCode == '38' || keyCode == '37' || keyCode == '39' || keyCode == '13' || keyCode == '9')) { 173 return false; 174 } 175 176 autoThisObj.index = -1; 177 results.scrollTop = 0; 178 179 var keyword = $.trim(jControl.val()); 180 if (keyword.length == 0) { 181 //jResults.hide(); 182 autoThisObj.clearResults(); 183 return; 184 } 185 186 //获取动态数据集,自定义函数的优先级最高 187 var autoDatas = autoThisObj.getCompleteDatas();//调用自定义数据获取函数 188 if( (autoDatas instanceof Array) && (autoDatas.length > 0) ){ 189 autoThisObj.dynamicDatas = autoDatas; 190 } 191 else if(autoThisObj.serverEnabled=="true"){ //服务器端获取数据 192 autoThisObj.getAjaxDatas(); 193 } 194 195 // 196 if(autoThisObj.dynamicDatas!=null){ 197 autoThisObj.generateHtml(autoThisObj.dynamicDatas); 198 } 199 else if(autoThisObj.useStaticDatas=="true" && autoThisObj.datas.length>0){ 200 autoThisObj.generateHtml(autoThisObj.datas); 201 } 261 }); //end keyup() 262 263 this.navigate(); 264 }, // end init() 265 266 /** 267 * 定义 up与down 按键功能 268 * @param event 269 * @param objectId 270 * @returns {boolean} 271 */ 272 navigate: function(event) { 273 274 var control = document.getElementById(this.controlId); 275 var results = document.getElementById(this.resultDivId); 276 var jControl = $(control); 277 var jResults = $(results); 278 279 var autoThisObj = this; 280 281 this.keyDownBind(jControl); 282 283 }, // end navigate() 284 285 /** 286 * 给指定jquery元素绑定keydown事件,使其可以进行匹配项的选择 287 */ 288 keyDownBind: function(jObject) { 289 var control = document.getElementById(this.controlId); 290 var results = document.getElementById(this.resultDivId); 291 var jControl = $(control); 292 var jResults = $(results); 293 var autoThisObj = this; 294 jObject.keydown(function(event) { 295 var e = autoThisObj.getEvent(event); 296 var key = e.keyCode; 297 if (i == "" || !i) 298 i = -1; 299 else 300 i = parseFloat(i); 301 302 var itemCount = results.childNodes.length; 303 304 if (key == '40') //Down 305 { 306 307 for (var i = 0, len = itemCount; i < len; i++) { 308 results.childNodes[i].className = "item"; //重置 309 } 310 311 autoThisObj.index++; 312 313 if (autoThisObj.index > itemCount - 1) { 314 if (autoThisObj.circleChoose == "true") { 315 autoThisObj.index = 0; 316 jResults.scrollTop(0); 317 } 318 else { 319 autoThisObj.index = itemCount - 1; 320 } 321 } 322 323 try { 324 results.childNodes[autoThisObj.index].className = "item chooseItem"; 325 results.childNodes[autoThisObj.index - 1].className = "item"; 326 } 327 catch (e) { 328 329 } 330 331 //以下两个判断语句用来将当前选中项置于div的可视范围内 332 if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) > $(results).scrollTop() + parseInt(results.style.height)) { 333 $(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) - parseInt(results.style.height)); 334 } 335 if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index) < $(results).scrollTop()) { 336 $(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index)); 337 } 338 } 339 else if (key == '38') //UP 340 { 341 for (var i = 0, len = itemCount; i < len; i++) { 342 results.childNodes[i].className = "item"; //重置 343 } 344 345 autoThisObj.index--; 346 if (autoThisObj.index < 0) { 347 autoThisObj.index = 0; 348 if (autoThisObj.circleChoose == "true") { 349 autoThisObj.index = itemCount - 1; 350 results.scrollTop = results.scrollHeight; 351 } 352 else { 353 autoThisObj.index = 0; 354 } 355 } 356 357 try { 358 results.childNodes[autoThisObj.index].className = "item chooseItem"; 359 results.childNodes[autoThisObj.index + 1].className = "item"; 360 } 361 catch (e) { 362 363 } 364 365 //以下两个判断语句用来将当前选中项置于div的可视范围内 366 if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) > $(results).scrollTop() + parseInt(results.style.height)) { 367 $(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) - parseInt(results.style.height)); 368 } 369 if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index) < $(results).scrollTop()) { 370 $(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index)); 371 } 372 } 373 else if (key == '13' || key == '9') // enter/tab 374 { 375 if (autoThisObj.index == -1) 376 autoThisObj.index = 0; 377 378 control.value = results.childNodes[autoThisObj.index].innerHTML; 379 380 autoThisObj.clearResults(); 381 return false; 382 } 383 else { 384 return; 385 } 386 }); // end keydown 387 }, 388 389 /** 390 * ajax方式获取匹配结果集 391 */ 392 getAjaxDatas:function(){ 393 var autoThisObj = this; 394 $.ajax({ 395 url: this.serverUrl, 396 data: this.ajaxRequestData, 397 type: "POST", 398 success: function(returnValue) { 399 if((returnValue instanceof Array)&&(returnValue.length > 0)){ 400 autoThisObj.dynamicDatas = returnValue; 401 } 402 else{ 403 autoThisObj.dynamicDatas = null; 404 } 405 } //end success() 406 }); //end ajax() 407 }, 408 409 /** 410 * 获取数据后生成html,并绑定基本事件 411 */ 412 generateHtml:function(datas){ 413 414 //var commonObj = this.getCommonObj(); 415 var autoThisObj = this; 416 417 var length = datas.length; 418 var htmlStr = ""; 419 420 if (length > 0) { 421 422 for (var i = 0; i < length; i++) { 423 htmlStr += "<div class='item'>" + datas[i] + "</div>"; 424 } 425 426 //htmlStr = "<div class='resultCss' id='"+ autoThisObj.resultDivId +"'>" + htmlStr + "</div>"; 427 428 commonObj.jResults.html(htmlStr).show(); 429 430 431 //计算单个item的高度 432 var itemHeight = $(".item").first().outerHeight(); 433 434 if (length >= 10) { 435 commonObj.jResults.height(10 * itemHeight); 436 } 437 else { 438 commonObj.jResults.height(length * itemHeight); 439 } 440 441 //调整结果div的宽度并定位 442 autoThisObj.positionDiv(autoThisObj.controlId, autoThisObj.resultDivId); 443 444 //默认选中第一项 445 autoThisObj.index = 0; 446 commonObj.results.childNodes[autoThisObj.index].className = "item chooseItem"; 447 448 //给结果集中的每一项添加基本事件 449 commonObj.jResults.find(".item").each(function() { 450 //点击事件 451 $(this).on("click", function() { 452 commonObj.jControl.val($(this).html()); 453 autoThisObj.clearResults(); 454 }); 455 456 //mouseover事件 457 $(this).mouseover(function() { 458 autoThisObj.index = $(this).index(); 459 var results = document.getElementById(autoThisObj.controlId); 460 var itemCount = document.getElementById(autoThisObj.resultDivId).childNodes.length; 461 for (var i = 0, len = itemCount; i < len; i++) { 462 document.getElementById(autoThisObj.resultDivId).childNodes[i].className = "item"; //重置 463 } 464 document.getElementById(autoThisObj.resultDivId).childNodes[autoThisObj.index].className = "item chooseItem"; 465 466 }); 467 468 }); 469 } 470 else { 471 autoThisObj.clearResults(); 472 } 473 }, 474 475 /** 476 * 清空结果div 477 */ 478 clearResults: function() { 479 var results = document.getElementById(this.resultDivId); 480 var jResults = $(results); 481 results.innerHTML = ""; 482 jResults.scrollTop(0); 483 results.style.display = "none"; 484 jResults.css("height", "auto"); 485 this.dynamicDatas = null; 486 }, 487 488 //重置结果集 489 resetResults: function() { 490 var results = document.getElementById(this.resultDivId); 491 var jResults = $(results); 492 jResults.scrollTop(0); 493 results.style.display = "none"; 494 this.index = 0; 495 496 var itemCount = results.childNodes.length; 497 for (var i = 0, len = itemCount; i < len; i++) { 498 results.childNodes[i].className = "item"; //重置 499 } 500 501 results.childNodes[this.index].className = "item chooseItem"; 502 } 503 504 } 505 506 //将AutoComplete暴露到全局范围 507 window.AutoComplete = AutoComplete; 508 })();
使用方法示例:
1 <html> 2 <head> 3 <meta http-equiv="Content-type" content="text/html; charset=utf-8"> 4 <title>autoComplete测试</title> 5 <style type="text/css"> 6 .item /*每一项的样式*/ 7 { 8 line-height:20px; 9 height:20px; 10 padding: 2px; 11 width:100%; 12 overflow: hidden; 13 } 14 .chooseItem{ /*选中的当前项样式*/ 15 background-color: #008B8B; 16 color:White; 17 cursor: pointer; 18 height:20px; 19 padding: 2px; 20 width:100%; 21 line-height:20px; 22 } 23 .resultsCss{ /*匹配结果集容器样式*/ 24 position:absolute; 25 overflow-y:auto; 26 overflow-x:hidden; 27 display:none; 28 border:solid 1px gray; 29 background-color:#F0FFFF; 30 } 31 </style> 32 <script type="text/javascript" src="jquery-1.7.2.min.js"></script> 33 <script type="text/javascript" src="autoCompleteHome.js"></script> 34 35 <body> 36 <div> 37 <label for="inpt">请输入:</label><input type="text" id="inpt" /> 38 </div> 39 <div id="tipList" class="resultsCss"> 40 </div> 41 <script type="text/javascript"> 42 //方式1:静态数据集 43 var staticDatas = ["asd","axcv","qwerfd","dfghj","cvbnm","bbghty","ertgb","trefgc","cssdavb","abcdefg","trefgc","cssdavb","abcdefg"]; 44 var autoCompleteOption = { 45 controlId: "inpt", 46 resultDivId: "tipList", 47 circleChoose: "true", 48 serverEndbled: "false", 49 useStaticDatas:"true", 50 datas:staticDatas 51 }; 52 53 var auto = new AutoComplete(autoCompleteOption); 54 auto.init(); 55 56 //方式2:自定义数据获取函数 57 /* 58 var autoCompleteOption = { 59 controlId: "inpt", 60 resultDivId: "tipList", 61 circleChoose: "true", 62 getCompleteDatas:function(){ 63 var datas = null; 64 //enter your code to get the data 65 return datas; 66 } 67 }; 68 69 var auto = new AutoComplete(autoCompleteOption); 70 auto.init(); 71 */ 72 73 //方式3:ajax获取数据 74 /* 75 var autoCompleteOption = { 76 controlId: "inpt", 77 resultDivId: "tipList", 78 circleChoose: "true", 79 resultDivId: "tipList", 80 ajaxRequestData:{}, 81 serverUrl: "AutoCompleteHandler.ashx" 82 }; 83 84 var auto = new AutoComplete(autoCompleteOption); 85 auto.init(); 86 */ 87 </script> 88 </body> 89 </head> 90 </html>