选择大量项目时的Select2性能

57
我正在使用带有Twitter Bootstrap的Select2 jQuery插件。对于较少数量的项目,它运行良好。但是当列表很大(超过1500个项目)时,速度会变慢。在IE中最慢。
正常的下拉列表在拥有1500多个项目时非常快。是否有任何解决这种情况的方法?

4
请使用 AJAX :) 替代加载全部数据。 - Seder
9
作为附带说明:我觉得这似乎是格式化成了一些诗歌。请使用Shift键? - Arjan
8个回答

53

使用分页建议,即使在IE8中也可以使其正常工作。

代码:

// Function to shuffle the demo data
function shuffle(str) {
  return str
    .split('')
    .sort(function() {
      return 0.5 - Math.random();
  })
.join('');
}

// For demonstration purposes we first make
// a huge array of demo data (20 000 items)
// HEADS UP; for the _.map function i use underscore (actually lo-dash) here
function mockData() {
  return _.map(_.range(1, 20000), function(i) {
    return {
      id: i,
      text: shuffle('te ststr ing to shuffle') + ' ' + i,
    };
  });
}
(function() {
  // init select 2
  $('#test').select2({
    data: mockData(),
    placeholder: 'search',
    multiple: true,
    // query with pagination
    query: function(q) {
      var pageSize,
        results,
        that = this;
      pageSize = 20; // or whatever pagesize
      results = [];
      if (q.term && q.term !== '') {
        // HEADS UP; for the _.filter function i use underscore (actually lo-dash) here
        results = _.filter(that.data, function(e) {
          return e.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0;
        });
      } else if (q.term === '') {
        results = that.data;
      }
      q.callback({
        results: results.slice((q.page - 1) * pageSize, q.page * pageSize),
        more: results.length >= q.page * pageSize,
      });
    },
  });
})();

这里有一个包含20000个项目的工作示例: http://embed.plnkr.co/db8SXs/preview

由于plnkr嵌入不支持IE8,因此请使用以下链接在IE8上尝试它: http://run.plnkr.co/plunks/db8SXs/


