skip to Main Content

I have implemented 2 custom functions with select2:

  1. Select parent node, add parent node to selected and disable child nodes.
  2. Unselect parent node, remove from selected and enable child nodes.

This is working, except when adding and removing parent node in this order:

  1. Select parent
  2. Open emerging options (you can see child nodes are disabled)
  3. Click outside search box, so emerging options close
  4. Unselect parent
  5. Open emerging box again (child nodes are still disabled)

On the contrary, it works if step 2 and 3 are not done.

  1. Select parent
  2. Unselect parent
  3. Open emerging options (you can see child nodes are enabled back)

Please see a working example:

const tags = [{
    'id': 1,
    'text': 'Parent 1',
    'children': [{
        'id': 'tag11',
        'text': 'Tag 11'
      },
      {
        'id': 'tag12',
        'text': 'Tag 12'
      }, {
        'id': 'parent1',
        'text': 'Parent 1'
      },
    ],
  },
  {
    'id': 2,
    'text': 'Parent 2',
    'children': [{
        'id': 'tag21',
        'text': 'Tag 21'
      },
      {
        'id': 'tag22',
        'text': 'Tag 22'
      },
      {
        'id': 'parent2',
        'text': 'Parent 2'
      },
    ],
  }
];
$(document).ready(function() {

  const selectField = $('#target');
  
  selectField.select2({
    width: '300px',
    templateResult: function(option) {
      if (option.element && (option.element).hasAttribute('hidden')) {
        return null;
      }
      return option.text;
    }
  });

  selectField.on('select2:open', function(e) {
    let allOptionsStart = this.options;
    $('#select2-target-results').on('click', function(event) {
      let allOptions = [];
      $.each(allOptionsStart, (key, option) => {
        allOptions[option.value] = option;
      });

      const data = $(event.target).html();
      const selectedOptionGroup = data.toString().trim();

      let selectedOptionGroupId = '';
      $.each(tags, (key, tag) => {
        if (selectedOptionGroup.toString() === tag.text.toString()) {
          $.each(tag.children, (key, child) => {
            if (selectedOptionGroup.toString() === child.text) {
              selectedOptionGroupId = child.id;
            }
            const jTag = $(allOptions[child.id]);
            if (Object.keys(jTag).length > 0) {
              if (!jTag[0].hidden) {
                jTag[0].disabled = true;
              }
            }
          });
        }
      });
      $('select').select2({
        width: '300px',
        templateResult: function(option) {
          if (option.element && (option.element).hasAttribute('hidden')) {
            return null;
          }
          return option.text;
        }
      });
      event.stopPropagation();

      let options = selectField.val();
      if (options === null || options === '') {
        options = [];
      }
      options.push(selectedOptionGroupId);

      selectField.val(options);
      selectField.trigger('change'); // Notify any JS components that the value changed
      selectField.select2('close');
    });
  });

  selectField.on('select2:unselecting', function(e) {
    let allOptions = [];
    $.each(this.options, (key, option) => {
      allOptions[option.value] = option;
    });
    const unselectedGroupText = e.params.args.data.text;
    $.each(tags, (key, tag) => {
      if (unselectedGroupText === tag.text) {
        $.each(tag.children, (key, childTag) => {
          const jTag = $(allOptions[childTag.id]);
          if (Object.keys(jTag).length > 0) {
            if (!jTag[0].hidden) {
              jTag[0].disabled = false;
            }
          }
        });
      }
    });
  });

});
li.select2-results__option strong.select2-results__group:hover {
  background-color: #ddd;
  cursor: pointer;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>

<select id="target" data-placeholder="Select an option" multiple='multiple'>
  <optgroup label="Parent 1">
    <option value="tag11">Tag 11</option>
    <option value="tag12">Tag 12</option>
    <option value="parent1" hidden>Parent 1</option>
  </optgroup>
  <optgroup label="Parent 2">
    <option class="tag21">Tag 21</option>
    <option class="tag22">Tag 22</option>
    <option value="parent2" hidden>Parent 2</option>
  </optgroup>
</select>

jsfiddle (just in case): https://jsfiddle.net/ivantxo/bzu2xmp9/151/

Can anyone please help me to see what’s wrong?

This should work in both cases.

Thanks.

2

Answers


  1. I have figured out this much. I hope it will be helpful.

    <!DOCTYPE html>
    <html>
    <head>
        <title>Select2 Example</title>
      
        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    
        
        <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet" />
        <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
    
        <style>
            
            .parent {
                font-weight: bold;
            }
            .child {
                margin-left: 20px;
                color: #888;
            }
        </style>
    </head>
    <body>
        <select id="mySelect" multiple style="width: 300px;">
            <optgroup label="Parent 1" class="parent">
                <option value="parent1">Parent 1</option>
                <option value="child1_1" class="child" data-parent="parent1">Child 1.1</option>
                <option value="child1_2" class="child" data-parent="parent1">Child 1.2</option>
            </optgroup>
            <optgroup label="Parent 2" class="parent">
                <option value="parent2">Parent 2</option>
                <option value="child2_1" class="child" data-parent="parent2">Child 2.1</option>
                <option value="child2_2" class="child" data-parent="parent2">Child 2.2</option>
            </optgroup>
        </select>
    
        <script>
            $(document).ready(function() {
                // Initialize Select2
                $('#mySelect').select2();
    
                // Listen for changes on the parent options
                $('#mySelect').on('change', function() {
                    var selectedOptions = $(this).val();
    
                    // Track selected parents and children
                    var selectedParents = [];
                    var selectedChildren = [];
    
                    // Iterate through selected options
                    $.each(selectedOptions, function(index, value) {
                        // Check if the selected option is a parent
                        if (value.startsWith('parent')) {
                            selectedParents.push(value);
                        } else {
                            selectedChildren.push(value);
                        }
                    });
    
                    // Deselect any child options associated with selected parents
                    $.each(selectedParents, function(index, parentValue) {
                        $.each(selectedChildren, function(index, childValue) {
                            var childOption = $('.child[data-parent="' + parentValue + '"][value="' + childValue + '"]');
                            if (childOption.length > 0) {
                                childOption.prop('selected', false);
                            }
                        });
                    });
    
                    // Disable child options associated with selected parents
                    $('.child').prop('disabled', false); // Enable all child options
                    $.each(selectedParents, function(index, parentValue) {
                        $('.child[data-parent="' + parentValue + '"]').prop('disabled', true);
                    });
    
                    // Trigger Select2 change event to refresh the UI
                    $('#mySelect').trigger('change.select2');
                });
            });
        </script>
    </body>
    </html>
    Login or Signup to reply.
  2. One solution would be to add some classes to the options, to make identification easier.

    I placed the select2 plugin assignment in a function, to facilitate reuse, as in this implementation, the select2 destroy method will be used to reapply the plugin and update the display.

    I used click monitoring in the optgroups in a delegated way, for practical reasons. When the click event occurs, it compares the label of the select2 component with the label of the optgroup, to disable and remove the selection of child options with the optnormal class, in addition to selecting the option with the opthidden class.

    selectField.on('select2:unselecting', function(e) { allows you to find out which element was removed from the selection with e.params.args.data.element, simply checking if it is the option with the opthidden class to fetch the parent optgroup and re-enable the child options with the optnormal class.

    const selectField = $('#target');
    
    const setSelect2 = function() {
      selectField.select2({
        width: '300px',
        templateResult: function(option) {
          if (option.element && (option.element).hasAttribute('hidden')) {
            return null;
          }
          return option.text;
        }
      });
    };
    
    setSelect2();
    
    $(document.body).on('click', '.select2-results__option', function() {
      var label = $(this).find('.select2-results__group').html();
      $('optgroup.optgroup').each(function() {
        if (this.label === label) {
          $(this).find('option.optnormal').prop({
            'disabled': true,
            'selected': false
          });
          $(this).find('option.opthidden').prop('selected', true);
        }
      });
    
      selectField.select2('destroy');
      setSelect2();
    });
    
    selectField.on('select2:unselecting', function(e) {
      var $option = $(e.params.args.data.element);
      if ($option.hasClass('opthidden')) {
        $option.parents('optgroup').find('option.optnormal').prop('disabled', false);
        window.setTimeout(function() {
          selectField.select2('close');
          selectField.select2('destroy');
          setSelect2();
          selectField.select2('open');
        }, 500);
      }
    });
    li.select2-results__option strong.select2-results__group:hover {
      background-color: #ddd;
      cursor: pointer;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>
    
    <select id="target" data-placeholder="Select an option" multiple='multiple'>
      <optgroup label="Parent 1" class="optgroup">
        <option value="tag11" class="optnormal">Tag 11</option>
        <option value="tag12" class="optnormal">Tag 12</option>
        <option value="parent1" hidden class="opthidden">Parent 1</option>
      </optgroup>
      <optgroup label="Parent 2" class="optgroup">
        <option value="tag21" class="optnormal">Tag 21</option>
        <option value="tag22" class="optnormal">Tag 22</option>
        <option value="parent2" hidden class="opthidden">Parent 2</option>
      </optgroup>
    </select>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search