Как включить бесконечную прокрутку в select2 4.0 без ajax

Я использую select2 с пользовательским адаптером данных. Все данные, предоставляемые select2, генерируются локально на веб-странице (поэтому нет необходимости использовать ajax). Поскольку метод query может генерировать много результатов (около 5k), открытие окна выбора происходит довольно медленно.

В качестве лекарства я хотел использовать бесконечную прокрутку. В Документация для пользовательского адаптера данных говорится, что метод query должен получать параметр page вместе с параметром term:

@param params.page Конкретная страница, которую следует загрузить. Обычно это предоставляется при работе с удаленными наборами данных, которые полагаются на разбиение на страницы, чтобы определить, какие объекты следует отображать.

Но это не так: присутствует только term. Я пытался вернуть more: true или more: 1000, но это не помогло. Я предполагаю, что это потому, что по умолчанию бесконечная прокрутка включен, если включен ajax.

Я предполагаю, что включение бесконечной прокрутки потребует использования amd.require, но я не уверен, что именно делать. Я пробовал этот код:

$.fn.select2.amd.require(
    ["select2/utils", "select2/dropdown/infiniteScroll"],
    (Utils, InfiniteScroll) =>
      input.data("select2").options.options.resultsAdapter = 
        Utils.Decorate(input.data("select2").options.options.resultsAdapter, InfiniteScroll)
)

Это кофейный скрипт, но я надеюсь, что он понятен всем. input — это элемент DOM, содержащий поле выбора — раньше я делал input.select2( //options )

Мой вопрос в основном, как мне включить бесконечную прокрутку без ajax?


person Paperback Writer    schedule 24.09.2015    source источник
comment
Мне было бы очень интересно получить ответ на этот вопрос. Вы что-нибудь выяснили?   -  person mothmonsterman    schedule 15.10.2015
comment
@happytimeharry Да, я сделал. Я описал свое решение в ответе. Я надеюсь, что это помогает!   -  person Paperback Writer    schedule 15.10.2015


Ответы (8)


Select2 включит бесконечную прокрутку только в том случае, если ajax включен. К счастью, мы можем включить его и по-прежнему использовать наш собственный адаптер. Таким образом, добавление пустого объекта в опцию ajax поможет.

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

Затем определите свой собственный адаптер данных. Внутри него гостиница query вводит pagination информацию в обратный вызов.

    CustomData.prototype.query = function (params, callback) {
        if (!("page" in params)) {
            params.page = 1;
        }
        var data = {};
        # you probably want to do some filtering, basing on params.term
        data.results = items.slice((params.page - 1) * pageSize, params.page * pageSize);
        data.pagination = {};
        data.pagination.more = params.page * pageSize < items.length;
        callback(data);
    };

Вот полная скрипка

person Paperback Writer    schedule 15.10.2015
comment
Кажется, поиск не работает с этим решением? - person prograhammer; 04.11.2015
comment
@prograhammer я расширил ответ, чтобы показать, как сохранить поиск без изменений прямо здесь stackoverflow.com/a/33174942/152640 - person mothmonsterman; 05.11.2015
comment
Ах! Пропустил это! Позвольте мне проверить ваш ответ (затем дайте вам +1 к ответу)... - person prograhammer; 05.11.2015
comment
Этот ответ заслуживает больше лайков! отлично работающий пример, но я не понимаю, почему им пришлось перейти с версии 3 на версию 4 Select2 и выбрать такой сумасшедший синтаксис !!! - person sarah.ferguson; 20.05.2016
comment
Но из вашего примера параметры фильтра не работают. если вы попытаетесь что-то найти, это не сработает. Можете ли вы улучшить работу функции сопоставления? - person Kvvaradha; 27.02.2017
comment
Это не работает на скрипке, потому что ресурсы github исчезли. вот один с ресурсами на cdn jsfiddle.net/m07c1Ldv/1 - person Heath Heath; 13.11.2019
comment
Могу ли я узнать, как следует изменить код в скрипке, если у вас есть два разных раскрывающихся списка на одной странице? - person Mohammed Nafie; 28.09.2020

Расширяя этот ответ, чтобы показать, как сохранить функции поиска, поставляемые с select2. Спасибо писателю в мягкой обложке!

Также упоминается этот пример, как добиться бесконечной прокрутки с использованием источника данных на стороне клиента с помощью select2 версии 3.4. .5.

В этом примере используются исходные параметры в теге select для построения списка вместо массива элементов, что было необходимо в моей ситуации.

function contains(str1, str2) {
    return new RegExp(str2, "i").test(str1);
}

CustomData.prototype.query = function (params, callback) {
    if (!("page" in params)) {
        params.page = 1;
    }
    var pageSize = 50;
    var results = this.$element.children().map(function(i, elem) {
        if (contains(elem.innerText, params.term)) {
            return {
                id:[elem.innerText, i].join(""),
                text:elem.innerText
            };
        }
    });
    callback({
        results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
        pagination:{
            more:results.length >= params.page * pageSize
        }
    });
};

Вот jsfiddle

person mothmonsterman    schedule 16.10.2015
comment
Можете ли вы предоставить работающий jsFiddle? Я тоже не могу заставить этот работать. - person prograhammer; 05.11.2015
comment
@prograhammer ну вот, братан - person mothmonsterman; 05.11.2015
comment
LOL, я почти закончил создавать скрипку для вас! Я наконец-то заработал! - person prograhammer; 05.11.2015
comment
да, проблема была в том, что вы используете теги параметров, а я использовал JSON напрямую. - person prograhammer; 05.11.2015
comment
конечно! жаль, что вы не уловили это раньше, но рад, что вы смогли заставить его работать. в любом случае скрипка может помочь любому, кому она понадобится в будущем ;-) - person mothmonsterman; 05.11.2015
comment
Я создал еще один ответ (как будто 2 было недостаточно!), Используя JSON напрямую для разъяснения другим. Это также демонстрирует, что Select2 4.0.0 на самом деле имеет хорошую производительность. (Раньше я использовал Selectize, но у меня были проблемы с мобильным телефоном). - person prograhammer; 05.11.2015
comment
jsfiddle не работает, он возвращает пустые результаты при поиске, изменение рабочего решения с помощью этого примера, чтобы добавить функцию поиска, также сломало мое решение... - person sarah.ferguson; 20.05.2016
comment
скрипка все еще работает нормально для меня, используя версию Chrome 50.0.2661.102 m. когда я ищу, он возвращает соответствующие результаты. если пример не сработал для вас, то зачем вам изменять рабочее решение??? - person mothmonsterman; 20.05.2016

