本文使用「署名 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
新浪微博
新浪微博登陆
新浪邮箱
新浪nba
新浪爱问
新浪体育
新浪博客
新浪爱问共享资料
新浪
新浪邮箱登陆
``` 然后是样式 样式: 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' //鼠标和方向键给于高亮的类名 ``` 写在最后,又写完了一篇。 因为很多内容在之前的两篇,还有之前的注释中提到过,所以不会再次赘述,如果有疑问,不妨先看看之前的内容。 实在无解,可以留言提问,一起学习,一起成长。我觉得内容还是很简单的,尤其是分解动作之后。