A-A+
jquery异步筛选搜索的详解
jquery异步筛选就是通过ajax来实现数据异步传输了,这个问题与ajax用户验证与注册是一样的原理了,只不过搜索会带有条件的,具体如下。
异步筛选搜索(下面简称搜索)是使用ajax发送搜索请求,可追回多个条件,应用的场景很多,比如:百度招聘,逻辑是只有在用户点击相关条件时触发重新请求,只请求内容,从而可以减少http请求和流量。
比如要完成上面百度招聘的搜索功能,先从简单的入手吧~
第一阶段 - 变量
思路是使用js记录下相应的几个变量,然后给元素绑定点击事件,并写入相关的变量,然后发送请求,发送请求的时候带上这些变量,如:
- var 月薪 = null;
- var 福利 = null;
- var 地区 = null;
- ...
- // 单选
- $('月薪').on('click', function(){
- // 让其他同级的类移除高亮className
- // 给当前点击元素添加高亮className
- // 让变量等于当前点击元素的参数
- 月薪 = $(thi).data('id');
- // 发送请求
- });
- // 多选
- $('福利').on('click', function(){
- // 给当前点击元素toggle高亮className
- // 查找当前元素所有的同级的高亮元素,并生成一个以参数为值的数组赋予变量
- var ids = $(this).siblings('.高亮className').map(function(){
- return $(this).data('id');
- }).get().join(',');
- 福利 = ids;
- // 发送请求
- });
- ...
- var 发送请求 = function(){
- $.ajax({
- ...
- data: {
- 福利: 福利,
- 月薪: 月薪,
- ...
- }
- });
- }
在线demo
第二阶段 - 委托
在第一阶段的基础上考虑到委托事件,声明一个缓存对象cache用来存放异步请求时的数据,给所有筛选条件的可点击元素添加data-param-name=参数名用来写入缓存的key,添加data-param-type用来控制选择的类型,比如单选、多选等,添加data-value为选中值,这样就可以写委托了,比如:
- <div id="J-demo">
- <a href="#" data-value="1000" data-param-name="月薪" data-param-type="单选">1000元</a>
- <a href="#" data-value="2000" data-param-name="月薪" data-param-type="单选">2000元</a>
- </div>
- var cache = {};
- $('#J-demo').on('click', 'a', function(){
- var 参数名 = $(this).data('param-name');
- var 类型 = $(this).data('param-type');
- if(类型 === '单选'){
- cache[参数名] = $(this).data('value');
- }
- // 操作高亮的类
- // 发送请求
- });
- var 请求 = function(){
- $.ajax({
- data: cache,
- });
- }
在线demo
第三阶段 - 插件化
当完成了第二阶段后,发现当筛选的条件越来越多,类型越来越多时,很不好处理,并且还会出现重复请求的bug(当在上一个请求还没有完成又点击触发下一个请求),于是我想把搜索+请求这块写一个公用的方法。。。
搜索插件(暂且这么叫吧)只提供设置数据,删除数据,获取数据,发送请求的功能,然后自己逻辑代码就可以写那些什么多选啊、单选啊、文本框啊、下拉什么的了,可是你会发现逻辑代码要远远的大于搜索插件的代码,是的,但起码思路比较明确了,谁只做谁的事~
完整的例子:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <meta name="renderer" content="webkit">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <title>搜索 - 插件化</title>
- <link rel="stylesheet" type="text/css" href="./search.css">
- <style type="text/css">
- .mask-wrap{
- position: relative;
- padding: 20px;
- }
- .mask{
- position: absolute;
- left: 0; top: 0;
- width: 100%;
- background: rgba(255,255,255,0.6) url('//github.xuexb.com/static/img/loading-32-32.gif') no-repeat center center;
- height: 100%;
- display: none;
- }
- .ui-page{
- font-size: 0;
- line-height: 24px;
- padding-top: 10px;
- }
- .ui-page a,
- .ui-page span{
- display: inline-block;
- font-size: 12px;
- padding: 0 10px;
- border: 1px solid #ccc;
- margin-left: 8px;
- text-decoration: none;
- }
- .ui-page a:first-child{
- margin-left: 0;
- }
- .ui-page a:hover,
- .ui-page .current{
- background-color: #09f;
- border-color: #09f;
- color: #fff;
- }
- .ui-page .disabled{
- background-color: #ccc;
- color: #999;
- border-color: #ccc;
- }
- .ui-list li{
- border-top: 1px solid #ddd;
- padding: 10px 0;
- }
- .ui-list li:first-child{
- border-top: none;
- }
- .ui-list-index{
- display: inline-block;
- background-color: #ccc;
- color: #333;
- padding: 0 4px;
- height: 16px; line-height: 16px; text-align: center;
- font-size: 12px;
- margin-right: 8px;
- vertical-align: middle;
- }
- #J-btn{
- display: inline-block;
- width: 100px;
- height: 30px;
- color: #fff;
- background-color: #09f;
- border: none;
- margin-top: 30px;
- }
- </style>
- </head>
- <body>
- <div id="J-demo">
- <dl>
- <dt>
- 名称(单选):
- </dt>
- <dd>
- <ul class="J-param">
- <li data-type="name" class="current">默认</li>
- <li data-type="name" data-id="baidu">百度</li>
- <li data-type="name" data-id="360">360</li>
- <li data-type="name" data-id="google">谷歌</li>
- <li data-type="name" data-id="facebook">非死不可</li>
- <li data-type="name" data-id="facebook">推特</li>
- </ul>
- </dd>
- <dt>
- 排序(多选):
- </dt>
- <dd>
- <ul class="J-params">
- <li data-type="order" class="current">默认</li>
- <li data-type="order" data-id="time">时间</li>
- <li data-type="order" data-id="id">id</li>
- <li data-type="order" data-id="name">名称</li>
- </ul>
- </dd>
- <dt>
- 视图(单选):
- </dt>
- <dd>
- <ul class="J-param">
- <li data-type="view" class="current">默认</li>
- <li data-type="view" data-id="code">代码</li>
- <li data-type="view" data-id="full">全屏</li>
- <li data-type="view" data-id="test">测试</li>
- </ul>
- </dd>
- <dt>
- 分页大小:
- </dt>
- <dd>
- <select name="" id="J-page_size">
- <option value="10" selected>10条</option>
- <option value="20">20条</option>
- <option value="30">30条</option>
- <option value="46">46条</option>
- </select>
- </dd>
- <dt>
- 后端返回多少条(模拟分页):
- </dt>
- <dd>
- <select name="" id="J-total">
- <option value="100" selected>100条</option>
- <option value="200">200条</option>
- <option value="300">300条</option>
- <option value="518">518条</option>
- </select>
- </dd>
- <dt>
- 关键词:
- </dt>
- <dd>
- <input type="text" id="J-query">
- </dd>
- <dt>快速跳页:</dt>
- <dd>
- <input type="text" id="J-quick-page"><button id="J-quick-btn">跳转</button>
- </dd>
- <dd>
- <button id="J-btn">搜索</button>
- </dd>
- </dl>
- </div>
- <hr>
- <h2>结果:</h2>
- <div class="mask-wrap">
- <div id="J-write">请点击...为了测试响应,故意让后端响应时间为1500ms</div>
- <div class="mask"></div>
- </div>
- <script type="text/javascript" src="//code.jquery.com/jquery-1.11.3.js"></script>
- <script type="text/javascript" src="search.js"></script>
- <script type="text/javascript">
- var renderTpl = function(res){
- var html = '';
- resres.items = res.items || [];
- if(res.items.length){
- $.each(res.items, function(){
- html += '<li><span class="ui-list-index">'+ this.index +'</span>'+ this.content +'</li>';
- });
- if(XXOO.get('query')){
- htmlhtml = html.replace(new RegExp(XXOO.get('query'), 'g'), function($0){
- return '<mark>'+ $0 +'</mark>';
- });
- }
- } else {
- html = '<li>真空~</li>';
- }
- return '<ul class="ui-list">'+ html + '</ul>';
- }
- var renderPage = function(res){
- var page_size = parseInt(XXOO.get('page_size'), 10) || 10;
- var pageCount = Math.ceil(res.total / page_size);
- var page = parseInt(XXOO.get('page'), 10) || 1;
- var str = '';
- var i = 1;
- if (pageCount > 1 && page <= pageCount) {
- if (page > 1) {
- str += '<a href="#" data-page="' + (page - 1) + '">上一页</a>';
- } else {
- str += '<span class="disabled">上一页</span>';
- }
- if (pageCount < 7) {
- for (i; i <= pageCount; i++) {
- if (page === i) {
- str += '<span class="current">' + i + '</span>';
- } else {
- str += '<a href="#" data-page="' + (i) + '">' + i + '</a>';
- }
- }
- } else {
- var start, end;
- if (page === 1) {
- str += '<span class="current">1</span>';
- } else {
- str += '<a href="#" data-page="' + (1) + '">1</a>';
- }
- if (page > 4) {
- str += '<span class="dot">...</span>';
- }
- if (page < 5) {
- start = 1;
- } else {
- start = page - 2;
- }
- if (page > (pageCount - 4)) {
- end = pageCount;
- } else {
- end = page + 3;
- }
- for (var i2 = start; i2 < end; i2++) {
- if (i2 !== 1 && i2 !== pageCount) { //避免重复输出1和最后一页
- if (i2 === page) {
- str += '<span class="current">' + i2 + '</span>';
- } else {
- str += '<a href="#" data-page="' + (i2) + '">' + i2 + '</a>';
- }
- }
- }
- if (page < (pageCount - 4)) {
- str += '<span class="dot">...</span>';
- }
- if (page === pageCount) {
- str += '<span class="current">' + pageCount + '</span>';
- } else {
- str += '<a href="#" data-page="' + (pageCount) + '">' + pageCount + '</a>';
- }
- start = end = null;
- }
- if (page < pageCount) {
- str += '<a href="#" data-page="' + (page + 1) + '">下一页</a>';
- } else {
- str += '<span class="disabled">下一页</span>';
- }
- str = '<div class="ui-page">' + str + '</div>';
- }
- return str;
- }
- var XXOO = new Search({
- url: '/api/search/index',
- success: function(res){
- var html = '';
- html += renderTpl(res);
- html += renderPage(res);
- $('#J-write').html(html);
- },
- error: function(){
- $('#J-write').text('出错了');
- },
- beforeSend: function(){
- $('.mask').stop().fadeIn();
- },
- complete: function(){
- $('.mask').stop().fadeOut();
- }
- });
- // 单选
- $('.J-param').on('click', 'li', function(){
- var id = $(this).data('id');
- var type = $(this).data('type');
- $(this).addClass('current').siblings().removeClass('current');
- if(!id){
- XXOO.del(type);
- } else {
- XXOO.set(type, id);
- }
- XXOO.request();
- });
- // 多选
- $('.J-params').on('click', 'li', function(){
- var id = $(this).data('id');
- var type = $(this).data('type');
- var ids;
- // 处理是否点击的默认,如果是默认则清除其他的
- // 不是默认
- if(id){
- $(this).toggleClass('current');
- ids = $(this).parent().children('.current').map(function(){
- return $(this).data('id');
- }).get().join(',');
- // 如果一个高亮的标签也没有
- if(!ids){
- $(this).parent().children().eq(0).addClass('current');
- XXOO.del(type);
- } else {
- $(this).parent().children().eq(0).removeClass('current');
- XXOO.set(type, ids);
- }
- } else {
- XXOO.del(type);
- $(this).addClass('current').siblings().removeClass('current');
- }
- XXOO.request();
- });
- // 分页大小
- $('#J-page_size').on('change', function(){
- XXOO.set('page_size', this.value).set('page', 1).request();
- });
- // 总数
- $('#J-total').on('change', function(){
- XXOO.set('total', this.value).set('page', 1).request();
- });
- // 关键词
- $('#J-query').on('blur', function(){
- XXOO.set('query', this.value);
- });
- // 搜索
- $('#J-btn').on('click', function(){
- XXOO.request();
- });
- // 分页
- $('#J-write').on('click', '.ui-page a', function(){
- XXOO.set('page', this.getAttribute('data-page')).request();
- return false;
- });
- // 快速跳页
- var $quickPage = $('#J-quick-page');
- $('#J-quick-btn').on('click', function(){
- var value = $quickPage.val();
- if(!value){
- $quickPage.focus();
- } else if(!/^\d+$/.test(value)){
- $quickPage.focus().select();
- } else {
- value = parseInt(value, 10) || 1;
- XXOO.set('page', value).request();
- }
- });
- </script>
- </body>
- </html>
上面没有提供php处理脚本这个非常的简单就不介绍了。