在web开发中,为了提高用户体验,会经常用到输入框的自动完成功能,不仅帮助用户进行快速输入,最重要的是帮助那些“记不全要输入什么”的用户进行选择。这个功能有很多插件已经实现了,为了适应项目的特殊需求,决定自己编写一个具备通用性、扩展性和灵活性的自动完成类,就当是边写边学习了,一举两得。该功能是比较简单的,核心是数据获取方式和导航的实现,简单写了一个,经测试非常好用,还有很多地方需要修改和改进,例如:在原型中只暴露init方法即可,其他方法都需要放到私有空间内,不让用户访问到,这个以后再完善吧。啥也不说了,小二,上菜:

自己手写的自动完成js类-LMLPHP

代码如下:(已更新,最新代码请参考:https://github.com/zjh-neverstop/AutoCompleteMulti

 /**
* 实现自动完成功能的js类
* 1、数据获取方式:设置静态数据集、ajax方式、自定义数据获取函数
* 2、可以控制是否启用匹配项的循环选择
* 3、可以控制是否使用默认静态数据集,在获取动态数据失败的情况下会显示
7 */ (function(){ //封装使用频繁的变量,在构造函数中进行初始化
var commonObj = {}; /**
* 构造函数
* @param option
* @constructor
*/
function AutoComplete(option) {
//需要实现自动完成功能的页面控件ID
this.controlId = option.controlId; //匹配结果div的id
this.resultDivId = option.resultDivId; //当前选中的项索引,第一项索引为0
this.index = -1; //静态数据集
this.datas = option.datas; //动态获取的数据集
this.dynamicDatas = null; //服务器端地址
this.serverUrl = option.serverUrl; //匹配结果集合
this.resultDatas = null; //ajax请求数据
this.ajaxRequestData = option.ajaxRequestData; //主要用在一个后台页面处理多个前端自动完成请求的情况,根据此字段调用具体的后台方法
this.actionName = option.actionName; //是否可以循环选择
this.circleChoose = option.circleChoose || "true"; //是否从服务器端获取数据
this.serverEnabled = option.serverEnabled || "false"; //是否使用静态数据
//一般情况下,自动完成都是获取动态数据的,开启这个标志后,在获取动态数据失败的情况下会使用静态数据,默认为false
this.useStaticDatas = option.useStaticDatas||"false"; //驱动函数
this.drivenFuc = null; //自定义数据获取方法
this.getCompleteDatas = function (){
if( typeof option.getCompleteDatas === 'function' ){
return option.getCompleteDatas();
}
else{
return null;
} }; //私有变量,封装后面常用的4个变量
//var commonObj = {}; //通过匿名函数来构造一个类似面向对象语言中的只读属性
//初始化commonObj变量
//注意:匿名函数中的this指向windows对象,这里需要将AutoComplete对象的引用赋值给that
(function(that){
commonObj = {
control : document.getElementById(that.controlId),
results : document.getElementById(that.resultDivId),
jControl : $(document.getElementById(that.controlId)),
jResults : $(document.getElementById(that.resultDivId))
};
})(this); //只读属性
/*this.getCommonObj = function(){
return commonObj;
}*/ } /**
* 原型方法,除了init方法和构造函数外,其他方法均需要私有化,待完善...
*/
AutoComplete.prototype = { //指定构造函数
constructor: AutoComplete, //获取事件对象
getEvent: function() {
return window.event || arguments[0]; //event ? event : window.event;
}, //获取事件源
getTarget: function(event) {
return event.target || event.srcElement;
}, /**
* 计算div的偏移量
* @param obj
* @returns {{left: (Number|number), top: (Number|number)}}
*/
getOffset: function(obj) {
var x = obj.offsetLeft || 0;
var y = obj.offsetTop || 0;
var temp = obj;
while (temp.offsetParent) {
temp = temp.offsetParent;
x += temp.offsetLeft;
y += temp.offsetTop;
}
//alert("x:"+x+" y:"+y);
return { left: x, top: y };
}, /**
* 将tagetDiv定位到sourceDiv下方,与sourceDic左对齐,宽度一致
* @param sourceDiv
* @param targetDiv
*/
positionDiv: function(sourceDiv, targetDiv) {
var obj = document.getElementById(sourceDiv);
var xy = this.getOffset(obj);
$("#" + targetDiv).css("left", xy.left);
$("#" + targetDiv).css("width", $("#" + sourceDiv).outerWidth());
$("#" + targetDiv).css("top", (xy.top + $("#" + sourceDiv).outerHeight())); }, init: function() {
var control = document.getElementById(this.controlId);
var results = document.getElementById(this.resultDivId);
var jControl = $(control);
var jResults = $(results);
var autoThisObj = this; document.onclick = function(event) {
//$("#"+resultDivId).hide();
var target = autoThisObj.getTarget(autoThisObj.getEvent(event));
//alert(target.id);
if (target.id == autoThisObj.controlId) {
return false;
}
autoThisObj.clearResults();
} //兼容ie(ie浏览器下,当按下up与down键时,输入框会失去焦点,导致up与down键不起作用)
jResults.bind("keydown", function(event) {
jControl.keydown();
return false;
}); //给指定控件绑定keyup事件
$("#" + autoThisObj.controlId).bind("keyup", function(event) {
var e = autoThisObj.getEvent(event);
var keyCode = e.keyCode;
if ((keyCode == '40' || keyCode == '38' || keyCode == '37' || keyCode == '39' || keyCode == '13' || keyCode == '9')) {
return false;
} autoThisObj.index = -1;
results.scrollTop = 0; var keyword = $.trim(jControl.val());
if (keyword.length == 0) {
//jResults.hide();
autoThisObj.clearResults();
return;
} //获取动态数据集,自定义函数的优先级最高
var autoDatas = autoThisObj.getCompleteDatas();//调用自定义数据获取函数
if( (autoDatas instanceof Array) && (autoDatas.length > 0) ){
autoThisObj.dynamicDatas = autoDatas;
}
else if(autoThisObj.serverEnabled=="true"){ //服务器端获取数据
autoThisObj.getAjaxDatas();
} //
if(autoThisObj.dynamicDatas!=null){
autoThisObj.generateHtml(autoThisObj.dynamicDatas);
}
else if(autoThisObj.useStaticDatas=="true" && autoThisObj.datas.length>0){
autoThisObj.generateHtml(autoThisObj.datas);
}
}); //end keyup() this.navigate();
}, // end init() /**
* 定义 up与down 按键功能
* @param event
* @param objectId
* @returns {boolean}
*/
navigate: function(event) { var control = document.getElementById(this.controlId);
var results = document.getElementById(this.resultDivId);
var jControl = $(control);
var jResults = $(results); var autoThisObj = this; this.keyDownBind(jControl); }, // end navigate() /**
* 给指定jquery元素绑定keydown事件,使其可以进行匹配项的选择
*/
keyDownBind: function(jObject) {
var control = document.getElementById(this.controlId);
var results = document.getElementById(this.resultDivId);
var jControl = $(control);
var jResults = $(results);
var autoThisObj = this;
jObject.keydown(function(event) {
var e = autoThisObj.getEvent(event);
var key = e.keyCode;
if (i == "" || !i)
i = -1;
else
i = parseFloat(i); var itemCount = results.childNodes.length; if (key == '40') //Down
{ for (var i = 0, len = itemCount; i < len; i++) {
results.childNodes[i].className = "item"; //重置
} autoThisObj.index++; if (autoThisObj.index > itemCount - 1) {
if (autoThisObj.circleChoose == "true") {
autoThisObj.index = 0;
jResults.scrollTop(0);
}
else {
autoThisObj.index = itemCount - 1;
}
} try {
results.childNodes[autoThisObj.index].className = "item chooseItem";
results.childNodes[autoThisObj.index - 1].className = "item";
}
catch (e) { } //以下两个判断语句用来将当前选中项置于div的可视范围内
if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) > $(results).scrollTop() + parseInt(results.style.height)) {
$(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) - parseInt(results.style.height));
}
if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index) < $(results).scrollTop()) {
$(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index));
}
}
else if (key == '38') //UP
{
for (var i = 0, len = itemCount; i < len; i++) {
results.childNodes[i].className = "item"; //重置
} autoThisObj.index--;
if (autoThisObj.index < 0) {
autoThisObj.index = 0;
if (autoThisObj.circleChoose == "true") {
autoThisObj.index = itemCount - 1;
results.scrollTop = results.scrollHeight;
}
else {
autoThisObj.index = 0;
}
} try {
results.childNodes[autoThisObj.index].className = "item chooseItem";
results.childNodes[autoThisObj.index + 1].className = "item";
}
catch (e) { } //以下两个判断语句用来将当前选中项置于div的可视范围内
if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) > $(results).scrollTop() + parseInt(results.style.height)) {
$(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index + 1) - parseInt(results.style.height));
}
if (($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index) < $(results).scrollTop()) {
$(results).scrollTop(($(results.childNodes[autoThisObj.index]).height() + 4) * (autoThisObj.index));
}
}
else if (key == '13' || key == '9') // enter/tab
{
if (autoThisObj.index == -1)
autoThisObj.index = 0; control.value = results.childNodes[autoThisObj.index].innerHTML; autoThisObj.clearResults();
return false;
}
else {
return;
}
}); // end keydown
}, /**
* ajax方式获取匹配结果集
*/
getAjaxDatas:function(){
var autoThisObj = this;
$.ajax({
url: this.serverUrl,
data: this.ajaxRequestData,
type: "POST",
success: function(returnValue) {
if((returnValue instanceof Array)&&(returnValue.length > 0)){
autoThisObj.dynamicDatas = returnValue;
}
else{
autoThisObj.dynamicDatas = null;
}
} //end success()
}); //end ajax()
}, /**
* 获取数据后生成html,并绑定基本事件
*/
generateHtml:function(datas){ //var commonObj = this.getCommonObj();
var autoThisObj = this; var length = datas.length;
var htmlStr = ""; if (length > 0) { for (var i = 0; i < length; i++) {
htmlStr += "<div class='item'>" + datas[i] + "</div>";
} //htmlStr = "<div class='resultCss' id='"+ autoThisObj.resultDivId +"'>" + htmlStr + "</div>"; commonObj.jResults.html(htmlStr).show(); //计算单个item的高度
var itemHeight = $(".item").first().outerHeight(); if (length >= 10) {
commonObj.jResults.height(10 * itemHeight);
}
else {
commonObj.jResults.height(length * itemHeight);
} //调整结果div的宽度并定位
autoThisObj.positionDiv(autoThisObj.controlId, autoThisObj.resultDivId); //默认选中第一项
autoThisObj.index = 0;
commonObj.results.childNodes[autoThisObj.index].className = "item chooseItem"; //给结果集中的每一项添加基本事件
commonObj.jResults.find(".item").each(function() {
//点击事件
$(this).on("click", function() {
commonObj.jControl.val($(this).html());
autoThisObj.clearResults();
}); //mouseover事件
$(this).mouseover(function() {
autoThisObj.index = $(this).index();
var results = document.getElementById(autoThisObj.controlId);
var itemCount = document.getElementById(autoThisObj.resultDivId).childNodes.length;
for (var i = 0, len = itemCount; i < len; i++) {
document.getElementById(autoThisObj.resultDivId).childNodes[i].className = "item"; //重置
}
document.getElementById(autoThisObj.resultDivId).childNodes[autoThisObj.index].className = "item chooseItem"; }); });
}
else {
autoThisObj.clearResults();
}
}, /**
* 清空结果div
*/
clearResults: function() {
var results = document.getElementById(this.resultDivId);
var jResults = $(results);
results.innerHTML = "";
jResults.scrollTop(0);
results.style.display = "none";
jResults.css("height", "auto");
this.dynamicDatas = null;
}, //重置结果集
resetResults: function() {
var results = document.getElementById(this.resultDivId);
var jResults = $(results);
jResults.scrollTop(0);
results.style.display = "none";
this.index = 0; var itemCount = results.childNodes.length;
for (var i = 0, len = itemCount; i < len; i++) {
results.childNodes[i].className = "item"; //重置
} results.childNodes[this.index].className = "item chooseItem";
} } //将AutoComplete暴露到全局范围
window.AutoComplete = AutoComplete;
})();

