本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh)
本文作者: 苏洋
创建时间: 2012年11月30日
统计字数: 2958字
阅读时间: 6分钟阅读
本文链接: https://soulteary.com/2012/11/30/jquery-auto-complete.html
-----
# jQuery 笔记:自动完成
高手莫入,浅显例子而已。最近在更换项目中的javascript库,觉得如果能把实践的过程记录下来,应该可以帮助到一些对javascript感兴趣的前端初学者。
![jquery-auto-complete](https://attachment.soulteary.com/2012/11/30/jquery-auto-complete.png "jquery-auto-complete")
每天使用百度,google,有的时候,你的网站或许需要一个自动完成的功能。 你可以在这个例子中,搜索新浪,或者苏洋,因为是测试PHP,没有数据库请求操作,只有简单的两个词库,你懂的。
完整例子: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/
首先还是页面结构
结构草图: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/step1.html
```html
JQUERY AUTO COMPLETE
```
然后是样式
样式: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/step2.html
```css
*,html,body,div,ul,li,h3,p{
margin: 0;
padding: 0;
}
body, input{
font-size: 12px;
font-family: arial,\5b8b\4f53,sans-serif;
}
body{
background-color:#F7F7F7;
}
div,ul,li,h3,p{
float: left;
display: block;
}
div#warp{
width: 460px;
height: 200px;
top: 50%;
left: 50%;
position: absolute;
margin-left: -200px;
border: 1px solid #E0E0E0;
margin-top: -200px;
background-color: #FEFEFE;
}
div#warp div#logo{
background: url(logo.png) 0 0 no-repeat;
width: 220px;
height: 64px;
margin: 20px 0 0 130px;
position: absolute;
}
div#warp input[type=text]{
display: block;
float: left;
}
div#warp input[type=text]#autocomplete{
width: 200px;
height: 22px;
padding: 4px 7px;
font: 16px arial;
border: 1px solid #CDCDCD;
border-color: #9A9A9A #CDCDCD #CDCDCD #9A9A9A;
vertical-align: top;
outline: none;
margin: 100px 0 0 110px;
background-color: white;
}
/*这里JS处理*/
div#warp div#result{
border: 1px solid #817F82;
position: relative;
margin: 0 0 0 110px;
text-align: left;
-webkit-user-select: none;
float: left;
clear: left;
min-width: 216px;
display: none;/*列表首先弄掉,有数据再显示*/
}
div#result table{
width: 100%;
background: white;
cursor: pointer;
border-collapse: collapse;
border-spacing: 0;
}
div#result table td {
color: black;
font: 14px arial;
height: 25px;
line-height: 25px;
padding: 0 8px;
text-align: left;
}
div#result table tr.hover {
background-color: #e2eaff
}
div#result table td b {
color: black;
}
div#warp input[type=submit]#search{
display: block;
float: left;
margin: 90px 0 0 5px;
height: 32px;
cursor: pointer;
background: url(btn.png) 0 0 no-repeat;
border: 0;
}
div#warp input[type=submit]#search{
display: block;
float: left;
margin: 100px 0 0 5px;
height: 32px;
cursor: pointer;
background: url(btn.png) 0 0 no-repeat;
border: 0;
padding: 0 14px;
font-size: 14px;
width: 53px;
}
div#warp input[type=submit]#search.clicked{
background-position: -55px 0;
}
```
接着一边想需求,一边实现吧。
基本事件: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/step3.html
```js
//首先是搜索按钮
//搜索框样式设置以及按钮事件
var searchBtn = $('#search');
//鼠标按下
searchBtn.bind('mousedown',function(){
$(this).addClass('clicked');
//开始搜索
//搜索代码...
});
//鼠标移出
//可以体会一下,如果不绑定这个事件
//(只是在鼠标按下的时候去掉样式)
//在按钮按下鼠标,并移出按钮
searchBtn.bind('mouseout',function(){
$(this).removeClass('clicked');
});
//结果输出
//缓存所有的表格tr元素
var target = $('table#wordlst').find('tr');
//元素数量
var count = target.length;
//if (!count) {return;}
//获取要事件委托的容器
var warp = $('table#wordlst').find('tbody');
//当前有焦点的元素项
var hover = 'hover';
//当前选择的元素的序号
//默认0是无,范围1~元素数量
var index = 0;
//初始化搜索关键词
var keyword = '';
//显示候选词列表
var showWordList = function(arrow){
var warp = $('div#result');
if (!arrow) {
//重置焦点为0
index = 0;
//去掉所有的焦点hover
for(var i=0;itbody的鼠标移动事件
warp.bind('mouseover', function(e){
var cur = e.target;
//去掉所有其他元素的class中的hover
target.removeClass(hover);
//给每个元素的父级最上层元素加hover
$(cur).parentsUntil(warp).addClass(hover);
//通过循环获取当前有焦点的项目的序号
for(var i=0;itbody的鼠标点击事件
target.bind('click',function(e){
var cur = e.target;
target.removeClass(hover);
//将选择的元素的内容赋值给关键词
keyword = $(cur).parentsUntil(warp)[0].innerText;
//调用搜索
//搜索代码...
event.stopPropagation();
});
//绑定搜索框事件
//鼠标按下,方向键上,方向键下,ESC,回车
$('#autocomplete').bind('keydown', function(e){
//根据index数值来进行候选下拉菜单的高亮
var userSelect = function(){
for(var i=0;i count-1) {
index = 1;
} else {
index++;
}
//调用函数userSelect
//设置选中项目高亮
//以及对关键词赋值
userSelect();
//显示候选词
showWordList(true);
break;
case 13:
//回车触发搜索
if (index==0) {
//没有展示下拉选择框
keyword = $('#autocomplete')[0].value;
}else{
//选择备选
keyword = $(target[index-1])[0].innerText;
}
//刷新备选列表
//刷新列表代码...
//调用搜索
//搜索代码...
break
case 27:
//触发ESC
//关闭下拉框
//并把index设置为0
index=0;
}
e.stopPropagation();
});
if($.browser.msie){
$('#autocomplete').on('propertychange', function(){
//动态赋值,匹配计算
keyword = $('#autocomplete')[0].value;
showWordList();
});
}else{
$('#autocomplete').on('input', function(){
//动态复制,匹配计算
keyword = $('#autocomplete')[0].value;
showWordList();
});
}
```
之前一直是用写死的table数据,实际使用中,我们需要从服务器后端获取数据,所以呢。
接下来开始优化之前的代码和进行数据绑定。
要交互数据,首先要设计数据格式。
```json
{
query:"新浪",
result:["新浪微博",
"新浪微博登陆",
"新浪邮箱",
"新浪nba",
"新浪爱问",
"新浪体育",
"新浪博客",
"新浪爱问共享资料",
"新浪网",
"新浪邮箱登陆"]
}
```
接下来用这份数据,生成我们需要的表格。
顺便把刚刚随便想到的草稿代码,整理一下
输出数据: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/step4.html
```js
var searchBtn = $('#search');
searchBtn.bind('mousedown',function(){
$(this).addClass('clicked');
//开始搜索
//搜索代码...
});
searchBtn.bind('mouseout',function(){
$(this).removeClass('clicked');
});
window.$SAE = [];
//给一份默认的热词列表,优化体验
window.$SAE['QUERY_WORD'] =
{
query:"新浪",
result:[["新浪微博!","http://weibo.com/"],
"新浪微博登陆!",
"新浪邮箱!",
"新浪nba",
"新浪爱问",
"新浪体育",
"新浪博客",
"新浪爱问共享资料",
"新浪网",
"新浪邮箱登陆"]
};
//自动完成列表
var autoWordList = $('table#wordlst');
//输入框
var autoWordText = $('#autocomplete');
//为输入框光标缓存一份
var textCursor = document.getElementById('autocomplete');
var warp = autoWordList.find('tbody');
var warpBox = autoWordList.parent();
var target = autoWordList.find('tr');
var count = target.length;
//搜索设置
var searchHost = 'http://www.baidu.com/s?wd=';
//JSON数据接口
var dataHost = 'http://localhost/query.php?';
var keyWord = '';
var sarchPrefix = '+site:sae.sina.com.cn';
var newWindows = true;
//输入框限制搜索内容长度
var keyWordMaxLen = 100 -1;
//延时执行的句柄,用来取消不需要执行的内容
var handle =null;
var hover = 'hover';
var index = 0;
//显示下拉菜单
var doDropMenu = function(arrow){
//如果被用户用ESC清空数据
if (target==null) {return;}
if (!arrow) {
index = 0;
for(var i=0;iDOM
//然后操作,其实既然选择JQ
//那么就通用操作都用JQ的封装方法吧
keyWord = curTR.text();
if (curTR.attr('data-url')) {
//如果包含直接的定义跳转,就不去进行搜索
//使用新的搜索函数进行搜索
doSearch('',curTR.attr('data-url'));
}else{
//调用搜索
doSearch(keyWord);
}
event.stopPropagation();
}
//高亮要搜索的内容
//使用B标签是因为节约流量(@百度)
var wordHighlight = function(word, keyARR){
//然后把其他的字符都高亮
for(var xx in keyARR){
//排除被分割的字符是空
if (keyARR[xx] !== '') {
word = word.replace(keyARR[xx], ''+keyARR[xx]+'');
}
}
return word;
}
//将原来的过程封装入函数
//利于延时执行
var coreQuery = function(){
//获取内容之前打扫卫生
doClean();
//因为是延时执行,所以用户碰巧在一瞬间把内容清空
//延时的任务还是进行了,所以要判断内容是否为空
if(autoWordText.val() == ''){return;}
//重新获得并整理数据
var data = $SAE.QUERY_WORD;
var query = data.query;
var list = data.result;
if (list.length == 0) {return;}
//最后要输出的HTML变量/
//和处理过程中用的临时数组
var tmpSTR = '';
var tmpLine = '';
var strHTML = [];
for(var oo in list){
//把内容分割为数组根据内容
if(list[oo] instanceof Array){
tmpSTR = list[oo][0];
}else{
tmpSTR = list[oo];
}
//用输入的内容分割字符串,排除要高亮的部分
//先把内容替换为***关键词*** |
//对内容进行高亮处理
tmpLine = tmpSTR.replace(query, ''+query+'');
tmpLine = wordHighlight(tmpLine, tmpSTR.split(query));
//对内容进行链接处理
if(list[oo] instanceof Array){
tmpLine =''+tmpLine+' |
';
}else{
tmpLine =''+tmpLine+' |
';
}
//把整理好的内容添加到要输出的数组中
strHTML.push(tmpLine);
}
//把整理好的数组合并字符串添加到文档
autoWordList.html(strHTML.join(''));
//展示自动完成的列表
warpBox.show();
//刷新数据
//重新获取数据并绑定事件
warp = autoWordList.find('tbody');
target = autoWordList.find('tr');
count = target.length;
warp.bind('mouseover', onLine);
target.bind('click',clickLine);
}
//添加延时执行,给用户一个选择的机会
//如果用户在搜索新内容前移动光标则取消请求
//而且可以降低服务器被请求数量
//清除上一次延时任务
clearTimeout(handle);
//进入延时执行...
//从服务器取数据
$.doJSON();
handle = setTimeout(coreQuery,300);
//延时执行完毕...
}
//限制输入最大长度
var limitTextCount = function(){
var safeSTR = autoWordText.val();
//如果内容是空,不进行处理
if (safeSTR=='') {return;}
if(safeSTR.length>keyWordMaxLen){
autoWordText.val(safeSTR.substring(0,keyWordMaxLen));
}
}
autoWordText.bind('keydown', function(e){
//如果按下ESC清空了缓存
if(target == null){return;}
var userSelect = function(){
for(var i=0;i count-1) {
index = 1;
} else {
index++;
}
userSelect();
doDropMenu(true);
break;
case 13:
if (index==0) {
keyWord = autoWordText.val();
}else{
keyWord = $(target[index-1]).text();
}
//内容不为空的时候
if (keyWord !=''){
//调用搜索
doSearch(keyWord);
}
break
case 27:
index=0;
//按下ESC后,应该清空上一次的搜索结果
//而且既然ESC,那么就是重新输入需求
//取消要进行的刷新数据
clearTimeout(handle);
//并且打扫卫生
doClean();
break;
default:
//限制输入的长度
limitTextCount();
}
e.stopPropagation();
});
//内容改变不仅仅是按键输入,还有CTRL+V
autoWordText.bind('paste', function(){
limitTextCount();
});
if($.browser.msie){
autoWordText.on('propertychange', function(){
//刷新备选列表
doQuery();
keyWord = autoWordText.val();
//如果关键词为空,那么不展示下拉菜单
if (keyWord!=='') {
doDropMenu();
}
});
}else{
autoWordText.on('input', function(){
//刷新备选列表
doQuery();
keyWord = autoWordText.val();
//如果关键词为空,那么不展示下拉菜单
if (keyWord!=='') {
doDropMenu();
}
});
}
```
感觉是不是距离成品越来越近了,那么把没有做的功能加上,关键是可以从数据源取数据。
优化和抽象: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/step5.html
```js
var searchBtn = $('#search');
searchBtn.bind('mousedown',function(){
$(this).addClass('clicked');
//开始搜索
//搜索代码...
});
searchBtn.bind('mouseout',function(){
$(this).removeClass('clicked');
});
window.$SAE = [];
//给一份默认的热词列表,优化体验
window.$SAE['QUERY_WORD'] =
{
query:"新浪",
result:[["新浪微博!","http://weibo.com/"],
"新浪微博登陆!",
"新浪邮箱!",
"新浪nba",
"新浪爱问",
"新浪体育",
"新浪博客",
"新浪爱问共享资料",
"新浪网",
"新浪邮箱登陆"]
};
//自动完成列表
var autoWordList = $('table#wordlst');
//输入框
var autoWordText = $('#autocomplete');
//为输入框光标缓存一份
var textCursor = document.getElementById('autocomplete');
var warp = autoWordList.find('tbody');
var warpBox = autoWordList.parent();
var target = autoWordList.find('tr');
var count = target.length;
//搜索设置
var searchHost = 'http://www.baidu.com/s?wd=';
//JSON数据接口
var dataHost = 'http://localhost/query.php?';
var keyWord = '';
var sarchPrefix = '+site:sae.sina.com.cn';
var newWindows = true;
//输入框限制搜索内容长度
var keyWordMaxLen = 100 -1;
//延时执行的句柄,用来取消不需要执行的内容
var handle =null;
var hover = 'hover';
var index = 0;
//显示下拉菜单
var doDropMenu = function(arrow){
//如果被用户用ESC清空数据
if (target==null) {return;}
if (!arrow) {
index = 0;
for(var i=0;iDOM
//然后操作,其实既然选择JQ
//那么就通用操作都用JQ的封装方法吧
keyWord = curTR.text();
if (curTR.attr('data-url')) {
//如果包含直接的定义跳转,就不去进行搜索
//使用新的搜索函数进行搜索
doSearch('',curTR.attr('data-url'));
}else{
//调用搜索
doSearch(keyWord);
}
event.stopPropagation();
}
//高亮要搜索的内容
//使用B标签是因为节约流量(@百度)
var wordHighlight = function(word, keyARR){
//然后把其他的字符都高亮
for(var xx in keyARR){
//排除被分割的字符是空
if (keyARR[xx] !== '') {
word = word.replace(keyARR[xx], ''+keyARR[xx]+'');
}
}
return word;
}
//将原来的过程封装入函数
//利于延时执行
var coreQuery = function(){
//获取内容之前打扫卫生
doClean();
//因为是延时执行,所以用户碰巧在一瞬间把内容清空
//延时的任务还是进行了,所以要判断内容是否为空
if(autoWordText.val() == ''){return;}
//重新获得并整理数据
var data = $SAE.QUERY_WORD;
var query = data.query;
var list = data.result;
if (list.length == 0) {return;}
//最后要输出的HTML变量/
//和处理过程中用的临时数组
var tmpSTR = '';
var tmpLine = '';
var strHTML = [];
for(var oo in list){
//把内容分割为数组根据内容
if(list[oo] instanceof Array){
tmpSTR = list[oo][0];
}else{
tmpSTR = list[oo];
}
//用输入的内容分割字符串,排除要高亮的部分
//先把内容替换为***关键词*** |
//对内容进行高亮处理
tmpLine = tmpSTR.replace(query, ''+query+'');
tmpLine = wordHighlight(tmpLine, tmpSTR.split(query));
//对内容进行链接处理
if(list[oo] instanceof Array){
tmpLine =''+tmpLine+' |
';
}else{
tmpLine =''+tmpLine+' |
';
}
//把整理好的内容添加到要输出的数组中
strHTML.push(tmpLine);
}
//把整理好的数组合并字符串添加到文档
autoWordList.html(strHTML.join(''));
//展示自动完成的列表
warpBox.show();
//刷新数据
//重新获取数据并绑定事件
warp = autoWordList.find('tbody');
target = autoWordList.find('tr');
count = target.length;
warp.bind('mouseover', onLine);
target.bind('click',clickLine);
}
//添加延时执行,给用户一个选择的机会
//如果用户在搜索新内容前移动光标则取消请求
//而且可以降低服务器被请求数量
//清除上一次延时任务
clearTimeout(handle);
//进入延时执行...
//从服务器取数据
$.doJSON();
handle = setTimeout(coreQuery,300);
//延时执行完毕...
}
//限制输入最大长度
var limitTextCount = function(){
var safeSTR = autoWordText.val();
//如果内容是空,不进行处理
if (safeSTR=='') {return;}
if(safeSTR.length>keyWordMaxLen){
autoWordText.val(safeSTR.substring(0,keyWordMaxLen));
}
}
autoWordText.bind('keydown', function(e){
//如果按下ESC清空了缓存
if(target == null){return;}
var userSelect = function(){
for(var i=0;i count-1) {
index = 1;
} else {
index++;
}
userSelect();
doDropMenu(true);
break;
case 13:
if (index==0) {
keyWord = autoWordText.val();
}else{
keyWord = $(target[index-1]).text();
}
//内容不为空的时候
if (keyWord !=''){
//调用搜索
doSearch(keyWord);
}
break
case 27:
index=0;
//按下ESC后,应该清空上一次的搜索结果
//而且既然ESC,那么就是重新输入需求
//取消要进行的刷新数据
clearTimeout(handle);
//并且打扫卫生
doClean();
break;
default:
//限制输入的长度
limitTextCount();
}
e.stopPropagation();
});
//内容改变不仅仅是按键输入,还有CTRL+V
autoWordText.bind('paste', function(){
limitTextCount();
});
if($.browser.msie){
autoWordText.on('propertychange', function(){
//刷新备选列表
doQuery();
keyWord = autoWordText.val();
//如果关键词为空,那么不展示下拉菜单
if (keyWord!=='') {
doDropMenu();
}
});
}else{
autoWordText.on('input', function(){
//刷新备选列表
doQuery();
keyWord = autoWordText.val();
//如果关键词为空,那么不展示下拉菜单
if (keyWord!=='') {
doDropMenu();
}
});
}
```
这里随便写了一个PHP,模拟输出搜索内容的返回数据
```php
```
最后就是为了以后的复用,插件化
插件化: http://thecdn.sinaapp.com/page/demo/jq-auto-complete/index.html
调用方法还是简单的
```js
$('#warp').autoComplete({
wdList:'table#wordlst', //下拉列表
wdText:'#autocomplete', //输入框
engine:'http://www.baidu.com/s?wd=', //搜索引擎地址
kwFix:'+site:sae.sina.com.cn', //搜索引擎参数
djson:'http://localhost/query.php?', //数据源地址
wdMax:100, //最多输入字符
newWin:false, //在新窗口打开
wait:300, //延时操作(毫秒)
gData:$SAE['QUERY_WORD'], //全局变量
hover:'hover' //鼠标和方向键给于高亮的类名
```
写在最后,又写完了一篇。
因为很多内容在之前的两篇,还有之前的注释中提到过,所以不会再次赘述,如果有疑问,不妨先看看之前的内容。
实在无解,可以留言提问,一起学习,一起成长。我觉得内容还是很简单的,尤其是分解动作之后。