Selectable optgroup using select2
Asked Answered
A

9

18

I have used select2 to select multiple options from a drop down, but is it possible for select2 to select the full optgroup?? What I want is when user select the option group all the child options should be selected. And I want to do this using jQuery Select2. How could I do this?

Ark answered 17/11, 2014 at 3:7 Comment(1)
I have fixed the issue and to future reference to others I have added the full code + the select all option vinurip.blogspot.com/2014/11/…Ark
S
26

This is possible if you back the Select2 with a hidden input element -- instead of a select element.

To make a group option selectable, you must give it an "id", but it appears it can be an empty string. You can then use the "select2-selecting" event to prevent the group option from getting selected, and instead have its child options get selected.

Additionally, a query function can be provided to prevent a group option from appearing in the list after all its child options have been selected.

If you have options defined like this:

var FRUIT_GROUPS = [
    {
        id: '',
        text: 'Citrus',
        children: [
            { id: 'c1', text: 'Grapefruit' },
            { id: 'c2', text: 'Orange' },
            { id: 'c3', text: 'Lemon' },
            { id: 'c4', text: 'Lime' }
        ]
    },
    {
        id: '',
        text: 'Other',
        children: [
            { id: 'o1', text: 'Apple' },
            { id: 'o2', text: 'Mango' },
            { id: 'o3', text: 'Banana' }
        ]
    }
];

You can instrument your Select2 like this:

$('#fruitSelect').select2({
    multiple: true,
    placeholder: "Select fruits...",
    data: FRUIT_GROUPS,
    query: function(options) {
        var selectedIds = options.element.select2('val');
        var selectableGroups = $.map(this.data, function(group) {
            var areChildrenAllSelected = true;
            $.each(group.children, function(i, child) {
                if (selectedIds.indexOf(child.id) < 0) {
                    areChildrenAllSelected = false;
                    return false; // Short-circuit $.each()
                }
            });
            return !areChildrenAllSelected ? group : null;
        });
        options.callback({ results: selectableGroups });
    }
}).on('select2-selecting', function(e) {
    var $select = $(this);
    if (e.val == '') { // Assume only groups have an empty id
        e.preventDefault();
        $select.select2('data', $select.select2('data').concat(e.choice.children));
        $select.select2('close');
    }
});

jsfiddle

Here is a jsfiddle without the query function, where the group options still appear when all of their child options are selected.

Smoker answered 19/11, 2014 at 6:8 Comment(9)
hi, thanks for the answer but what dose this line do ? $select.select2('val', $select.select2('val').concat(childIds)); what I have is a html option list with optgroups I want it to combine mine but can't get this lineArk
is it possible when all the options are selected under the opt group name it say "all options selected"???Ark
@user1269524 - The line $select.select2('val', $select.select2('val').concat(childIds)); sets the selected options. It adds all the child options of the selected parent option to all the previously selected options, and then gives that list to the Select2 control.Smoker
@user1269524 - I could not get this to work with a <select element because I do not see a way to make the <optgroup> elements selectable. You would have to switch to use a hidden input element with the data like I show.Smoker
@user1269524 - I updated my answer. I included a new jsfiddle that hides a group option if all its children are selected. You could have it alter the text instead if you want.Smoker
I have a problem with this code. link look at this code. It is your code but I added a new 3 groups. When I choose for example "Mango3" then scrolling to the bottom of list and just moving the mouse at the area of select then the scrolls goes automatically to the up. I need to fix it but I do not know what is actually does inside this. Is there fix?Aubarta
@Sidron - When you added the additional items, you did not give them unique id values. If you fix that, the scrolling issue goes away.Smoker
Thanks for the answer, i need more help for this, need to set selected some values by default.Generality
There are some Mixed Content errors on the linked jsfiddles. Here are some fixed versions: jsfiddle #1, jsfiddle #2Garnes
C
3

I found a plugin for Select2 v4 which adds the ability to click on the optgroup to select/unselect all of child options. It worked perfectly for me. bnjmnhndrsn/select2-optgroup-select

Thanks Ben Henderson!

Causation answered 18/12, 2018 at 14:42 Comment(0)
W
3

One option for V 4.0.2 using a select element:

<select style="width:100%;" id="source" multiple="" tabindex="-1" aria-hidden="true">                 
   <optgroup class="select2-result-selectable" label="Statuses"   >        
      <option value="1">Received</option>                          
      <option value="2">Pending Acceptance</option>                             
   </optgroup>                                 
   <optgroup class="select2-result-selectable" label="Progress" >                
      <option value="6">In Progress</option>
      <option value="7">Follow Up</option>                         
  </optgroup>                                                    
