Update select2 data without rebuilding the control
Asked Answered
A

10

51

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.

enter image description here

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 ?

Altitude answered 10/5, 2013 at 11:8 Comment(0)
C
60

select2 v3.x

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).


select2 v4: custom data adapter

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/


select2 v4: ajax transport function

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/

Consonant answered 27/6, 2013 at 16:25 Comment(5)
Check the following fiddle.. it's render the result but filter not working.. jsfiddle.net/RVnfn/106Bozeman
@JeevaJsb you are adding objects with 'check' key... 'text' key is needed for search!Consonant
@Consonant thanks for edit, now it works fine, I have removed my comment (cannot be edited after 5minutes)Kokaras
@Consonant you rock, this is exactly what I needed, thanks!Faroff
The "ajax transport function" works very well, that's exactly what I was looking for! Small remark though, you parsing the input query as a regex will break if you enter syntax that is not a valid regex. Just try something like /test(, and it crashes. I switched to a generic insensitive string includes check.Coopt
P
19

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");
Poky answered 3/6, 2013 at 15:43 Comment(4)
@AnuradhaJayathilaka It is defined by select2 herePoky
Thats weird. The method is not on the doc and when i ran it it gave and error.Recha
@AnuradhaJayathilaka The solution to that might be in @ndpu's answer about allowedMethodsPoky
search.trigger("change") also appears to work well.Endear
S
16

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:

  1. When Template is rendered...
  2. Init a select2 control with data from Session.
  3. @autorun (this.autorun) function runs every time the value of Session.get("select2Data") changes.
  4. Whenever Session changes, clear existing select2 options and re-create with new data.

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.

Swaine answered 19/10, 2015 at 11:12 Comment(7)
empty()-ing first was the only way i could get it to work, yet it does also remove my additional constructor options when i re-initialize it.Lahey
There was no mention of meteor in the question.Empyema
@Empyema true - however I landed here looking for a solution that will work with Meteor. I followed the answers above and adapted for Meteor. Maybe others will land here too.Swaine
On 4.0 this answer resets the control, which was what the OP was trying to avoid in the first place.Edla
Depends what you mean by "reset the control". OP wrote "I need those updates to occur without rebuilding the select2 entirely but rather just refreshing the items on the dropdown." This solution should do that. See the referenced GitHub issue - I believe this is the best you can do in 4.0. Feel free to edit if you can do better.Swaine
Very helpful tip about no method to 'update the options' - thank you!Tadpole
@DataMeister You're better of creating a new question and answering it with your own relevant solution, so when someone searches for it they'll find the answer to what they've been searching for.Papillote
H
13

Try this one:

var data = [{id: 1, text: 'First'}, {id: 2, text: 'Second'}, {...}];
$('select[name="my_select"]').empty().select2({
    data: data
});
Homework answered 29/1, 2019 at 12:37 Comment(2)
This code is clear to understand: when we empty the selectbox select2 updates itself and when we add new options' data select2 refreshes again. So, no need to trigger "change" and "change.select2".Homework
This rebuilds select2, which means if you had any configurations they will be lost. If you had any events attached, you need to detach them first and then re-attach.Windgall
E
7
var selBoxObj = $('#selectpill');
selBoxObj.trigger("change.select2");
Exhortation answered 25/5, 2016 at 9:29 Comment(0)
R
4

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++;
});
Rondi answered 30/4, 2017 at 15:41 Comment(2)
Select2 dynamic options demo link https://jsfiddle.net/calebkiage/oxm4gf4y/ showing page not found.Nugatory
@w.Daya, my fiddle has been deleted :-( I'll remove the link but the JS code I used is all in the answer.Rondi
P
2

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.

Pickett answered 12/5, 2013 at 23:47 Comment(3)
the buttons will make ajax calls depending on what is chosen, and the reason its floating above the search input is because we cannot afford to have them outside, there is no space for them. The user will use the search, i applied padding-left to it so the text begins after the two buttons..Altitude
What are the contents of the drop down prior to clicking one of the buttons? You could have the ajax call close the select2 drop down and then open it again. You could also have it grab any search text and re-enter it and trigger a keydown in order to make the close/open seem less jarring.Pickett
the select2 cannot close and reopen between calls, it must only be created once, a call will be made to populate the dropdown, after that, each time u press Btn A or B they will make a call and refresh the dropdown contents only, which is the difficult part :/Altitude
R
1

For Select2 4.X

var instance = $('#select2Container').data('select2');
var searchVal = instance.dropdown.$search.val();
instance.trigger('query', {term:searchVal});
Remittance answered 25/8, 2016 at 12:28 Comment(0)
W
0

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:

  • can handle selectors that return multiple instances
  • use the existing select2's instance options (default) or pass in a new set of options
  • keep any already-selected values (default) that are still valid, or clear them entirely
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();
  }
}
Waterman answered 7/10, 2020 at 21:39 Comment(0)
A
0

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

                }
            });

        });
    });
Amaty answered 2/2, 2023 at 9:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.