如何在没有 ajax 的情况下在 select2 4.0 中启用无限滚动

How to enable infinite scrolling in select2 4.0 without ajax

我正在使用 select2 和自定义数据适配器。提供给 select2 的所有数据都是在网页本地生成的(因此无需使用 ajax)。由于 query 方法会产生很多结果(大约 5k)打开 select 框是相当慢的。

作为补救措施,我想使用无限滚动。 Documentation 对于自定义数据适配器表示 query 方法应该接收 page 参数以及 term:

@param params.page The specific page that should be loaded. This is typically provided when working with remote data sets, which rely on pagination to determine what objects should be displayed.

但事实并非如此:只有 term 存在。我尝试 return more: truemore: 1000,但这没有帮助。我猜这是因为,默认情况下,infinite scroll is enabled iff ajax is enabled.

我猜测启用无限滚动将涉及使用 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 是包含 select 框的 DOM 元素 - 我之前做过 input.select2( //options )

我的问题基本上是,如何在没有 ajax 的情况下启用无限滚动?

如果启用 ajax

Select2 只会启用无限滚动。幸运的是我们可以启用它并且仍然使用我们自己的适配器。所以将空对象放入 ajax 选项就可以了。

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

接下来,定义您自己的数据适配器。在里面,inn query push pagination info into callback.

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

这是一个full fiddle

to show how to retain the search functionality that comes with select2. Thanks Paperback Writer 上扩展!

还参考了 this example 如何使用客户端数据源实现无限滚动,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

我觉得上面的答案需要更好的证明。选择 2 4.0.0 introduces the ability to do custom adapters. Using the ajax: {} trick, I created a custom dataAdapter jsonAdapter that uses local JSON directly. Also notice how Select2's 4.0.0 release has impressive performance using a big JSON string. I used an online JSON generator 并创建 10,000 个名称作为测试数据。然而,这个例子非常混乱。虽然这行得通,但我希望有更好的方法。

在此处查看完整的 fiddle: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');

这不是一个直接的答案:在为此苦苦挣扎了一段时间之后,我最终改用了 selectize。 Select2 对非 Ajax 搜索的支持,从版本 4 开始就非常复杂,近乎荒谬,而且没有很好的记录。 Selectize 明确支持非 Ajax 搜索:您只需实现一个 returns 列表的函数。

这是一个 更短 的 Select2 v4 可搜索版本,具有分页功能。它使用 lo-dash 用于 正在搜索。

编辑新fiddle: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 函数:

我发现劫持 ajax 适配器比像上面的答案那样创建一个全新的 CustomAdapter 更容易。上面的回答其实好像都不支持分页,因为都是从array开始的,array不支持分页。也不支持延迟处理。

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>

None 这对我有用。我不知道原来的问题是什么意思,但就我而言,我正在使用 angular 和 HTTP 调用和服务,并希望避免 AJAX 调用。 所以我只是想调用一个服务方法来代替 AJAX。这甚至没有记录在图书馆的网站上,但不知何故我找到了使用 transport

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

如果像我一样的人来这里是为了这个,那就好了!

我对 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);
      }
    }
  });
}