How do I detect when a selected option has been listed?

I am trying to implement a searchable, AJAX driven, multi-select to allow users to select one or more individuals, or groups of individuals to address a message to.

The dropdown choices are made up of individuals or groups of individuals matching the search term. For example, if the user were to type ‘par’, the choices would include all groups and individuals matching ‘par’ such as:

Steve Parks
Parents (10 members)

If the user were to select the ‘Parents’ choice from the choices above, the group name and each of its members are displayed as selected options e.g.

[Parents (10 members) x ] [Fred x] [ Joe x] etc.

I achieve this with the following code snippet in a function triggered by the ‘select2:select’ event:

    if ('users' in data) {
      var selected_group = $("option[value=" + data.id + "]", select_field);

      var options = [];

      // Add option for each group recipient unless already ready in the select list
      $.each(data.users, function( index, value ) {
        if (select_field.find("option[value='" + value.id + "']").length == 0) {
          var option = new Option(value.text, value.id, false, true);
          $(option).addClass('group-recipient');
          options.push(option);
        }
      });
      if ( options.length > 0 ) {
        select_field.append(options);
      }
    } 

As you can see, I am appending a new option for each member of the group (members are stored as a nested array within the selected ‘data’ item) and giving each option the class of ‘group-recipient’. I copy across the class in my templateSelection function:

    templateSelection: function (recipient) {
      // copy across class names from options, if any
      var $element = $(recipient.element);
  
      var $wrapper = $('<span></span>');
      $wrapper.addClass($element[0].className);
  
      $wrapper.text(recipient.text);
  
      return $wrapper;
    }

I would now like to hide the options for each group member and offer the user an option to toggle them (show/hide).

How can I detect that the options have been listed fully so that I can trigger some code to hide (display: none) all rendered select2 choices with the class name of ‘group-recipient’?

Thanks
Lee.

You can do that with a custom templateResult render function. I would suggest modifying your code above to store the id values of the child options in an array* that you can access from your custom render function. Then when the render function is called with each item in the result set, you can test its id against your “selected group members” array, and if that ID value is in the array and the display toggle is set to “hide”, return null from the render function, which will prevent that item from displaying in the results list. (If the item’s ID value is not in the array, or the toggle is set to “show”, then you just return that item.)

*Note: the separate array is more efficient, but you could simply search the HTML select's list of options for those that have the “group-recipient” class, and from that subset check whether any of them has the same id value as the current item passed to your custom render function.

However, I think you’re opening yourself up to some potential issues. Assuming the results list dropdown might be visible while the selected child options are displayed in the selected list, if the user removes one of those items from the selected list you’ll need to decide whether to redisplay that item in the dropdown (which you may or may not want to do). If you do want to do that, I’m not sure there’s an easy way without manually inserting items in the Select2 dropdown. You will also need to make sure you remove the matching de-selected items from the underlying HTML select.

Thanks for your reply.

I’m actually trying to hide options that have been selected.

To clarify, the dropdown might include an option called ‘Parents’ which represents 10 members.

If the user selects the ‘Parents’ option, it appears in the list of user choices (selections) and its members are also added as ‘hidden’ selected options ( display: none so that they remain ‘selected’), one option per member.

I’ve tried the following in a select2.change callback in an attempt to hide the select2 li elements:

    if (choices_to_hide.length > 0) {
      $('.select2-container .group-recipient').each( function() {
        $(this).parent().hide();
      });
    }

This does hide the li elements temporarily but then select2 redraws the list of items. If I change the above to add a class to the select2 li element (so a CSS selector can target it with display: none), the class is also lost when select2 redraws the list.

The simple approach of having a CSS selector target the contents of the select2 li element with display: none leaves the remove ‘X’ in the list of items - that is, only the second span below is hidden.

<li class="select2-selection__choice" ...>
  <span class="select2-selection__choice__remove" ...>x</span>
  <span class="group-recipient" ...>Fred</span>
</li>

I’ve also tried removing the relevant options from select_field.val() but this is undesirable as they are no longer ‘selected’.

Is there a reliable way to hide selected items (choices) whilst preserving their selected status?

Yes, as I described before, you can do this in a custom templateResult render function. The templateResult callback allows you to control the display of individual options in the Select2’s dropdown results panel.

Thanks but I am trying to control the display of the options after they have been chosen from the dropdown results, not those displayed in the dropdown.

As described above, and by way of example, when a user selects the ‘Parents’ option from the dropdown, this item would be listed as a ‘selected’ item. A selected item would also be created for each of its 10 members but these would be hidden initially (but still selected).

I’ve tried returning null from the ‘selectionTemplate’ but this renders an empty item with a ‘X’.

Ideally I’d like to find a way to append a class to the containing

  • tag so I can hide the list item.
  • In that case you will probably have to override the default SelectionAdapter. Unfortunately, there’s not much documentation about how to do that. Your best bet is probably to study the source code for the default SelectionAdapter on the Select2 GitHub site.