Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Making Select2 (4.x) list boxes cascading / dependent. Options of a select2 list box will be loaded/refreshed by ajax based on selection of another select2 list box.
/**
* A Javascript module to loadeding/refreshing options of a select2 list box using ajax based on selection of another select2 list box.
*
* @url : https://gist.github.com/ajaxray/187e7c9a00666a7ffff52a8a69b8bf31
* @auther : Anis Uddin Ahmad <anis.programmer@gmail.com>
*
* Live demo - https://codepen.io/ajaxray/full/oBPbQe/
* w: http://ajaxray.com | t: @ajaxray
*/
var Select2Cascade = ( function(window, $) {
function Select2Cascade(parent, child, url, select2Options) {
var afterActions = [];
var options = select2Options || {};
// Register functions to be called after cascading data loading done
this.then = function(callback) {
afterActions.push(callback);
return this;
};
parent.select2(select2Options).on("change", function (e) {
child.prop("disabled", true);
var _this = this;
$.getJSON(url.replace(':parentId:', $(this).val()), function(items) {
var newOptions = '<option value="">-- Select --</option>';
for(var id in items) {
newOptions += '<option value="'+ id +'">'+ items[id] +'</option>';
}
child.select2('destroy').html(newOptions).prop("disabled", false)
.select2(options);
afterActions.forEach(function (callback) {
callback(parent, child, items);
});
});
});
}
return Select2Cascade;
})( window, $);

Select2 Cascade (for v4.x)

Loadeding/refreshing options of a select2 list box (using ajax) based on selection of another select2 list box.

Check the live demo.

How to use

Create a new instance of Select2Cascade by passing the following 4 things -

  • Parent select2 listbox
  • Child select2 listbox
  • URL to load child items from
  • (OPTIONAL) select2 options

To set the parent selected value in ajax request, keep :parentId: as a placeholder in url. The selected value of parent select2 listbox will replace the :parentId: string in url. For example -
Query string: /path/to/api?type=childType&parent_id=:parentId:
RESTful url: /path/to/api/items/:parentId:/sub-items

The response json should be a flat object of value:label pairs. e,g,

{
  "20150415" : "Chittagong Zila",
  "20190901" : "Comilla Zila",
  "20221601" : "Cox's Bazar Zila",
  "20301401" : "Feni Zila"
}

Otherwisw you have to adjust the way of populating child options (at line 29).

Examples

When #parentList is changed, call to path/to/geocode/district/SELECTED-PARENT/zilla and set the options of #childList from the ajax response.

var options = { width: 'resolve' };
new Select2Cascade($('#district'), $('#zilla'), 'path/to/geocode/district/:parentId:/zilla', options);
new Select2Cascade($('#zilla'), $('#thana'), 'path/to/geocode/zilla/:parentId:/thana', options);

If you want to do something with the response data or selectboxes, you can set (any number of) callbacks to be executed after the child listbox refreshed -

var cascadLoading = new Select2Cascade($('#parent'), $('#child'), 'path/to/api/categories?parent_id=:parentId:');
cascadLoading.then( function(parent, child, items) {
    // Open the child listbox immediately
    child.select2('open');
    // Dump response data
    console.log(items);
})

m47730 commented Feb 13, 2017

/**
 * A Javascript module to loadeding/refreshing options of a select2 list box using ajax based on selection of another select2 list box.
 *
 * @url : https://gist.github.com/ajaxray/187e7c9a00666a7ffff52a8a69b8bf31
 * @auther : Anis Uddin Ahmad <anis.programmer@gmail.com>
 *
 * Live demo - https://codepen.io/ajaxray/full/oBPbQe/
 * w: http://ajaxray.com | t: @ajaxray
 */
var Select2Cascade = ( function(window, $) {

    function Select2Cascade(parent, child, url, options) {
        var afterActions = [];

        // Register functions to be called after cascading data loading done
        this.then = function(callback) {
            afterActions.push(callback);
            return this;
        };

        parent.select2(options).on("change", function (e) {

            child.prop("disabled", true);
            var _this = this;

            $.getJSON(url.replace(':parentId:', $(this).val()), function(items) {
                var newOptions = '<option value=""></option>';
                for(var id in items) {
                    newOptions += '<option value="'+ id +'">'+ items[id] +'</option>';
                }

                child.select2('destroy').html(newOptions).prop("disabled", false)
                    .select2(options);

                afterActions.forEach(function (callback) {
                    callback(parent, child, items, options);
                });
            });
        });
    }

    return Select2Cascade;

})( window, $);

a simple modification to support select2 options (as theme)

Owner

ajaxray commented Feb 21, 2017

@m47730, Very good idea! we can keep the options open for the user.
I am going to add this. Thank you very much 👍

@ajaxray how would you set a default value for the child when the value for parent is already selected on load?

Owner

ajaxray commented Mar 7, 2017

@magicalbanana I guess, as we are re-generating child values from html <option>s, if you keep the attribute selected with an option, select2 will remain it selected by default.

nicpot commented Mar 26, 2017