1
这是一个很好的解决方案。以前的问题在于屏幕上可能同时出现200个下拉框,并且在下拉框中有超过x个选项会导致页面加载时间非常长。这个解决方案能够将加载时间缩短到几秒钟而不是2分钟。 - yaegerbomb
1
感谢分享你的代码。如果你还在的话,我该如何选择默认值?不幸的是,与以前不同,在实例化代码之后运行$('#e24').select2('val', ["150"]);不起作用。 - timetofly
2
如果在初始化select2之前设置值,例如$('#e24').val([firstSelectedValue, secondSelectedValue)并且根据select2文档将initSelection : function (element, callback) { var data = []; $(element.val().split(",")).each(function () { data.push({id: this, text: this}); }); callback(data); },添加到select2初始化中,那么这是可能的。 但在初始化器内使用val: ["1", "2"]时我无法使其工作。更新的工作plnkr:http://embed.plnkr.co/db8SXs/preview - MarcusAsplund
请看我的答案更新,这段代码甚至可以在optgroup中进行搜索。 - Maulik Vora
我收到了“错误:没有select2/compat/query”的提示。 - Bassie
2
plnkr示例现已更新并正常工作,请注意:仅适用于Select2 v3。 - MarcusAsplund

33

我知道这是一个老问题,但我想分享一下对我有用的方法。如果你必须预加载大列表(这可能更容易取决于你是从头开始还是在别人的代码基础上构建),请使用minimumInputLength,如文档中所述。只有用户输入了几个字符后,才会显示巨大的选项列表。当实际选择Select2下拉列表时,这极大地减少了呈现它们时的性能影响。希望这有所帮助!


3
唯一的缺点是我的用户中有些人喜欢滚动列表并选择相邻的几个项目。您可以通过设置 closeOnSelect:false 来允许这样做。如果使用了 minimumInputLength,用户无法一次选择多个项目,只能一次选择一个。 - Darren Griffith
想补充一下,如果页面有许多带有大量列表的下拉框,则页面加载仍可能很慢。 - Anupam

11

这是Select2 v4的可用版本

基于此处的答案,并进行修改以使搜索与lo-dash配合使用

$(function () {
    items = []
    for (var i = 0; i < 1000; i++) {
        items.push({ id: i, text : "item " + i})
    }

    pageSize = 50

    jQuery.fn.select2.amd.require(["select2/data/array", "select2/utils"],

    function (ArrayData, Utils) {
        function CustomData($element, options) {
            CustomData.__super__.constructor.call(this, $element, options);
        }
        Utils.Extend(CustomData, ArrayData);

        CustomData.prototype.query = function (params, callback) {

            var results = [];
            if (params.term && params.term !== '') {
              results = _.filter(items, function(e) {
                return e.text.toUpperCase().indexOf(params.term.toUpperCase()) >= 0;
              });
            } else {
              results = items;
            }

            if (!("page" in params)) {
                params.page = 1;
            }
            var data = {};
            data.results = results.slice((params.page - 1) * pageSize, params.page * pageSize);
            data.pagination = {};
            data.pagination.more = params.page * pageSize < results.length;
            callback(data);
        };

        $(document).ready(function () {
            $("select").select2({
                ajax: {},
                dataAdapter: CustomData
            });
        });
    })
});

JsFiddlehttp://jsfiddle.net/nea053tw/

编辑: Fiddle已更改。


在实现自定义数据适配器后,标记对此答案无效。 - Dan Cundy
要添加标记,您需要引入标记模块并在选项中装饰适配器。 dataAdapter:utils.Decorate(CustomData,tags) - Dan Cundy
1
谢谢你的答复。我不确定是库还是脚本的问题,但是使用你给出的示例,当使用 <select multiple =“multiple”> 时无法取消选择选定选项。你可以通过添加多个选项来复制它,然后尝试选择和取消选择选项,它不起作用。但是,如果未使用 dataAdapter,则可以无缝运行。 - atay

9

对我来说最简单、最短的方法是:

$(".client_id").select2({
   minimumInputLength: 2
 });

您可以根据需要更改minimumInputLength的值。

这样,select2就不必显示整个列表,而是仅在输入固定数量的字符后才会带来结果。尽管您仍然在前端代码中拥有大型数组列表。

此外,如果您正在使用allowClear,则必须像这样声明placehodler:

$(".client_id").select2({
    minimumInputLength: 2,
    allowClear: true,
    placeholder: '--Select Client--'
 });

请查看这里的文档 http://select2.github.io/select2

如果您的数据仍然太大并且仍然存在性能问题,请使用Ajax方法。最好不要在选择器中加载过多的数据,而是使用Ajax来为Select2加载数据 https://select2.org/data-sources/ajax


8
请注意,您正在以<option>的形式加载超过1500个实际元素到页面上,这可能会影响页面性能。正如一位用户在评论中建议的那样,您可以通过向后端服务发起AJAX调用来解决性能问题,该服务将返回您的值。 Select2 AJAX 如何操作

我使用ajax调用了我的数据。数据显示良好,但问题是我无法选择任何选项。 - Amanullah Aman

3

虽然我们已经有了更新版本的select2,但是如果有人想要在optgroup中进行搜索,可以尝试这个解决方案。

http://jsfiddle.net/na1zLkz3/4/

    // Function to shuffle the demo data 
var shuffle = function (str) {
    return str.split('').sort(function () {
      return 0.5 - Math.random();
    }).join('');
  };

// For demonstration purposes we first make
// a huge array of demo data (20 000 items)
// HEADS UP; for the _.map function i use underscore (actually lo-dash) here
var mockData = function () {
    var array = _.map(_.range(1, 10), function (i) {
        return {
          id  : i,
          text: shuffle('te ststr ing to shuffle') + ' ' + i
        };
      });
    return array;
  };
  var mockData1 = function () {
    var array = _.map(_.range(10, 15), function (i) {
        return {
          id  : i,
          text: shuffle('te ststr ing to shuffle') + ' ' + i
        };
      });
    return array;
  };
  var mockData2 = function () {
    var array = _.map(_.range(15, 20), function (i) {
        return {
          id  : i,
          text: shuffle('te ststr ing to shuffle') + ' ' + i
        };
      });
    return array;
  };
  // create demo data
  var dummyData = mockData();
  var dummyData1 = mockData1();
  var dummyData2 = mockData2();
  dummyData.push({
  text: 'canada',
  children: dummyData1
  });
  dummyData.push({
  text: 'USA',
  children: dummyData2
  });

  // init select 2
  $('#ddlCar').select2({
    data             : dummyData,
    // init selected from elements value
    initSelection    : function (element, callback) {
      var initialData = [];
      $(element.val().split(",")).each(function () {
        initialData.push({
          id  : this,
          text: this
        });
      });
      callback(initialData);
    },

    // NOT NEEDED: These are just css for the demo data
    dropdownCssClass : 'capitalize',
    containerCssClass: 'capitalize',

    // NOT NEEDED: text for loading more results
    formatLoadMore   : function() {return 'Loading more...'},

    // query with pagination
    query            : function (q) {
      var pageSize,
        results;
      pageSize = 20; // or whatever pagesize
      var results  = [];
      if (q.term && q.term !== "") {
        // HEADS UP; for the _.filter function i use underscore (actually lo-dash) here
        var results = this.data;
        var results = _.filter(results, function (e) {
            if(typeof e.children != 'undefined')
          {
            subresults = _.filter(e.children, function (f) {
                return (f.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
            });
            if(subresults.length > 0)
                return true;
            return false;
          }
          return (e.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
        });
        newresults = [];
        for (var i = 0, len = results.length; i < len; i++) {
        newresults[i] = {};
        if(typeof results[i].text != 'undefined')
            newresults[i].text = results[i].text;
        if(typeof results[i].id != 'undefined')
            newresults[i].id = results[i].id;
        if(typeof results[i].children != 'undefined')
        {
            newresults[i].children = results[i].children;
            newresults[i].children = _.filter(newresults[i].children, function (f)                          {
                return (f.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
            });
        }
      }
      results = newresults;
      } else if (q.term === "") {
        results = this.data;

      }

      q.callback({
        results: results.slice((q.page - 1) * pageSize, q.page * pageSize),
        more   : results.length >= q.page * pageSize
      });
    }
  });

1
我们需要的英雄 - jake

0

0
作为又一个更新,我想分享一下我的经验,因为找到这么老的东西的支持越来越困难了。我使用npm安装了select 2,并发现包含两个版本。默认版本require('select2')不支持查询参数。需要完整版本require('select2/dist/js/select2.full'),然后使用此处显示的代码,在select 2 v4中适用于我,data()返回一个包含16k个城市列表。

       $(".select_2_cities").select2({
            theme: "bootstrap",
            data: data(),
            multiple: true,
            query            : function (q) {
              var pageSize,
                results;
              pageSize = 20; // or whatever pagesize
              var results  = [];
              if (q.term && q.term !== "") {
                // HEADS UP; for the _.filter function i use underscore (actually lo-dash) here
                var results = this.data;
                var results = _.filter(results, function (e) {
                    if(typeof e.children != 'undefined')
                  {
                    subresults = _.filter(e.children, function (f) {
                        return (f.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
                    });
                    if(subresults.length > 0)
                        return true;
                    return false;
                  }
                  return (e.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
                });
                newresults = [];
                for (var i = 0, len = results.length; i < len; i++) {
                newresults[i] = {};
                if(typeof results[i].text != 'undefined')
                    newresults[i].text = results[i].text;
                if(typeof results[i].id != 'undefined')
                    newresults[i].id = results[i].id;
                if(typeof results[i].children != 'undefined')
                {
                    newresults[i].children = results[i].children;
                    newresults[i].children = _.filter(newresults[i].children, function (f)                          {
                        return (f.text.toUpperCase().indexOf(q.term.toUpperCase()) >= 0);
                    });
                }
              }
              results = newresults;
              } else if (q.term === "") {
                results = this.data;
              }
              q.callback({
                results: results.slice((q.page - 1) * pageSize, q.page * pageSize),
                more   : results.length >= q.page * pageSize
              });
            }
        });


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接