不重新构建控件的情况下更新select2数据

51

我正在将一个 <input type="hidden"> 转换成 select2 下拉选择器,并通过查询方法提供数据。

$('#inputhidden').select2({
    query: function( query ) {
        query.callback( data ); // the data is in the format select2 expects and all works well..
    );
});

问题是我需要对select2 UI进行hack,将两个按钮放在搜索栏顶部,点击这些按钮将执行ajax调用,并且必须更新select2内容。
现在,我需要在不完全重建select2的情况下进行更新,而只需刷新下拉列表中的项目即可。我找不到一种方法来传递新的数据给已经创建的select2控件,这是否可能?
10个回答

60

选择2 v3.x

如果您有带有选项的本地数组(通过ajax调用接收),我认为您应该使用data参数作为返回选择框结果的函数:

var pills = [{id:0, text: "red"}, {id:1, text: "blue"}]; 

$('#selectpill').select2({
    placeholder: "Select a pill",
    data: function() { return {results: pills}; }
});

$('#uppercase').click(function() {
    $.each(pills, function(idx, val) {
        pills[idx].text = val.text.toUpperCase();
    });
});
$('#newresults').click(function() {
    pills = [{id:0, text: "white"}, {id:1, text: "black"}];
});

FIDDLEhttp://jsfiddle.net/RVnfn/576/

如果您使用按钮自定义select2界面,只需在更新数据数组(示例中的pills)后调用updateResults方法即可。请注意,这个方法不允许从select2对象的外部调用,但是如果需要,您可以将它添加到allowedMethods数组中。


select2 v4:自定义数据适配器

使用具有附加的updateOptions方法的自定义数据适配器(原始的ArrayAdapter为什么缺少此功能尚不清楚),可以动态更新选项列表(此示例中的所有选项):

$.fn.select2.amd.define('select2/data/customAdapter',
    ['select2/data/array', 'select2/utils'],
    function (ArrayAdapter, Utils) {
        function CustomDataAdapter ($element, options) {
            CustomDataAdapter.__super__.constructor.call(this, $element, options);
        }
        Utils.Extend(CustomDataAdapter, ArrayAdapter);
        CustomDataAdapter.prototype.updateOptions = function (data) {
            this.$element.find('option').remove(); // remove all options
            this.addOptions(this.convertToOptions(data));
        }        
        return CustomDataAdapter;
    }
);

var customAdapter = $.fn.select2.amd.require('select2/data/customAdapter');

var sel = $('select').select2({
    dataAdapter: customAdapter,
    data: pills
});

$('#uppercase').click(function() {
    $.each(pills, function(idx, val) {
        pills[idx].text = val.text.toUpperCase();
    });
    sel.data('select2').dataAdapter.updateOptions(pills);
});

FIDDLE: https://jsfiddle.net/xu48n36c/1/


select2 v4: ajax transport function

v4中,你可以定义自定义的传输方法,并且可以使用本地数据数组来工作(感谢 @Caleb_Kiage 示例,我已经尝试过了,但没有成功)

文档:https://select2.github.io/options.html#can-an-ajax-plugin-other-than-jqueryajax-be-used

Select2 使用在 ajax.transport 中定义的传输方法向您的 API 发送请求。默认情况下,此传输方法是 jQuery.ajax,但可以更改。

$('select').select2({
    ajax: {
        transport: function(params, success, failure) {
            var items = pills;
            // fitering if params.data.q available
            if (params.data && params.data.q) {
                items = items.filter(function(item) {
                    return new RegExp(params.data.q).test(item.text);
                });
            }
            var promise = new Promise(function(resolve, reject) {
                resolve({results: items});
            });
            promise.then(success);
            promise.catch(failure);
        }
    }
});

但是,使用这种方法,如果选项的文本在数组中发生更改,则需要更改选项的ID - 内部的select2 dom选项元素列表不会被修改。如果选项的ID保持不变,则将显示先前保存的选项,而不是来自数组的更新选项!如果仅通过向其中添加新项目来修改数组,那么这不是问题-对于大多数常见情况都可以接受。

FIDDLE:https://jsfiddle.net/xu48n36c/3/


1
请检查以下的fiddle链接,它可以渲染结果但是过滤器不起作用: http://jsfiddle.net/RVnfn/106/ - Jeeva J
1
@JeevaJsb,您正在添加带有“check”键的对象...搜索需要“text”键! - ndpu
1
@ndpu 谢谢您的编辑,现在它运行得很好,我已经删除了我的评论(5分钟后无法编辑)。 - Mani Deep
2
@ndpu,你太棒了,这正是我所需要的,谢谢! - PaulM

19

我认为直接交付数据就足够了:

$("#inputhidden").select2("data", data, true);

请注意,第二个参数似乎表示需要进行刷新。