@ajaxray Thanks for the nice code! I'm using 3 cascaded select2 dropdowns in a bootstrap modal - so I'm re-initializing and destroying the select2 dropdowns in the bootstrap show.bs.modal and hide.bs.modal. Here's what I have:

$("#addressModal").on("hide.bs.modal", function (e) {
    $("#addressProvinceIdId").select2("destroy");
    $("#addressDistrictId").select2("destroy");
    $("#addressMunicipalityId").select2("destroy");
   
    // some other code...
});

$("#addressModal").on("show.bs.modal", function (e) {
    var select2Options = {
        placeholder: "- Select -",
        theme: "bootstrap",
        triggerChange: true,
        allowClear: false,
        dropdownAutoWidth: true,
        minimumResultsForSearch: 50
    };

    $("#addressProvinceId").select2(select2Options);
    $("#addressDistrictId").select2(select2Options);
    $("#addressMunicipalityId").select2(select2Options);

    var provinceCascade = Select2Cascade($("#addressProvinceId"), $("#addressDistrictId"), "/someurl/getdistricts?provinceId=:parentId:", select2Options);
    var districtCascade = Select2Cascade($("#addressDistrictId"), $("#addressMunicipalityId"), "/someurl/getmunicipalities?districtId=:parentId:", select2Options);

    // some other code...
});

I noticed that the Select2Cascade ajax requests would increase as the modal is opened/closed on the same page. So I guess I need to re-init and destroy the Select2Cascade functions as well. What would be the correct way to do this?

Owner

ajaxray commented Apr 1, 2017

Hello @nicpot,
As we are binding ONLY Select2's change event for handling the association, it it will automatically unbind it on destroy() call.

pjam commented Jun 26, 2017

Hi @ajaxray

The placeholder option should be optional or its text should be customizable. Thinking about other languages.

Great piece of code, by the way.

select2cascade code is interesting for me,
but when i see the live demo, i think the json is not working.

Owner

ajaxray commented Sep 28, 2017

@pjam Thanks :)

Owner

ajaxray commented Sep 28, 2017

@jazzsnap Thanks. Right you are, the blob was no more there. FIxing ASAP.
Thanks again for informing 👍

Thanks for the script! A couple of things I tweaked:

  • A new callback for data processing
  • The option builder operates on the same data structure that select2 .ajax expects:
    [{"id": val, "text": textval}, {"id": val, "text": textval},...]
    Because it seemed counterproductive to NOT use the same data structure. The ajax endpoint should not need to be altered to send data to a cascading select2 rather than a regular ajax select2.
function Select2Cascade(parent, child, url, select2Options) {
  var afterActions = [];
  var handleData = [];
  var options = select2Options || {};

  // Register functions to be called after cascading data loading done
  this.then = function(callback) {
    afterActions.push(callback);
    return this;
  };
  // register functions to be called for data translation <-----------------------------
  this.formatData = function(callback) {
    handleData.push(callback);
  }

  parent.select2(select2Options).on("change", function (e) {
    child.prop("disabled", true);
    var _this = this;

    $.getJSON(url.replace(':parentId:', $(this).val()), function(response) {
      var newOptions = '<option value="">-- Select --</option>';
      var newData = response; // <-----------------------------------------
      handleData.forEach(function(callback) {
        newData = callback(child, response);
      });
      newData.forEach(function(item) {
        newOptions += '<option value="'+ item.id +'">'+ item.text +'</option>';
      });
      // end of changes <--------------------------------------------------
      child.select2('destroy').html(newOptions).prop("disabled", false)
           .select2(options);

      afterActions.forEach(function (callback) {
        callback(parent, child, response);
      });
    });
  });
}

For my data, I instantiated the cascade with:

  $('#parent, #child').select2();
  var url = 'blah?lookup=:parentId:';
  var cascader = new Select2Cascade($('#parent'), $('#child'), url, {});
  cascader.then(function(parent, child, items) {
    child.select2('open');
  });
  cascader.formatData(function(child, items) {
    var data = $.map(items, function (item) {
      item.id = item.myId;
      item.text = '(' + item.myId + ') ' + item.description;
      return item;
    });
    return data;
  });
});
Owner

ajaxray commented Dec 9, 2017

Hello @L5eoneill ,
Definitely very good addition! 👍
I'll soon update the script and will add the callback for data processing functionality.

About using {"id": val, "text": textval} as default format, I'm not yet sure. The reason behind using {id: "textVal"} was, it's generic output of most of the list APIs. So I didn't want people to make another, custom API only for feeding Select2 list boxes.

L5eoneill commented Dec 13, 2017

Has anybody had problems making this work in IE11/Edge? The browser quits as soon as it hits the $.getJSON line, with no apparent reason. Stepping in, I find it actually quitting inside Select2's (version 4.0, line 637) listeners[i].apply(this, params);

Our other usages of Select2 do not have any problem, in IE or anywhere.

The only way I can make cascading work in IE is to make the parent NOT be a select2.

UPDATE: my colleague discovered that if you remove child.prop("disabled", true);, then IE/Edge is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment