Initialising select2 created dynamically
Asked Answered
S

7

22

I have a select2 drop-down for which I provide a matcher function. It is initialised like this on initial page load:

jQuery(document).ready(function() {
    jQuery(".my_select2").select2({
        matcher: function(term, text) {...}
    });
});

That works find on initial page load.

Now, I have additional drop-downs (select elements created dynamically (pulled in via AJAX, i.e. jQuery(match).load(url). These additional drop-downs do not get intialised as select2 widgets, which is understandable, even though they match the original select2 selector.

So, how can I tell jQuery to treat these dynamically-created select elements as select2 items that need to be initialised? Can I set some kind of "watch" on matching elements so the select2 initialisation kicks in every time a matching element gets added to the page?

I recall live() being introduced in jQuery some time ago, that supported matching elements before they are created, if I understood it correctly. I never used that feature, and it now appears deprecated. But it does feel like the kind of thing I am looking for.

This is for a WordPress plugin, which uses jQuery v1.11.2 at present.

Statue answered 30/4, 2015 at 15:54 Comment(4)
You can have a look at github.com/uzairfarooq/arriveBlepharitis
This implies jQuery cannot watch for items being created, without a helper. Or was arrive.js created to make things easier for when jQuery is not being used at all?Statue
If you can't fire select2 after add the select item to the DOM, you must use a dom mutation observer like the first commentFrankel
Thanks, I am taking this approach now - explicitly firing select2 when adding the new select list to the DOM. It means there are two places the lists are initialised, but I've moved all the initialisation code to functions to keep repetition to a minimum. I think my first thought that the initialisation of new elements could be automated, was a bit optimistic. It can be done, as demonstrated in juna-c's answer, but it does rely on features in some more modern browsers, which is not reasonable to assume/enforce on end users in this application (a shop - and all customers are important).Statue
K
34

you can try with DOMNodeInserted and look for select or the class you're assigning them

Demo

$('body').on('DOMNodeInserted', 'select', function () {
    $(this).select2();
});

Update

DOMNodeInserted

Deprecated This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Avoid using it and update existing code if possible;

The suggested method would be something like this with MutationObserver

$(function() {
  $("button").on("click", function() {
    $("#dynamic-container").append($("<select><option>test</option><select/>"));
  });

  // select the target node
  var target = document.getElementById('dynamic-container');

  if (target) {
    // create an observer instance
    var observer = new MutationObserver(function(mutations) {
      //loop through the detected mutations(added controls)
      mutations.forEach(function(mutation) {
      //addedNodes contains all detected new controls
        if (mutation && mutation.addedNodes) {
          mutation.addedNodes.forEach(function(elm) {
          //only apply select2 to select elements
            if (elm && elm.nodeName === "SELECT") {
              $(elm).select2();
            }
          });
        }
      });
    }); 
    
    // pass in the target node, as well as the observer options
    observer.observe(target, {
      childList: true
    });

    // later, you can stop observing
    //observer.disconnect();
  }
});
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>


<button>Add new select</button>
  <div id="dynamic-container">

  </div>
Karlakarlan answered 30/4, 2015 at 16:17 Comment(5)
Thanks, that works well. I do want to try and avoid some of the duplication though - the select2 drop-downs are initialised in two separate places. I have a matcher, and an .on('change', ...) trigger on the dropdown, so those would need to be initialised also in both places. I'll look at how the intialisation can be defined in one place.Statue
I'll accept this as the answer, since it does work so solve the original question asked, even though I have changed my whole approach as a consequence of the answer anyway. The github.com/uzairfarooq/arrive package suggested wraps this to make it easier to use (i.e. more abstract). Thanks all.Statue
@Statue just to make things clear. arrive.js does not wrap DOMNodeInserted api. Internally it uses the new MutationObserver api. DOMNodeInserted api is deprecated and should be avoided.Bacteroid
Not working, I tested in Google Chrome. Even demo is not working.Floater
Works for me in Google ChromeHadwin
D
19

I came across a similar situation recently but did it in a very usual way:

$(document).ready(function() {

 //function to initialize select2
  function initializeSelect2(selectElementObj) {
    selectElementObj.select2({
      width: "80%",
      tags: true
    });
  }


 //onload: call the above function 
  $(".select-to-select2").each(function() {
    initializeSelect2($(this));
  });

 //dynamically added selects

  $(".add-new-select").on("click", function() {
    var newSelect = $("<select class='select-to-select2'  multiple><option>option 1</option><option>option 2</option></select>");
    $(".select-container").append(newSelect);
    initializeSelect2(newSelect);
  });


});
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/css/select2.css" rel="stylesheet" />

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/js/select2.min.js"></script>
<div class="select-container">
  <select class='select-to-select2' multiple>
    <option value='1'>option1</option>
    <option value='2'>option2</option>
  </select>

  <select class='select-to-select2' multiple>
    <option value='1'>option1</option>
    <option value='2'>option2</option>
  </select>

</div>
<div>
  <button class="add-new-select">Add New Select</button>
</div>

In case of the .load function find all the select elements which are to be initialized in the callback of load function and call initializeSelect2 on each of those select elements.

I hope this helps someone who is looking for a simple solution.

Desexualize answered 3/1, 2016 at 14:13 Comment(4)
Thanks @Roshan I have used your code for getting select2. I got stuck, the problem is only one of the selct2 is working other select have not get the select2 initializeSelect2Triton
Have you added select-to-select2 class to the select element?Desexualize
@ankitsuthar I was using jQuery clone() to duplicate the select element and Select2 was working only on the first one, like you said. I found out that you DON'T have to apply Select2 on the element you want to clone, but only to the cloned ones. Cloning a select with Select2 already applied generates unreliable results.Crissman
I used this with remote select2.js and dropzone.js, just need to get the function out of the document ready. Thanks my friendHansom
P
12

It works for me

<div id="somediv">
    <select class="component">
    ...
    </select>
</div>



<script>
    $(document).on('click', '#addComponent', function () {

        $('#somediv').append(data); //data is my new select
        $('.component:last').select2();
    });
</script>
Prepay answered 17/4, 2018 at 8:56 Comment(1)
This is pretty neat actually. I was thinking about using 'MutationObserver' but your solution worked like a charm.Noteworthy
T
0

I have faced the same issue, But after scratching my head for days, I found a solution but its not good if you expect good performance.

So when DOM loaded for first time, whatever select items are loaded will have select2 functionality, For dynamic generated fields select2 functionality wont be available because the select2 scripts are already loaded.

So to make the select2 work, reload the scripts dynamically on click.

function reload(id) {
    $(id).on("click", function () {
        $("head").append($("<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/css/select2.css' type='text/css' media='screen' />"));
        $.getScript("https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js", function () {
            $.getScript("https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/js/select2.min.js", function () { 
                $('select').select2();
            })
        })
    })
}

What this does is, it adds the select2 functionality to $.

Tarry answered 16/5, 2019 at 10:13 Comment(0)
F
0

Similar to above answer:

function initSelect2(element){
  element.select2({   });
}

// Dynamic add 
$('button[data-id="add-item"]').on('click', function(){
   // Add element
   obj.addItem();
   // Add Select2 to element
   initSelect2($('element'));
});
Fletafletch answered 8/11, 2019 at 3:36 Comment(0)
C
0

To add to other answers, select2 may return strange results if applied on

  1. hidden selects

or

  1. selects with the same ID

To not apply to hidden elements use the :visible selector. If you are dynamically generating elements, make sure the id and data-select2-id is unique (you can also remove it).

Chuu answered 21/12, 2022 at 11:49 Comment(0)
S
-1

I worked this out similar to roshan's solution, but didn't pass the select object in the function. This is for a table output from an ajax call.

$(document).ready(function() {

        function initSelect2() {
            $("[id^='DDAlertFreq']").select2({
                minimumResultsForSearch: Infinity,
                allowClear: false,
                theme: "bootstrap"
            });
        };

//define the dropdown and set to variable    
var DDAlertFrequency = '<select id="DDAlertFreq" ><option value="Fifteen_Minutes">15 Minutes</option><option value="Thirty_Minutes">30 Minutes</option><option value="Hour">Hour</option><option value="Two_Hours">2 Hours</option><option value="Three_Hours">3 Hours</option><option value="Four_Hours">4 Hours</option><option value="Six_Hours">6 Hours</option><option value="Eight_Hours">8 Hours</option><option value="Twelve_Hours">12 Hours</option><option value="Day">Day</option></select>'

function RetrieveUserAlerts(uid) {
                $.ajax({
                    url: 'SavedSearches.asmx/LoadUserAlerts',
                    dataType: 'json',
                    method: 'post',
                    data: { userid: uid },
                    success: function (data) {
                        var tbl = $("#tblAlerts > tbody");
                        tbl.empty();
                        $.each(data, function () {
                            userAlert.alert_idx = this['alert_idx'];
                            userAlert.Name = this['Name'];
                            userAlert.price_alert_low = this['price_alert_low'];
                            userAlert.alert_frequency = this['alert_frequency'];
                            userAlert.alert_max_per_day = this['alert_max_per_day'];
                            userAlert.alert_to_email = this['alert_to_email'];
                            userAlert.alert_to_sms = this['alert_to_sms'];
                            userAlert.active = this['active'];
                            userAlert.alert_start_date = moment(this['alert_start_date']).format("MM/DD/YY");
                            userAlert.alert_end_date = moment(this['alert_end_date']).format("MM/DD/YY");
                            userAlert.OpenSectionID = this['OpenSectionID'];
// modify the dropdown to assign unique id and match selection
                            var selectAlert = DDAlertFrequency.replace("DDAlertFreq", "DDAlertFreq_" + userAlert.alert_idx).replace('"' + userAlert.alert_frequency + '"', '"' + userAlert.alert_frequency + '" selected');
                            var tblRow = '<tr><td>'
                                + userAlert.Name
                             + '</td><td>'
                            + '<input id="txtPriceAlertLow_' + userAlert.alert_idx + '" type="text" class="form-control" value="' + userAlert.price_alert_low + '"/>'
                             + '</td><td>'
                            + '<input id="chkAlertToEmail_' + userAlert.alert_idx + '" type="checkbox" ' + ((userAlert.alert_to_email == true) ? "checked" : "") + ' />'
                             + '</td><td>'
                            + '<input id="chkAlertToEmail_' + userAlert.alert_idx + '" type="checkbox" ' + ((userAlert.alert_to_sms == true) ? "checked" : "") + ' />'
                             + '</td><td>'
                            + selectAlert //modified Select2 dropdown
                             + '</td><td>'
                             + '<input id="txtMaxPerDay_' + userAlert.alert_idx + '" type="text" class="form-control" value="' + userAlert.alert_max_per_day + '"/>'
                             + '</td><td>'
                            + userAlert.alert_start_date
                             + '</td><td>'
                            + userAlert.alert_end_date
                             + '</td><td>'
                           + '<input id="chkActive_' + userAlert.alert_idx + '" type="checkbox" ' + ((userAlert.active == true) ? "checked" : "") + ' />'
                             + '</td><tr>'
                            tbl.append(tblRow);
                            initSelect2(); //call the function to initialize all Select2 dropdowns created
                        });
                    },
                    error: function (err) {
                        console.log('Error (RetrieveUserAlerts): ' + JSON.stringify(err, null, 2));
                    }
                });
            };

Sorry for leaving in the extraneous stuff - I commented on areas of interest. Hope this helps others!

Sturgeon answered 23/11, 2016 at 8:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.