项目中使用了 combo select,为缺省的select增长模糊搜索的功能,一直运行得很好。html
但最近碰到一个大数据量的select:初始化加载的数据项有2000多个。咱们采用的是ajax读取全部的option json,并由js在浏览器中遍历并最终生成完整的html。当数据量变大的时候,ajax读取数据和浏览器处理数据都会有比较明显的损耗,页面初始化时须要较长时间,下降了用户友好度。前端
你们简单分析了一下这个问题,想到了三种可能的解决方案。git
目前的同级数据有2000多条,数据从逻辑上能够拆分为两级结构。这样,将数据拆分为两级结构后,使用两个联动Select,能大大减小每一个select加载的option数量。github
由于采用分布式部署,这些数据实际上通过了屡次服务器之间的传输。数据量大,每一级传输耗时增长,致使最终的耗时难于接受。ajax
能够在api server端利用nosql对数据进行缓存,能在必定程度上下降耗时。redis
从前端入手,select只显示少许数据,当用户输入关键字进行搜索时,实时从服务器加载。这种方式增长了调用次数,但能够大大下降数据量,缩短页面加载的耗时。sql
三种方案,都能在必定程度上解决问题。咱们决定先从combo select插件尝试,若是达不到效果,再考虑redis缓存或修改数据结构的方案。json
网址 https://github.com/PebbleRoad/combo-select ,感谢提供如此优秀的一个插件。api
首先在页面中构建一个select,并初始化option数据,而后调用脚本浏览器
$("#selectId").comboSelect(); |
其余更复杂的功能,请自行前往官网学习。
Combo Select在执行时,在原 select 外层套了一个 <div class=”combo-select”>,而后在select后面添加了三个element。
div.combo-arrow,是下拉箭头
ul.combo-dropdown是用来显示的下拉列表
input.combo-input 是用来输入模糊搜索内容的输入框
并经过修改原 select 的属性,隐藏掉。
combo select初始化时,通过一系列代码,最终构造几个属性:
$container : 生成一个新的div,将原来的select和新生成的ul等都放在其中。
$el : 初始的select element
$options : 全部的option 数据
$dropdown : 生成的 ul.combo-dropdown 对象
$items : 全部的options转成 li 格式后的数据。
下图是数据模型和html元素之间的对应关系。
在js插件的代码function Plugin ( element, options )会完成插件的初始化,根据select当前的数据,完成html元素的调整,以及js数据模型的初始化。初始化流程以下
当用户在input中输入文字的时候,会触发 keydown和keyup事件,在keyup事件中,对 $items中的数据依次进行匹配,设置 visible属性,实现部分数据的展现。
在这个过程当中,原始的select($el)及其全部的options($options)没有变化,下拉列表的变化,主要是将ul.li($items)设置为可见或不可见。
整个修改方案,分别从Server API、js组件、前端调用三方面解决。
Server端须要提供根据名称进行模糊搜索的接口。不赘述,须要注意的是返回数据要设置最大条数。避免根据查询条件返回了大量的数据,就失去了解决的优点。
限制最大条数后,须要跟产品介绍清楚这个实现逻辑,若是用户输入的关键字区分度不大时,可能没法查到真正须要的数据;此时须要用户输入更具备区分度的关键字。
修改keyup事件时的逻辑:原来是分别设置ul.li是否可见,修改成从新加载select的全部options,并根据options重建$items,并设置为全部ul.li都是可见的。
entity: 'entity', itemName: 'itemName', curItemField: 'curItemCode', curItemValue: '', curItemName: '', url: '', limit: 7 |
修改了原组件的这个方法,判断是否设置了服务器端刷新的url。若是没设置,沿用原来的逻辑;若是设置了,根据用户输入进行模糊查询,并从新生成浏览器中被隐藏的select的全部options,并更新到$dropdown中。
if(self.settings.url != ''){ // 准备调用api须要的json数据 var self = this; var ajaxData = { "paging": true, "offset": 0, "limit": self.settings.limit }; if(self.settings.itemName != '' && needle != ''){ ajaxData[self.settings.itemName] = needle; } if(self.settings.curItemField != '' && self.settings.curItemValue != ''){ ajaxData[self.settings.curItemField] = self.settings.curItemValue; }
// 从服务器查询数据 $.ajax({ url : self.settings.url, type : 'post', data: ajaxData, success : function(data) { var obj = $.parseJSON(data);
// 先删掉select原来的数据,并遍历查询结果生成option添加到select中 var dropdownHtml = '', k = 0, p = ''; self.$el.empty(); self.$el.append("<option value=''>请选择</option>");
var confirmedValue; self.$dropdown.html("<li class='option-item' data-index='0' data-value=''>请选择</li>"); for (var i = 0; i < obj.length; i++) { var itemCode; var itemName; var itemExtraCode; if(self.settings.entity == 'entity'){ itemCode = obj[i].entityCode; itemName = obj[i].entityName; itemExtraCode = obj[i].entityShortName; }else{ itemCode = obj[i].itemCode; itemName = obj[i].itemName; itemExtraCode = itemCode; }
// 生成select option var oneOption = $("<option></option>"); $(oneOption).val(itemCode); $(oneOption).html(itemName); if(itemCode == self.settings.curItemValue || itemExtraCode == self.settings.curItemValue || itemName == self.settings.curItemName){ $(oneOption).attr("selected", "selected"); self.settings.curItemValue = itemCode; confirmedValue = itemCode; } self.$el.append(oneOption);
if(confirmedValue != undefined && confirmedValue != ''){ self.$el.val(confirmedValue); }
// 生成$dropdown 中的li var oneItem = $("<li></li>"); $(oneItem).attr("class",this.disabled? self.settings.disabledClass : "option-item"); $(oneItem).attr("data-index", (i+1)); $(oneItem).attr("data-value", itemCode); $(oneItem).html(itemName);
self.$dropdown.append(oneItem); }
// 为$items 从新赋值 self.$items = self.$dropdown.children();
// 触发后续的open方法 self.$container.trigger('comboselect:open') } }); } |
代码相似_filter()。应该要独立出一个方法来在两个方法中调用,没作。
init: function () { var self = this;
if(self.settings.url != ''){ //动态刷新代码 ... ... }else{ // 组件的原始逻辑 self._construct(); self._events(); } }, |
使用 comboselect- 前缀,如
<select class="list-filedV" id="entityCode" name="entityCode" comboselect-entity="entity" onchange="getBranch('')"> </select> <input type='hidden' name='entityName' id='entityName'> |
在js代码中完成初始化,代码
//获取数据 function getEntityData(){ $("#entityCode").comboSelect({ "itemName": "entityName", "url": contextPath+"/new/dictionary/searchEntityData.ajax", "limit": 7 }); } |
在编辑界面比较常见
//获取数据_修改 function getEntityDataUp(curEntityCode, curEntityName){ $("#entityCode").comboSelect({ "itemName": "entityName", "curItemField": "includeEntityCode", "curItemValue": curEntityCode, "curItemName": curEntityName, "url": contextPath+"/new/dictionary/searchEntityData.ajax", "limit": 7 }); } |
注意看_keyup 的代码,每次按键(不包括该函数忽略的特殊字符),每次都会刷新数据。若是是在浏览器内部进行数据过滤,问题还不明显。但每次模糊查询都经过服务器查询,就会带来大量的api访问。
在_keyup()中,调用_delayFilter(),由它触发前面修改后的 _filter()方法。
this.filterTimer = 0;
_delayFilter: function(search){ if(this.filterTimer > 0){ clearTimeout(this.filterTimer); this.filterTimer = 0; } var self = this; this.filterTimer = setTimeout(function(){ self._filter(search); }, 500); }, |