Я чувствовал, что приведенные выше ответы нуждаются в лучшей демонстрации. Select2 4.0.0 вводит возможность делать пользовательские адаптеры. Используя трюк ajax: {}, я создал собственный адаптер данных jsonAdapter, который напрямую использует локальный JSON. Также обратите внимание на впечатляющую производительность версии Select2 4.0.0 при использовании большой строки JSON. Я использовал онлайн-генератор JSON и создал 10 000 имен в качестве тестовых данных. Однако этот пример очень мутный. Хотя это работает, я надеюсь, что есть лучший способ.

Полную версию скрипта см. здесь: http://jsfiddle.net/a8La61rL/

 $.fn.select2.amd.define('select2/data/customAdapter', ['select2/data/array', 'select2/utils'],
    function (ArrayData, Utils) {
        function CustomDataAdapter($element, options) {
            CustomDataAdapter.__super__.constructor.call(this, $element, options);
        }

        Utils.Extend(CustomDataAdapter, ArrayData);

        CustomDataAdapter.prototype.current = function (callback) {
            var found = [],
                findValue = null,
                initialValue = this.options.options.initialValue,
                selectedValue = this.$element.val(),
                jsonData = this.options.options.jsonData,
                jsonMap = this.options.options.jsonMap;

            if (initialValue !== null){
                findValue = initialValue;
                this.options.options.initialValue = null;  // <-- set null after initialized              
            }
            else if (selectedValue !== null){
                findValue = selectedValue;
            }

            if(!this.$element.prop('multiple')){
                findValue = [findValue];
                this.$element.html();     // <-- if I do this for multiple then it breaks
            }

            // Query value(s)
            for (var v = 0; v < findValue.length; v++) {              
                for (var i = 0, len = jsonData.length; i < len; i++) {
                    if (findValue[v] == jsonData[i][jsonMap.id]){
                       found.push({id: jsonData[i][jsonMap.id], text: jsonData[i][jsonMap.text]}); 
                       if(this.$element.find("option[value='" + findValue[v] + "']").length == 0) {
                           this.$element.append(new Option(jsonData[i][jsonMap.text], jsonData[i][jsonMap.id]));
                       }
                       break;   
                    }
                }
            }

            // Set found matches as selected
            this.$element.find("option").prop("selected", false).removeAttr("selected");            
            for (var v = 0; v < found.length; v++) {            
                this.$element.find("option[value='" + found[v].id + "']").prop("selected", true).attr("selected","selected");            
            }

            // If nothing was found, then set to top option (for single select)
            if (!found.length && !this.$element.prop('multiple')) {  // default to top option 
                found.push({id: jsonData[0][jsonMap.id], text: jsonData[0][jsonMap.text]}); 
                this.$element.html(new Option(jsonData[0][jsonMap.text], jsonData[0][jsonMap.id], true, true));
            }

            callback(found);
        };        

        CustomDataAdapter.prototype.query = function (params, callback) {
            if (!("page" in params)) {
                params.page = 1;
            }

            var jsonData = this.options.options.jsonData,
                pageSize = this.options.options.pageSize,
                jsonMap = this.options.options.jsonMap;

            var results = $.map(jsonData, function(obj) {
                // Search
                if(new RegExp(params.term, "i").test(obj[jsonMap.text])) {
                    return {
                        id:obj[jsonMap.id],
                        text:obj[jsonMap.text]
                    };
                }
            });

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

        return CustomDataAdapter;

    });

var jsonAdapter=$.fn.select2.amd.require('select2/data/customAdapter');
person prograhammer    schedule 05.11.2015
comment
Ницца! Спасибо @prograhammer - person mothmonsterman; 05.11.2015
comment
Хотел бы я проголосовать за это более 10 000 раз. Я уверен, что есть веская причина, но кажется, что v4 переработан. Это заняло несколько часов, чтобы понять, но, к счастью, я наткнулся на ваш ответ. Спасибо @prograhammer - person user1447679; 11.06.2016
comment
Это отличный пример адаптера. Тем не менее, я пытаюсь найти лучший способ сопоставления других значений настраиваемых объектов. Обычно вы можете получить $(selector).select2(data) и получить что-то кроме id и текста, но этот адаптер ограничивает эту часть. - person Mark; 18.07.2016
comment
Есть ли шанс привести пример того, как этот адаптер будет реализован в огромных списках выбора (в моем случае это требуется по дизайну) и доступа к атрибутам data-*? Спасибо - person Kosta; 17.11.2016
comment
@Коста, с тех пор я отказался от использования Select2. Код был слишком грязным ИМХО. Сейчас я использую VueJS, и это очень легко и просто реализовать самостоятельно, если вы хотите. И уже существуют компоненты, которые очень просты в использовании: github.com/monterail/vue-multiselect< /а> - person prograhammer; 18.11.2016
comment
@prograhammer хороший пример ... всего один вопрос. Как мне обойти отсутствие возвращаемых исходных данных, т.е. initialValue: [] вместо initialValue: [2, 8, 6]? - person Mshini; 06.12.2016
comment
@prograhammer Вы все еще довольны Vue вместо Select2? - person mothmonsterman; 20.04.2017
comment
@happytimeharry Ага, очень рад. На самом деле я сделал автозаполнение/фильтруемый выбор для компонента выбора платформы VuetifyJs: vuetifyjs.com/components/selects но мы временно удалили эти способности, так как сначала хотели исправить несколько проблем. Должен вернуться позже в этом месяце, когда у меня будет свободное время от работы. Хитрость заключается в том, чтобы сначала построить компонент list, затем компонент menu и, наконец, компонент select. Так чисто и быстро (мы загружаем элементы списка только при прокрутке). Сокрушает сложный разочаровывающий код Select2 IMO. - person prograhammer; 21.04.2017

Я обнаружил, что проще взломать адаптер ajax, чем создавать совершенно новый CustomAdapter, как в приведенных выше ответах. Приведенные выше ответы на самом деле не поддерживают пейджинг, потому что все они начинаются с массива, который не поддерживает пейджинг. Он также не поддерживает отложенную обработку.

window.myarray = Array(10000).fill(0).map((x,i)=>'Index' + i);
    
let timer = null;
$('select[name=test]')
    .empty()
    .select2({
        ajax: {
            delay: 250,
            transport: function(params, success, failure) {
                let pageSize = 10;
                let term = (params.data.term || '').toLowerCase();
                let page = (params.data.page || 1);

                if (timer)
                    clearTimeout(timer);

                timer = setTimeout(function(){
                    timer = null;
                    let results = window.myarray // your base array here
                    .filter(function(f){
                        // your custom filtering here.
                        return f.toLowerCase().includes(term);
                    })
                    .map(function(f){
                        // your custom mapping here.
                        return { id: f, text: f}; 
                    });

                    let paged = results.slice((page -1) * pageSize, page * pageSize);

                    let options = {
                        results: paged,
                        pagination: {
                            more: results.length >= page * pageSize
                        }
                    };
                    success(options);
                }, params.delay);
            }
        },
        tags: true
    });
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.full.min.js"></script>
<select name='test' data-width="500px"><option>test</option></select>

person Robert McKee    schedule 12.08.2019
comment
Лучший ответ! Четкое решение! - person Evgeny Ivanov; 05.05.2020
comment
Это хорошо работает с элементом ‹select›, но, похоже, не загружает никаких данных, если применяется к элементу ‹input type=text›. Есть ли решение для этого? - person ouija; 25.07.2020
comment
@ouija Боюсь, select2.js работает только с выбором. - person Robert McKee; 26.07.2020
comment
Другие решения со временем устаревают или их скрипка больше не работает. Это решение простое и лучшее. Спасибо - person Tahir Shahzad; 25.11.2020

Вот более короткая версия Select2 v4 с возможностью поиска, в которой есть пейджинг. Для поиска используется lo-dash.

EDIT Новая скрипта: http://jsfiddle.net/nea053tw/

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

Цикл поиска изначально взят из этих старых функций Select4 v3: https://stackoverflow.com/a/25466453/5601169

person lofihelsinki    schedule 29.01.2019
comment
Эта скрипка выдает исключение, и раскрывающийся список пуст. - person Robert McKee; 13.08.2019
comment
Исправлено: в скрипке отсутствовал jQuery. Кроме того, jQuery следует использовать вместо $ в jQuery.fn.select2.amd.require() - person lofihelsinki; 13.08.2019
comment
Это хорошо и работает, но как здесь установить выбранное значение? - person Umashankar Saw; 07.03.2021

Мое решение для angular 9

  this.$select2 = this.$element.select2({
    width: '100%',
    language: "tr",
    ajax: {
      transport: (params, success, failure) => {
        let pageSize = 10;
        let page = (params.data.page || 1);
        let results = this.options
          .filter(i => new RegExp(params.data.term, "i").test(i.text))
          .map(i => {
            return {
              id: i.value,
              text: i.text
            }
          });
        let paged = results.slice((page - 1) * pageSize, page * pageSize);

        let options = {
          results: paged,
          pagination: {
            more: results.length >= page * pageSize
          }
        };
        success(options);
      }
    }
  });
}
person OMANSAK    schedule 04.09.2020

Это не прямой ответ: после того, как я долго боролся с этим, я в конечном итоге переключился на selectize. Поддержка Select2 поиска без Ajax, начиная с версии 4, ужасно сложна, граничит с нелепостью и плохо документирована. Selectize имеет явную поддержку поиска без Ajax: вы просто реализуете функцию, которая возвращает список.

person Max    schedule 03.11.2017

Ничто из этого не сработало для меня. Я не знаю, что имел в виду первоначальный вопрос, но в моем случае я использую угловые и HTTP-вызовы и службы и хотел избежать вызовов AJAX. Поэтому я просто хотел вызвать сервисный метод вместо AJAX. Это даже не задокументировано на сайте библиотеки, но каким-то образом я нашел способ сделать это с помощью transport

ajax: {
        delay : 2000,
        transport: (params, success, failure) => {
          this.getFilterList(params).then(res => success(res)).catch(err => failure(err));
        }
      }

Если кто-то вроде меня пришел сюда за этим, то вот!

person Akshita Agrawal    schedule 04.08.2020