</select>

JS + JQuery:

$(document).ready(function() {

   $('#source').select2();

$(document).on("click", ".select2-results__group", function(){

    var groupName = $(this).html()
    var options = $('#source option');

    $.each(options, function(key, value){

        if($(value)[0].parentElement.label.indexOf(groupName) >= 0){
            $(value).prop("selected","selected");
        }

    });

    $("#source").trigger("change");
    $("#source").select2('close'); 

  });
});

Fiddle: https://jsfiddle.net/un1oL8w0/4/

Womenfolk answered 23/10, 2019 at 17:23 Comment(1)
thank's wok fine ;)Poor
V
2

I've used John's code, but my problem was that I needed the ability to filter, so I added it. You can see the code working on jsfiddle.

This is the query code:

         query: function (options) {
            var selectedIds = options.element.select2('val');
            var data = jQuery.extend(true, {}, FRUIT_GROUPS);
            var selectableGroups = $.map(data, function (group) {
                var areAllChildrenSelected = true,
                    parentMatchTerm = false,
                    anyChildMatchTerm = false;
                if (group.text.toLowerCase().indexOf(options.term.toLowerCase()) >= 0) {
                    parentMatchTerm = true;
                }
                var i = group.children.length
                while (i--) {
                    var child = group.children[i];

                    if (selectedIds.indexOf(child.id) < 0) {
                        areAllChildrenSelected = false;
                    };

                    if (options.term == '' || (child.text.toLowerCase().indexOf(options.term.toLowerCase()) >= 0)) {
                        anyChildMatchTerm = true;
                    }
                    else if (!parentMatchTerm) {
                        var index = group.children.indexOf(child);
                        if (index > -1) {
                            group.children.splice(index, 1);
                        };
                    };
                };

                return (!areAllChildrenSelected && (parentMatchTerm || anyChildMatchTerm)) ? group : null;
            });

            options.callback({ results: selectableGroups });
        }
Vitalize answered 29/4, 2015 at 22:20 Comment(2)
Thanks for the answer, i need more help for this, need to set selected some values by default.Generality
jsfiddle with Mixed Content warning fixedGarnes
M
2

First give id to your select for example

<select style="width: 95%" id="selectgroup">

and then add class to your optgroup like

 <optgroup value="ATZ" label="Alaskan/Hawaiian Time Zone" class="select2-result-selectable">

and then add this

$('#selectgroup').select2({

    }).on('select2-selecting', function (e) {
        debugger;
        var $select = $(this);
        if (e.val == undefined) {
            e.preventDefault();
            var childIds = $.map(e.choice.children, function (child) {
                return child.id;
            });
            $select.select2('val', $select.select2('val').concat(childIds));
            $select.select2('close');
       }
    });

If you click on optgroup then It will select all the options under the optgroup.

Mcgurn answered 27/2, 2017 at 11:23 Comment(0)
D
2

Well, I came across this issue and I found that every time the select2 (Select2 4.0.5) opens it adds a span element before the closing body element. In addition, inside the span element adds a ul with the id: select2-X-results, where X is the select2 id. So I found the following workaround (jsfiddle):

var countries = [{
  "id": 1,
  "text": "Greece",
  "children": [{
    "id": "Athens",
    "text": "Athens"
  }, {
    "id": "Thessalonica",
    "text": "Thessalonica"
  }]
}, {
  "id": 2,
  "text": "Italy",
  "children": [{
    "id": "Milan",
    "text": "Milan"
  }, {
    "id": "Rome",
    "text": "Rome"
  }]
}];

$('#selectcountry').select2({
  placeholder: "Please select cities",
  allowClear: true,
  width: '100%',
  data: countries
});

