Reputation: 2405
I am converting an <input type="hidden">
to a select2 dropdown and feeding it data through the query method
$('#inputhidden').select2({
query: function( query ) {
query.callback( data ); // the data is in the format select2 expects and all works well..
);
});
The problem is i needed to hack the select2 UI and position two buttons on top of the search bar that, when clicked, will perform ajax calls and will have to update the select2 content.
Now, I need those updates to occur without rebuilding the select2 entirely but rather just refreshing the items on the dropdown. I cannot find a way to pass a new set of data to an already created select2 control, is that possible ?
Upvotes: 51
Views: 182108
Reputation: 8276
Diego's comment on the answer given by SpinyMan is important because the empty()
method will remove the select2 instance, so any custom options will no longer be retained. If you want to keep existing select2 options you must save them, destroy the existing select2 instance, and then re-initialize. You can do that like so:
const options = JSON.parse(JSON.stringify(
$('#inputhidden').data('select2').options.options
));
options.data = data;
$('#inputhidden').empty().select2('destroy').select2(options);
I would recommend to always explicitly pass the select2 options however, because the above only copies over simple options and not any custom callbacks or adapters. Also note that this requires the latest stable release of select2 (4.0.13 at the time of this post).
I wrote generic functions to handle this with a few features:
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');
}
}
UPDATE 2024-04-12: I decided to revisit this answer several years later, after posting a solution to the question How to change placeholder in select2? The most common use case is to keep existing configuration options, but I wanted to revisit the answer and make it a little more flexible, defaulting to overriding configuration options that are passed in rather than replacing them entirely. Otherwise, you have to save the initial options and pass them back into this function.
I also wanted to do some renaming to help clarify the difference between the data options and the select2 configuration options, both in the function names and the variable names. Here is an alternative solution:
// update data options for matching select2 instance(s), default to reselect
// any previously selected options that still exist in the replaced data;
// can also override (default) or replace the select2 configuration options;
function select2UpdateData(
elem,
data,
updatedConfigOptions = {},
keepExistingSelectedData = true,
replaceConfigOptions = false
) {
// loop through all instances of the matching selector and update each instance
toJQuery(elem).each(function() {
select2InstanceUpdateData(
$(this),
data,
updatedConfigOptions,
keepExistingSelectedData,
replaceConfigOptions
);
});
}
// update an individual select2 instance with new data options
function select2InstanceUpdateData(
instance,
data,
updatedConfigOptions = {},
keepExistingSelectedData = true,
replaceConfigOptions = false
) {
// make sure this instance has select2 initialized
if (!instance.hasClass('select2-hidden-accessible')) {
return;
}
// get existing configuration options and the currently-selected data
const existingConfigOptions = JSON.parse(
JSON.stringify(instance.data('select2').options.options)
);
const existingSelected = instance.val();
// by default, keep the original config options and override;
// otherwise if specified, replace the original options entirely
const options = (replaceConfigOptions)
? updatedConfigOptions
: Object.assign({}, existingConfigOptions, updatedConfigOptions)
;
// 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
instance.empty().select2('destroy').select2(options);
// by default re-select data that was already selected; any previously-selected
// data that no longer exists will automatically be cleared
if (keepExistingSelectedData) {
instance.val(existingSelected).change();
}
}
Upvotes: 0
Reputation: 2280
Here's a good example:
$(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);
});
}
});
});
});
Upvotes: 0
Reputation: 1432
I solved this issue by using the ajax option and specifying a custom transport function.
Here is the relevant js to get this to work.
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++;
});
Upvotes: 4
Reputation: 667
var selBoxObj = $('#selectpill');
selBoxObj.trigger("change.select2");
Upvotes: 7
Reputation: 484
Try this one:
var data = [{id: 1, text: 'First'}, {id: 2, text: 'Second'}, {...}];
$('select[name="my_select"]').empty().select2({
data: data
});
Upvotes: 15
Reputation: 22561
If you have local array with options (received by ajax call), i think you should use data
parameter as function returning results for select box:
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"}];
});
FIDDLE: http://jsfiddle.net/RVnfn/576/
In case if you customize select2 interface with buttons, just call updateResults
(this method not allowed to call from outsite of select2 object but you can add it to allowedMethods
array in select2 if you need to) method after updateting data array(pills in example).
Custom data adapter with additional updateOptions
(its unclear why original ArrayAdapter
lacks this functionality) method can be used to dynamically update options list (all options in this example):
$.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/
in v4 you can define custom transport method that can work with local data array (thx @Caleb_Kiage for example, i've played with it without succes)
docs: https://select2.github.io/options.html#can-an-ajax-plugin-other-than-jqueryajax-be-used
Select2 uses the transport method defined in ajax.transport to send requests to your API. By default, this transport method is jQuery.ajax but this can be changed.
$('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);
}
}
});
BUT with this method you need to change ids of options if text of option in array changes - internal select2 dom option element list did not modified. If id of option in array stay same - previous saved option will be displayed instead of updated from array! That is not problem if array modified only by adding new items to it - ok for most common cases.
FIDDLE: https://jsfiddle.net/xu48n36c/3/
Upvotes: 60
Reputation: 11
For Select2 4.X
var instance = $('#select2Container').data('select2');
var searchVal = instance.dropdown.$search.val();
instance.trigger('query', {term:searchVal});
Upvotes: 1
Reputation: 1369
Using Select2 4.0 with Meteor you can do something like this:
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")
})
What's happening:
This works for any reactive data source - such as a Collection.find().fetch() - not just Session.get().
NOTE: as of Select2 version 4.0 you must remove existing options before adding new onces. See this GitHub Issue for details. There is no method to 'update the options' without clearing the existing ones.
The above is coffeescript. Very similar for Javascript.
Upvotes: 16
Reputation: 1844
I think it suffices to hand the data over directly:
$("#inputhidden").select2("data", data, true);
Note that the second parameter seems to indicate that a refresh is desired.
Thanks to @Bergi for help with this.
If that doesn't automatically update you could either try calling it's updateResults method directly.
$("#inputhidden").select2("updateResults");
Or trigger it indirectly by sending a trigger to the "input.select2-input" like so:
var search = $("#inputhidden input.select2-input");
search.trigger("input");
Upvotes: 19
Reputation: 4880
As best I can tell, it is not possible to update the select2 options without refreshing the entire list or entering some search text and using a query function.
What are those buttons supposed to do? If they are used to determine the select options, why not put them outside of the select box, and have them programmatically set the select box data and then open it? I don't understand why you would want to put them on top of the search box. If the user is not supposed to search, you can use the minimumResultsForSearch option to hide the search feature.
Edit: How about this...
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);
Example: Plunker
Edit 2: That will at least get it to update the list, however there is still some weirdness if you have entered search text before triggering the keyup-change
event.
Upvotes: 2