項目中使用了 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); }, |