Select2 performance for large set of items
Asked Answered
H

8

60

I'm using select2 jquery plugin with twitter bootstrap. It's working fine for smaller number of items. But when the list is huge (more than 1500 items) it really slows down. It's slowest in IE.

Normal Dropdownlist works very fast with more than 1500 items. Are there any workarounds for this situation?

Hosmer answered 23/2, 2013 at 13:29 Comment(2)
Use AJAX :) instead of loading the whole dataDecagram
As an aside: I feel this is formatted as some poem. And please use the Shift key?Grandiose
P
53

You can make this work good even in IE8 with paginating the suggestions,

Code:

// 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,
      });
    },
  });
})();

working example with 20000 items here: http://embed.plnkr.co/db8SXs/preview

plnkr embed does not support IE8 so try it out on IE8 with this link instead: http://run.plnkr.co/plunks/db8SXs/

Paraplegia answered 23/8, 2014 at 21:15 Comment(6)
This is a great solution. had an issue where having the possibility of 200 dropdowns on screen at once and having over x items in the dropdowns was causing lonnnnnnggg page loads. This was able to bring it down to a couple seconds instead of 2 minutes.Ca
Thanks for sharing your code. If you're still around, how can I have default value(s) selected? Unfortunately, unlike before, running $('#e24').select2('val', ["150"]); after your instantiation code does not work.Larisalarissa
It is possible if you set the value before initializing the select2 as e.g. $('#e24').val([firstSelectedValue, secondSelectedValue) and add initSelection : function (element, callback) { var data = []; $(element.val().split(",")).each(function () { data.push({id: this, text: this}); }); callback(data); }, inside the select2 init according to select2 docs. I could not get it to work with e.g. val : ["1", "2"] inside the initializer though. Updated working plnkr: http://embed.plnkr.co/db8SXs/previewParaplegia
See my answer for update of this code, it searches even in the optgroupBulbil
I get Error: No select2/compat/queryAnility
plnkr examples now updated and are working, note: only in Select2 v3Paraplegia
G
33

I know it's an old question, but I wanted to share what worked for me. If you must pre-load the big list(depending on if you're starting from scratch or building on someone else's code, this may be easier), use the minimumInputLength as described here in the documentation. The huge list of options doesn't show until the user has typed a couple of characters. This greatly reduces the performance hit while rendering them when the Select2 dropdown is actually selected. Hope that helps!

Gaskill answered 16/7, 2014 at 20:51 Comment(2)
Only downside is some of my users like to scroll down the list and then select a few items that are next to each other. You can allow this by setting closeOnSelect:false. If you use minimumInputLength, the user can't select multiple items at a time, just one at a time.Insufficiency
Wanted to add that the page loading can still be slow if the page has many dropdowns with large listsJane
S
12

Here's a working version for Select2 v4

Based on the answer here: and modified it to make searching work with 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
            });
        });
    })
});

JsFiddle: http://jsfiddle.net/nea053tw/

Edit: Fiddle changed.

Starinsky answered 29/1, 2019 at 14:2 Comment(3)
Tagging does not work with this answer after implementing the custom data adapter.Auerbach
To add tagging you need to bring in the tagging module and decorate the adapter in the options. dataAdapter: utils.Decorate(CustomData, tags)Auerbach
thank you for your answer. I don't know if it's a bug of a library or the script, but using your example it's impossible to unselect the selected option when using <select multiple="multiple">. You can replicate it by adding multiple option to select, then try to select and deselect option, it does not work. But if dataAdapter is not used, it works flawlessly.Steading
C
9

The easiest and shortest works for me is:

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

You can change the value of minimumInputLength at your wish.

This way, select2 will not have to show whole list, rather it will bring result only after the fixed number of characters typed. Although you are still having the large array of list at front-end-code.

Also, if you are using allowClear, then you must declare placehodler like this:

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

Check the documentation here http://select2.github.io/select2

If still your data is too large and still having performance issue, use Ajax method. It's better not to load too large data in select, istead use Ajax for Select2 https://select2.org/data-sources/ajax

Causal answered 28/3, 2018 at 6:22 Comment(0)
C
8

So keep in mind you are loading >1500 actual elements onto the page in the form of <option>s, which can end up hurting page performance as well. As a user suggested in the a comment, you can solve the performance issue by making an AJAX call to a backend service that will return your values.

Select2 Ajax how-to

Centuplicate answered 29/7, 2013 at 2:30 Comment(1)
I've called my data using ajax. Data is showing good but the problem is I can't select any option.Uredo
B
3

This is very old question and answer and even we have newer version of select2. but if someone is trying to search in optgroup too. can try this solution.

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
      });
    }
  });
Bulbil answered 25/8, 2016 at 12:32 Comment(1)
The hero we need🎉Casemate
U
1

Unfortunatelly the answer is switch to selectize.js!

lofihelsinki solution works for <select>. Unfortunately it is not working with <select multiple>.

Because of this I switch from select2 to selectize.js. It runs really fast with large set of items.

Here is an example with 13500+ items! https://htmlcssjsp.vasilisp.repl.co/selectize-example/

Unadvised answered 18/3, 2022 at 2:8 Comment(0)
N
0

As yet another update, I wanted to share what worked for me, since finding support for something this old is increasingly difficult. I had installed select 2 with npm and found that there are two versions included. The default version require('select2') does not include support for the query argument. requiring the full version require('select2/dist/js/select2.full') and then using the code shown here worked for me with select 2 v4, with data() returning a list of 16k cities

       $(".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
              });
            }
        });
Ness answered 4/8, 2020 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.