在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. App你真的需要么

    随着智能手机.移动路联网的普及,APP火的一塌糊涂,APP应用可谓五花八门,街上经常看到各种推广:扫码安装送东西,送优惠券.仿佛一夜之间一个企业没有自己的APP就跟不上时代了. 有时我在想:APP,你 ...

  2. JavaScript动画-拖拽改变元素大小

    ▓▓▓▓▓▓ 大致介绍 拖拽改变元素大小是在模拟拖拽上增加了一些功能 效果:拖拽改变元素大小 ▓▓▓▓▓▓ 拖拽改变元素大小原理 首先这个方块得知道我们想要改变这个它的大小,所以我给它设定一个范围,当 ...

  3. Gradle 实现 Android 多渠道定制化打包

    Gradle 实现 Android 多渠道定制化打包 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在项目中遇到需要实现 Apk 多渠道.定制化打包, Google .百度查找了一些资料, ...

  4. SQL Server 2016白皮书

    随着SQL Server 2016正式版发布日临近,相关主要特性通过以下预览学习: Introducing Microsoft SQL Server 2016 e-bookSQL Server 201 ...

  5. 在Asp.Net中操作PDF – iTextSharp - 使用表格

    使用Asp.Net生成PDF最常用的元素应该是表格,表格可以帮助比如订单或者发票类型的文档更加格式化和美观.本篇文章并不会深入探讨表格,仅仅是提供一个使用iTextSharp生成表格的方法介绍 使用i ...

  6. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

  7. Autofac - 方法注入

    方法注入, 其实就是在注册类的时候, 把这个方法也注册进去. 那么在生成实例的时候, 会自动调用这个方法. 其实现的方法, 有两种. 准备工作: public interface IAnimal { ...

  8. 满堂红CIO邓劲翔:房屋中介突围

    人脸识别.客户关系管理进度监控.业务流程实时监控.网站访问人数及流量实时监控等实际企业应用场景淋漓尽致.羽羽如生的以大屏幕上图表形式展现在人们面前,如果你不去继续询问,你不会知道这是一家才刚刚在房地产 ...

  9. 小程序用户反馈 - HotApp小程序统计仿微信聊天用户反馈组件,开源

    用户反馈是小程序开发必要的一个功能,但是和自己核心业务没关系,主要是产品运营方便收集用户的对产品的反馈.HotApp推出了用户反馈的组件,方便大家直接集成使用 源码下载地址: https://gith ...

  10. Missing Push Notification Entitlement 问题

    最近打包上传是遇到一个问题: 描述: Missing Push Notification Entitlement - Your app includes an API for Apple's Push ...