Only allow 1 option from an optgroup to be selected in select2

My goal is to only allow 1 option in an optgroup to be selected from the select2 plugin.

Please see this code example: https://jsfiddle.net/xpvt214o/554423/

The problem I’m having is with this code:

$portSelect.on('select2:unselect', function (evt) {
  $portSelect.find('optgroup').each(function (gindex) {
    var $group = $(this);
    var selected = 0;

    $group.children().each(function (cindex) {
      if ($(this).prop('selected') === true) {
        selected++;
      }
    });

    if (selected != 0) {
      $group.children().removeAttr('disabled');
    }
  });

  $portSelect.select2('destroy').select2(select2options);
});

Specifically the $portSelect.select2('destroy').select2(select2options); line. Commenting this line prevents the error from happening, but does not refresh the select2 options…

When you select an option, then deselect that option, you get the following console error:
select2.min.js:1 Uncaught TypeError: Cannot read property 'query' of null

I posted this on StackOverflow but had no answers.

While it does look like you might have found a bug, you’re also doing way more work than you need to accomplish what you want:

  1. You don’t need to loop through the optgroups and their children to find the selected option; the “select2:select” and “select2:unselect” events have a “params” object that includes a reference to the option that was selected (or unselected). You can simply wrap that element in $() for ease of updating its siblings.
  2. You shouldn’t need to destroy and re-initialize the widget at the end of the event handler; instead you should be able to trigger the “change.select2” event to update the state of the widget after modifying the underlying select. And this does work if you only have one selection and you then unselect it, or if you have multiple selections and you unselect them all with the “x” at the right of the input box.
    But if you unselect them one at a time, only the siblings of the first option you unselected become selectable again in the widget (even though the underlying select is actually updated correctly). That seems to be a bug, but destroying and re-initializing the widget does what you want in all situations, although I had to wrap it in a setTimeout call to make it work consistently (triggering “change.select2” in a setTimeout call didn’t work either).

So here’s my rewrite of your “select2:unselect” event handler that works without errors in all situations (at least all I could think to test):

$portSelect.on('select2:unselect', function (evt) {
  $(evt.params.data.element).siblings().removeAttr('disabled');
  setTimeout(function() { 
   	$portSelect.select2('destroy').select2(select2options);
  }, 0);
});

I’ll leave it to you to rewrite your “select2:select” handler using similar techniques.

1 Like