$('#selectcountry').on('select2:open', function(e) {

  $('#select2-selectcountry-results').on('click', function(event) {

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

    var groupchildren = [];

    for (var i = 0; i < countries.length; i++) {


      if (selectedOptionGroup.toString() === countries[i].text.toString()) {

        for (var j = 0; j < countries[i].children.length; j++) {

          groupchildren.push(countries[i].children[j].id);

        }

      }


    }


    var options = [];

    options = $('#selectcountry').val();

    if (options === null || options === '') {

      options = [];

    }

    for (var i = 0; i < groupchildren.length; i++) {

      var count = 0;

      for (var j = 0; j < options.length; j++) {

        if (options[j].toString() === groupchildren[i].toString()) {

          count++;
          break;

        }

      }

      if (count === 0) {
        options.push(groupchildren[i].toString());
      }
    }

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

  });
});
li.select2-results__option strong.select2-results__group:hover {
  background-color: #ddd;
  cursor: pointer;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/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://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.full.min.js"></script>


<h1>Selectable optgroup using select2</h1>
<select id="selectcountry" name="country[]" class="form-control" multiple style="width: 100%"></select>
Dint answered 15/1, 2018 at 9:19 Comment(0)
O
1

In Select2 v4, I found that John S's answer won't work (see here). I am loading my data as an array using AJAX and created a workaround:

$(document).on("click", ".select2-results__group", function(){
    var input = $(this);
    var location = input.html();
    // Find the items with this location
    var options = $('#select2 option');
    $.each(options, function(key, value){
        var name = $(value).html();
        // The option contains the location, so mark it as selected
        if(name.indexOf(location) >= 0){
            $(value).prop("selected","selected");
        }
    });
    $("#select2").trigger("change");
});

I am grouping my items by location, and each option contains the location name somewhere in it's html. Any time an optgroup header is clicked, I get it's location (the name displayed in the dropdown). Then I look through all options in the #select2 table and find which ones contain that location in its html.

I know this is a hacky workaround, but hopefully it helps/points in the right direction.

Oxfordshire answered 22/7, 2015 at 15:12 Comment(0)
M
0

John S, provided example works really well (for V3) for most of the cases. However it has one bug:

Assuming the selection list is long enough to have scroll. When you select any item in any group that is only available after you scroll down - you can't select the next item after the one you selected from this group anymore. It is because ensureHighlightVisible method of select2 starts misbehaving because the selectors it is using made using assumptions that group is always 'unselectable'. So the scroll will jump every time you try to select the item.

So unfortunately although this solution looks really good - I dropped it and re-implemented it without using group id's:

$selectEl..on("select2-open", function(event) {
          $(event.target).data("select2").dropdown.on("click", "li.select2-result-unselectable", selectGroup);
          $(event.target).data("select2").dropdown.on("mousemove-filtered", "li.select2-result-unselectable", highlight);
        }).on("select2-close", function(event) {
          $(event.target).data("select2").dropdown.off("click", "li.select2-result-unselectable", selectGroup);
          $(event.target).data("select2").dropdown.off("mousemove-filtered", "li.select2-result-unselectable", highlight);
        });

and

  // selection of the group.
  function selectGroup(e) {
    var $li = $(this);
    e.preventDefault();
    $select.select2('data', $select.select2('data').concat($li.data("select2Data").children));
    $select.select2('close');
    _this.$field.trigger('change');
  }

  // highlight of the group.
  function highlight(e) {
    if ($(e.target).hasClass("select2-result-unselectable") || $(e.target.parentNode).hasClass('select2-result-unselectable')) {
      e.preventDefault();
      e.stopPropagation();
      $select.data("select2").dropdown.find(".select2-highlighted").removeClass("select2-highlighted");
      $(this).addClass("select2-highlighted");
    }
  }
Mystic answered 3/3, 2016 at 18:19 Comment(0)
E
0

You can use templateResult to add specific behaviour for groups and children.

const something = {
  "results": [
    {
      "text": "Group 1",
      "children": [
        {
          "id": 1,
          "text": "Option 1.1",
        }, {
          "id": 2,
          "text": "Option 1.2"
        }
      ]
    }, {
      "text": "Group 2",
      "children": [
        {
          "id": 3,
          "text": "Option 2.1"
        }, {
          "id": 4,
          "text": "Option 2.2"
        }
      ]
    }
  ],
  "pagination": {
    "more": true
  }
};

function formatState(state) {
  if (!state.id) {
    var $state = $('<span>' + state.text + '</span>');
    $state.on('click', function() {
      let currentIds = $('#opt-select').select2('data').map(el => el.id);
      let newIds = state.children.map(el => el.id);
      $('#opt-select').val(currentIds.concat(newIds));
      $('#opt-select').trigger('change');
      $('#opt-select').select2('close');
    });
    return $state;
  }
  return state.text;
}

$(document).ready(function () {
  $("#opt-select").select2({
    data: something.results,
    templateResult: formatState,
  });
});
<select id="opt-select" style="width: 300px;" name="states[]" multiple="multiple"> </select>
Electron answered 22/3, 2023 at 16:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.