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

代码如下:(已更新,最新代码请参考: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>

自己手写的自动完成js类的更多相关文章

  1. 手写代码自动实现自动布局,即Auto Layout的使用

    手写代码自动实现自动布局,即Auto Layout的使用,有需要的朋友可以参考下. 这里要注意几点: 对子视图的约束,若是基于父视图,要通过父视图去添加约束. 对子视图进行自动布局调整,首先对UIVi ...

  2. 手写SpringBoot自动配置及自定义注解搭配Aop,实现升级版@Value()功能

    背景 项目中为了统一管理项目的配置,比如接口地址,操作类别等信息,需要一个统一的配置管理中心,类似nacos. 我根据项目的需求写了一套分布式配置中心,测试无误后,改为单体应用并耦合到项目中.项目中使 ...

  3. 手写网页扫雷之js部分(vue)

    var vm = new Vue({ el:"#ui", data(){ return{ num:0, saoleiStyle:{ width: "0px", ...

  4. 一套手写ajax加一般处理程序的增删查改

    倾述下感受:8天16次驳回.这个惨不忍睹. 好了不说了,说多了都是泪. 直接上代码 : 这个里面的字段我是用动软生成的,感觉自己手写哪些字段太浪费时间了,说多了都是泪 ajax.model层的代码: ...

  5. idea中自动生成实体类

    找到生成实体的路径,找到Database数据表 找到指定的路径即可自动生成entity实体 在创建好的实体类内如此修改 之后的步骤都在脑子里  写给自己看的东西 哪里不会就记录哪里 test类(以前都 ...

  6. 待实践二:MVC3下的3种验证 (1)前台 jquery validate验证 (2)MVC实体验证 (3)EF生成的/自己手写的 自定义实体校验(伙伴类+元素据共享)

    MVC3下的3种验证 (1):前台Jquery Validate脚本验证 引入脚本 <script src="../js/jquery.js" type="text ...

  7. 2019前端面试系列——JS高频手写代码题

    实现 new 方法 /* * 1.创建一个空对象 * 2.链接到原型 * 3.绑定this值 * 4.返回新对象 */ // 第一种实现 function createNew() { let obj ...

  8. 几道JS代码手写面试题

    几道JS代码手写面试题   (1) 高阶段函数实现AOP(面向切面编程)    Function.prototype.before = function (beforefn) {        let ...

  9. 手写一个类SpringBoot的HTTP框架:几十行代码基于Netty搭建一个 HTTP Server

    本文已经收录进 : https://github.com/Snailclimb/netty-practical-tutorial (Netty 从入门到实战:手写 HTTP Server+RPC 框架 ...

随机推荐

  1. Python列表去重

    标题有语病,其实是这样的: 假设有两个列表 : L1 = [1,2,3,4] ; L2 = [1,2,5,6] 然后去掉L1中包含的L2的元素 直接这样当然是不行的: def removeExists ...

  2. Python标准库--typing

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python 3.5 增加了一个有意思的库--typ ...

  3. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  4. Web安全相关(四):过多发布(Over Posting)

    简介 过多发布的内容相对比较简单,因此,我只打算把原文中的一些关键信息翻译一下.原文链接如下: http://www.asp.net/mvc/overview/getting-started/gett ...

  5. 如何优化coding

    如何优化coding 前言 最近一直在做修改bug工作,修改bug花费时间最多的不是如何解决问题而是怎样快速读懂代码.如果代码写的好的,不用debug就可以一眼看出来哪里出了问题.实际上,我都要deb ...

  6. .NET应用程序域

    在.NET平台下,可执行程序并没有直接承载在Windows进程中,而非托管程序是直接承载的..NET可执行程序承载在进程的一个逻辑分区中,称之为应用程序域(AppDomain).一个进程可以包含多个应 ...

  7. CSS三个定位——常规、浮动、绝对定位

    .dage { width: 868px; background: #5B8C75; border: 10px solid #A08C5A; margin-top: -125px; margin-le ...

  8. Ubuntu(Linux) + mono + xsp4 + nginx +asp.net MVC3 部署

    折腾了一下,尝试用Linux,部署mvc3. 分别用过 centos 和 ubuntu ,用ubuntu是比较容易部署的. 操作步骤如下: 一.终端分别如下操作 sudo su ->输入密码 a ...

  9. Angular2学习笔记——NgModule

    在Angular2中一个Module指的是使用@NgModule修饰的class.@NgModule利用一个元数据对象来告诉Angular如何去编译和运行代码.一个模块内部可以包含组件.指令.管道,并 ...

  10. "过期不候"--具备生命周期的数据的技术实现方案

    "过期不候"--具备生命周期的数据的技术实现方案 1   引言 本文可以作为之前的一个 原理性文章 对应的 技术实现部分 . 此处给出其上文的直达电梯: http://www.cn ...