• 改造 Combo Select支持服务器端模糊搜索


    项目中使用了 combo select,为缺省的select增加模糊搜索的功能,一直运行得很好。

    1    碰到的问题

    但最近碰到一个大数据量的select:初始化加载的数据项有2000多个。我们采用的是ajax读取所有的option json,并由js在浏览器中遍历并最终生成完整的html。当数据量变大的时候,ajax读取数据和浏览器处理数据都会有比较明显的损耗,页面初始化时需要较长时间,降低了用户友好度。

    2    备选解决方案

    大家简单分析了一下这个问题,想到了三种可能的解决方案。

    2.1 修改数据结构

    目前的同级数据有2000多条,数据从逻辑上可以拆分为两级结构。这样,将数据拆分为两级结构后,使用两个联动Select,能大大减少每个select加载的option数量。

    2.2 使用redis缓存数据

    因为采用分布式部署,这些数据实际上经过了多次服务器之间的传输。数据量大,每一级传输耗时增加,导致最终的耗时难于接受。

    可以在api server端利用nosql对数据进行缓存,能在一定程度上降低耗时。

    2.3 修改combo select插件

    从前端入手,select只显示少量数据,当用户输入关键字进行搜索时,实时从服务器加载。这种方式增加了调用次数,但可以大大降低数据量,缩短页面加载的耗时。

    三种方案,都能在一定程度上解决问题。我们决定先从combo select插件尝试,如果达不到效果,再考虑redis缓存或修改数据结构的方案。

    3    Combo Select代码分析

    网址 https://github.com/PebbleRoad/combo-select ,感谢提供如此优秀的一个插件。

    3.1 基本用法

    首先在页面中构建一个select,并初始化option数据,然后调用脚本

    $("#selectId").comboSelect();

    其他更复杂的功能,请自行前往官网学习。

    3.2 html结构

     

    Combo Select在执行时,在原 select 外层套了一个 <div class=”combo-select”>,然后在select后面添加了三个element。

    div.combo-arrow,是下拉箭头

    ul.combo-dropdown是用来显示的下拉列表

    input.combo-input 是用来输入模糊搜索内容的输入框

    并通过修改原 select 的属性,隐藏掉。

    3.3 js数据模型

    combo select初始化时,经过一系列代码,最终构造几个属性:

    $container : 生成一个新的div,将原来的select和新生成的ul等都放在其中。

    $el : 初始的select element

    $options : 所有的option 数据

    $dropdown : 生成的 ul.combo-dropdown 对象

    $items : 所有的options转成 li 格式后的数据。

    下图是数据模型和html元素之间的对应关系。

     

    3.4 插件初始化

    在js插件的代码function Plugin ( element, options )会完成插件的初始化,根据select当前的数据,完成html元素的调整,以及js数据模型的初始化。初始化流程如下

     

    3.5 模糊查询的逻辑

    当用户在input中输入文字的时候,会触发 keydown和keyup事件,在keyup事件中,对 $items中的数据依次进行匹配,设置 visible属性,实现部分数据的展示。

     

    在这个过程中,原始的select($el)及其所有的options($options)没有变化,下拉列表的变化,主要是将ul.li($items)设置为可见或不可见。

    4    修改为Server端实时查询方案

    整个修改方案,分别从Server API、js组件、前端调用三方面解决。

    4.1 Server API 修改

    Server端需要提供根据名称进行模糊搜索的接口。不赘述,需要注意的是返回数据要设置最大条数。避免根据查询条件返回了大量的数据,就失去了解决的优势。

    限制最大条数后,需要跟产品介绍清楚这个实现逻辑,如果用户输入的关键字区分度不大时,可能无法查到真正需要的数据;此时需要用户输入更具有区分度的关键字。

    4.2 ComboSelect组件修改

    4.2.1      修改方案

    修改keyup事件时的逻辑:原来是分别设置ul.li是否可见,修改为重新加载select的所有options,并根据options重建$items,并设置为所有ul.li都是可见的。

     

    4.2.2      为组件新增几个参数

                  entity: 'entity',

                  itemName: 'itemName',

                  curItemField: 'curItemCode',

                  curItemValue: '',

                  curItemName: '',

                  url: '',

                  limit: 7

    • entity: 当前处理的数据类型,这是为了适应不同api返回的json定义的差异。更好的办法是要求所有数据类型使用相同的属性名;变通的方案就是增加这个entity,在js上做差异化处理。这样就减少了改造的通用性。
    • itemName: 调用api时需要的用户输入值的参数名
    • curItemField:在html中,item的input名称
    • curItemValue: 当前已选中数据的value
    • curItemName: 当前已选中数据的title
    • limit: 服务器api模糊搜索返回值的分页大小

    4.2.3      修改 _filter() 方法实现服务器端模糊查询

    修改了原组件的这个方法,判断是否设置了服务器端刷新的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')

      }

     });

    }

    4.2.4      修改 init() 实现首次加载

    代码类似_filter()。应该要独立出一个方法来在两个方法中调用,没做。

    init: function () {

     var self = this;

     if(self.settings.url != ''){

      //动态刷新代码

      ... ...

     }else{

      // 组件的原始逻辑

      self._construct();

      self._events();

     }

    },

    4.3 前端调用

    4.3.1      html代码中添加参数

    使用 comboselect- 前缀,如

    <select class="list-filedV" id="entityCode" name="entityCode"

    comboselect-entity="entity" onchange="getBranch('')">

    </select>

    <input type='hidden' name='entityName' id='entityName'>

    4.3.2      js生成没有缺省值的combobox

    在js代码中完成初始化,代码

    //获取数据

    function getEntityData(){

        $("#entityCode").comboSelect({

           "itemName": "entityName",

            "url": contextPath+"/new/dictionary/searchEntityData.ajax",

            "limit": 7

        });

    }

    4.3.3      js生成有缺省值的combobox

    在编辑界面比较常见

    //获取数据_修改

    function getEntityDataUp(curEntityCode, curEntityName){

        $("#entityCode").comboSelect({

            "itemName": "entityName",

            "curItemField": "includeEntityCode",

            "curItemValue": curEntityCode,

            "curItemName": curEntityName,

            "url": contextPath+"/new/dictionary/searchEntityData.ajax",

            "limit": 7

        });

    }

    5    降低调用服务器频度

    注意看_keyup 的代码,每次按键(不包括该函数忽略的特殊字符),每次都会刷新数据。如果是在浏览器内部进行数据过滤,问题还不明显。但每次模糊查询都通过服务器查询,就会带来大量的api访问。

    5.1 修改方案

    在_keyup()中,调用_delayFilter(),由它触发前面修改后的 _filter()方法。

     

    5.2 代码 _delayFilter()

    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);

    },

  • 相关阅读:
    连通域标记
    qt&gdal
    gdal vs2013编译
    java配置
    windows下面安装Python和pip
    mfc operator new”: 没有重载函数接受 3 个参数
    std::min&std::max与mfc冲突问题
    qt中vtk易出现错误
    cmake构建qt工程
    Webstorm补丁
  • 原文地址:https://www.cnblogs.com/codestory/p/8178409.html
Copyright © 2020-2023  润新知