使用方法示例:

 <html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>autoComplete测试</title>
<style type="text/css">
.item /*每一项的样式*/
{
line-height:20px;
height:20px;
padding: 2px;
width:100%;
overflow: hidden;
}
.chooseItem{ /*选中的当前项样式*/
background-color: #008B8B;
color:White;
cursor: pointer;
height:20px;
padding: 2px;
width:100%;
line-height:20px;
}
.resultsCss{ /*匹配结果集容器样式*/
position:absolute;
overflow-y:auto;
overflow-x:hidden;
display:none;
border:solid 1px gray;
background-color:#F0FFFF;
}
</style>
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="autoCompleteHome.js"></script> <body>
<div>
<label for="inpt">请输入:</label><input type="text" id="inpt" />
</div>
<div id="tipList" class="resultsCss">
</div>
<script type="text/javascript">
//方式1:静态数据集
var staticDatas = ["asd","axcv","qwerfd","dfghj","cvbnm","bbghty","ertgb","trefgc","cssdavb","abcdefg","trefgc","cssdavb","abcdefg"];
var autoCompleteOption = {
controlId: "inpt",
resultDivId: "tipList",
circleChoose: "true",
serverEndbled: "false",
useStaticDatas:"true",
datas:staticDatas
}; var auto = new AutoComplete(autoCompleteOption);
auto.init(); //方式2:自定义数据获取函数
/*
var autoCompleteOption = {
controlId: "inpt",
resultDivId: "tipList",
circleChoose: "true",
getCompleteDatas:function(){
var datas = null;
//enter your code to get the data
return datas;
}
}; var auto = new AutoComplete(autoCompleteOption);
auto.init();
*/ //方式3:ajax获取数据
/*
var autoCompleteOption = {
controlId: "inpt",
resultDivId: "tipList",
circleChoose: "true",
resultDivId: "tipList",
ajaxRequestData:{},
serverUrl: "AutoCompleteHandler.ashx"
}; var auto = new AutoComplete(autoCompleteOption);
auto.init();
*/
</script>
</body>
</head>
</html>
04-26 16:18