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

Posted

技术标签:

【中文标题】如何在没有 ajax 的 select2 4.0 中启用无限滚动【英文标题】:How to enable infinite scrolling in select2 4.0 without ajax 【发布时间】:2015-12-21 18:46:08 【问题描述】:

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

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

@param params.page 应该加载的特定页面。这是 通常在处理远程数据集时提供,这些数据集依赖 在分页上确定应该显示哪些对象。

但它没有:只有term 存在。我试图返回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 是包含选择框的 DOM 元素 - 我之前做过 input.select2( //options )

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

【问题讨论】:

我会对这个问题的答案非常感兴趣。你有什么发现吗? @happytimeharry 是的,我做到了。我在答案中描述了我的解决方案。希望对您有所帮助! 【参考方案1】:

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

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

接下来,定义您自己的数据适配器。在其中,inn querypagination 信息推送到回调中。

    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

【讨论】:

搜索似乎被这个解决方案破坏了? @prograhammer 我扩展了答案以显示如何在此处保持搜索完整***.com/a/33174942/152640 啊!错过了!让我看看你的答案(然后给你+1的答案)... 这个答案值得更多的喜欢!完美的工作示例,但我不明白为什么他们必须从 Select2 的版本 3 更改为版本 4 并选择如此疯狂的语法!!! 它在 fiddle 上不起作用,因为 github 资源已经消失了。这是cdn上的资源jsfiddle.net/m07c1Ldv/1【参考方案2】:

扩展 this answer 以展示如何保留 select2 附带的搜索功能。谢谢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

【讨论】:

你能提供一个工作的jsFiddle吗?我也无法让这个工作。 @prograhammer 兄弟,你去吧 大声笑,我几乎完成了为你创建小提琴!我终于让它工作了! 是的,问题是您使用的是选项标签,而我直接使用的是 JSON。 确实!抱歉,您没有早点发现它,但很高兴您能够让它正常工作。无论如何,小提琴可以帮助任何需要它的人;-)【参考方案3】:

我觉得上面的答案需要更好的演示。 Select2 4.0.0 introduces 能够做自定义adapters。使用 ajax: 技巧,我创建了一个直接使用本地 JSON 的自定义 dataAdapter jsonAdapter。另请注意 Select2 的 4.0.0 版本如何使用大 JSON 字符串获得令人印象深刻的性能。我使用了online JSON generator 并创建了 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');

【讨论】:

不错!谢谢@prograhammer 我希望我能投票超过 10,000 次。我确信有充分的理由,但似乎 v4 设计过度。这需要几个小时才能弄清楚,但幸运的是我偶然发现了你的答案。谢谢@prograhammer 这是一个很好的适配器示例。但是,我正在尝试找出映射其他自定义对象值的最佳方法。通常,您可以检索 $(selector).select2("data") 并获取 id 和 text 以外的内容,但此适配器限制了该部分。 是否有机会举例说明如何在巨大的选择列表上实现此适配器(在我的情况下是设计要求的),并访问 data-* 属性?谢谢 @Kosta,我已经放弃使用 Select2。恕我直言,代码太乱了。我现在正在使用 VueJS,如果你愿意的话,自己实现它非常容易和干净。并且已经有现有的组件非常易于使用:github.com/monterail/vue-multiselect【参考方案4】:

我发现劫持 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-><option>test</option></select>

【讨论】:

最佳答案!明确的解决方案! 这适用于 元素似乎不会加载任何数据 -- 任何解决方案? @ouija 恐怕 select2.js 只适用于选择。 其他解决方案随着时间的推移而被弃用,或者它们的小提琴不再起作用。此解决方案简单且最佳。谢谢【参考方案5】:

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

【讨论】:

【参考方案6】:

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

编辑新小提琴: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://***.com/a/25466453/5601169

【讨论】:

这个fiddle抛出异常,下拉框为空。 已修复:小提琴缺少 jQuery。此外,jQuery 应在 jQuery.fn.select2.amd.require() 中使用 $ 替换 这很好,它的工作原理,但是如何在这里设置选定的值?【参考方案7】:

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

【讨论】:

【参考方案8】:

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

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

如果有像我这样的人为此而来,那就去吧!

【讨论】:

以上是关于如何在没有 ajax 的 select2 4.0 中启用无限滚动的主要内容,如果未能解决你的问题,请参考以下文章

Select2 与 Ajax Asp.net 4.0 webmethod 请求

带有 templateResult 和 templateSelection 的 jquery select2 (4.0) ajax

如何在 select2.js v4.0 中显示 ajax 数据?

Select2 4.0 - 创建后推送新条目

jquery Select2 Ajax - 如何设置值(initSelection)

Select2 with ajax, multiple and knockout binding 不将对象保存到 selectedOptions