感谢@Bergi在此处提供的帮助。


如果那样无法自动更新,则可以尝试直接调用其updateResults方法。

$("#inputhidden").select2("updateResults");

或者通过向“input.select2-input”发送一个触发器来间接触发它,方法如下:

var search = $("#inputhidden input.select2-input");
search.trigger("input");

2
@AnuradhaJayathilaka 它是由select2在此处定义 - zsawyer
3
很奇怪,这个方法不在文档中,当我运行它时出现了错误。 - astroanu
2
@AnuradhaJayathilaka,解决方法可能在@ndpu的答案中关于allowedMethods的部分。 - zsawyer
4
search.trigger("change") 似乎也有效。 - Paul Richter

16

使用Select2 4.0与Meteor,您可以像这样做:

Template.TemplateName.rendered = ->

  $("#select2-el").select2({
    data  : Session.get("select2Data")
  })

  @autorun ->

    # Clear the existing list options. 
    $("#select2-el").select2().empty()

    # Re-create with new options.
    $("#select2-el").select2({
      data  : Session.get("select2Data")
    })

正在发生的事情:

  1. 当模板被渲染时...
  2. 使用来自Session的数据初始化select2控件。
  3. @autorun (this.autorun)函数每次Session.get("select2Data")的值改变时都会运行。
  4. 每当Session发生更改时,清除现有的select2选项并重新创建新的选项。

这适用于任何反应式数据源 - 例如Collection.find().fetch() - 而不仅仅是Session.get()。

注意:从Select2版本4.0起,您必须先删除现有选项,然后再添加新选项。 查看此GitHub问题获取详细信息。没有“更新选项”的方法,而不清除现有选项。

以上是coffeescript。 JavaScript非常相似。


1
使用 empty() 是我能让它正常工作的唯一方法,但当我重新初始化时,它也会删除我的其他构造函数选项。 - haferje
3
问题中没有提到流星。 - Craicerjack
3
@Craicerjack 确实没错 - 但是我来这里是为了寻找一个适用于Meteor的解决方案。我按照上面的答案并针对Meteor进行了调整。也许其他人也会来到这里。 - Lucidity
在4.0版本中,这个答案会重置控件,而这正是OP一开始想要避免的。 - Anderson Fortaleza
取决于您所说的“重置控件”的含义。OP写道:“我需要这些更新发生,而无需完全重建select2,而只需刷新下拉列表中的项目。”此解决方案应该可以做到这一点。请参阅引用的GitHub问题 - 我认为这是4.0中最好的解决方案。如果您能做得更好,请随意编辑。 - Lucidity
显示剩余2条评论

13

试试这个:

var data = [{id: 1, text: 'First'}, {id: 2, text: 'Second'}, {...}];
$('select[name="my_select"]').empty().select2({
    data: data
});

1
这段代码很容易理解:当我们清空选择框 select2 时,它会自动更新;当我们添加新的选项数据时,select2 会再次刷新。因此,不需要触发 "change" 和 "change.select2"。 - SpinyMan
4
这将重新构建select2,这意味着如果你有任何配置,它们将会丢失。如果你有任何已附加的事件,你需要先将它们分离,然后重新附加。 - Diego Jancic

7
var selBoxObj = $('#selectpill');
selBoxObj.trigger("change.select2");

4

我通过使用ajax选项并指定自定义传输函数解决了这个问题。

以下是相关的js代码,可用于使其正常工作。

var $items = [];

let options = {
ajax: {
    transport: function(params, success, failure) {
    let items = $items;

    if (params.data && params.data.q) {
        items = items.filter(function(item) {
        return new RegExp(params.data.q).test(item.text);
        });
    }

    let promise = new Promise(function(resolve, reject) {
        resolve({
        results: items
        });
    });

    promise.then(success);
    promise.catch(failure);
    }
},
placeholder: 'Select item'
};

$('select').select2(options);

let count = $items.length + 1;

$('button').on('click', function() {
$items.push({
    id: count,
    text: 'Item' + count
});
count++;
});

“Select2 动态选项演示”链接 https://jsfiddle.net/calebkiage/oxm4gf4y/ 显示页面未找到。 - w.Daya
@w.Daya,我的小提琴被删除了 :-( 我会移除链接,但是我在答案中使用的JS代码都在那里。 - Caleb Kiage
@w.Daya,我的Fiddle被删除了 :-( 我会删除链接,但我使用的JS代码都在答案中。 - Caleb Kiage

2
据我所知,在不刷新整个列表或输入一些搜索文本并使用查询函数的情况下,无法更新select2选项。
这些按钮应该做什么?如果它们用于确定选择选项,为什么不将它们放在选择框外面,并通过编程方式设置选择框数据,然后打开它?我不明白为什么你想把它们放在搜索框的上面。如果用户不想搜索,您可以使用minimumResultsForSearch选项来隐藏搜索功能。
编辑:这样怎么样?
HTML:
<input type="hidden" id="select2" class="select" />

Javascript

var data = [{id: 0, text: "Zero"}],
    select = $('#select2');

select.select2({
  query: function(query) {
    query.callback({results: data});
  },
  width: '150px'
});

console.log('Opening select2...');
select.select2('open');

setTimeout(function() {
  console.log('Updating data...');
  data = [{id: 1, text: 'One'}];
}, 1500);

setTimeout(function() {
  console.log('Fake keyup-change...');
  select.data().select2.search.trigger('keyup-change');
}, 3000);

示例: Plunker

编辑2: 这至少可以更新列表,但是如果在触发keyup-change事件之前输入了搜索文本,仍然会出现一些奇怪的情况。


按钮会根据所选内容进行AJAX调用,它漂浮在搜索输入框上方的原因是因为我们无法把它们放在外面,没有空间。用户将使用搜索,我对其应用了padding-left,以便文本在两个按钮之后开始。 - mfreitas
点击按钮之前下拉菜单中的内容是什么?您可以让ajax调用关闭select2下拉菜单,然后再次打开它。您还可以获取任何搜索文本并重新输入它并触发keydown,以使关闭/打开过程更加平滑。 - rtcherry
select2不能在调用之间关闭和重新打开,它只能被创建一次。将会发出一个调用来填充下拉列表,之后每次按下按钮A或B时都会发出一个调用并刷新下拉列表内容,这是困难的部分 :/ - mfreitas

1
对于Select2 4.X
var instance = $('#select2Container').data('select2');
var searchVal = instance.dropdown.$search.val();
instance.trigger('query', {term:searchVal});

0

这里有一个很好的例子:

    $(document).ready(function() {
        var $jobSelect = $('...');
        var $specificAccreditationTarget = $('..');

        $jobSelect.on('change', function() {
            $.ajax({
                url: url,
                data: {
                    'job.id': $jobSelect.val()
                },
                dataType : 'json',
                success: function (jsonArray) {
                    if (!jsonArray) {
                        $specificAccreditationTarget.find('select').remove();
                        $specificAccreditationTarget.addClass('d-none');
                        return;
                    }

                    $specificAccreditationTarget.empty();
                    $.each(jsonArray, function(index,jsonObject) {
                        var option = new Option(jsonObject.name, jsonObject.id, true, true);
                        $specificAccreditationTarget.append(option);
                    });

                }
            });

        });
    });

0

Diego的评论SpinyMan给出的答案非常重要,因为empty()方法将删除select2实例,因此任何自定义选项都将不再保留。如果您想保留现有的select2选项,则必须保存它们,销毁现有的select2实例,然后重新初始化。您可以像这样做:

const options = JSON.parse(JSON.stringify(
  $('#inputhidden').data('select2').options.options
));
options.data = data;

$('#inputhidden').empty().select2('destroy').select2(options);

我建议始终明确传递select2选项,因为上述代码仅复制简单选项而不包括任何自定义回调或适配器。另请注意,这需要select2的最新稳定版本(本文发布时为4.0.13)。
我编写了通用函数来处理此问题,并具有以下几个特点:
  • 可以处理返回多个实例的选择器
  • 使用现有的select2实例选项(默认)或传入一组新选项
  • 保留任何已选择的值(默认)且仍然有效,或完全清除它们
function select2UpdateOptions(
  selector,
  data,
  newOptions = null,
  keepExistingSelected = true
) {
  // loop through all instances of the matching selector and update each instance
  $(selector).each(function() {
    select2InstanceUpdateOptions($(this), data, newOptions, keepExistingSelected);
  });
}

// update an existing select2 instance with new data options
function select2InstanceUpdateOptions(
  instance,
  data,
  newOptions = null,
  keepSelected = true
) {
  // make sure this instance has select2 initialized
  // @link https://select2.org/programmatic-control/methods#checking-if-the-plugin-is-initialized
  if (!instance.hasClass('select2-hidden-accessible')) {
    return;
  }

  // get the currently selected options
  const existingSelected = instance.val();

  // by default use the existing options of the select2 instance unless overridden
  // this will not copy over any callbacks or custom adapters however
  const options = (newOptions)
    ? newOptions
    : JSON.parse(JSON.stringify(instance.data('select2').options.options))
  ;

  // set the new data options that will be used
  options.data = data;
      
  // empty the select and destroy the existing select2 instance
  // then re-initialize the select2 instance with the given options and data
  instance.empty().select2('destroy').select2(options);
      
  // by default keep options that were already selected;
  // any that are now invalid will automatically be cleared
  if (keepSelected) {
    instance.val(existingSelected).trigger('change');
  }
}

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