Bootstrap-select: Ability to select/deselect all optgroup items

Created on 16 Sep 2014  路  33Comments  路  Source: snapappointments/bootstrap-select

I need to clickable optgroup option, when we click on the optgroup label to select / deselect what are all items having inside the groups are selected /deselected. This is very urgent please...

enhancement

Most helpful comment

I am also interested in this feature for bootstrap-4.0 i.e. bootstrap-select-v1.13.0-beta version.

All 33 comments

Hello Silvio,

Is there any chance that we get some help about this? Thanks.

Any support regarding this?

Looking forward to this functionality too.

Also, duplicate of #680, in which @t0xicCode tagged this functionality for the 1.7.0 milestone.

It would be nice and seems logical.

Looking forward for that.

For anybody who truly needs this right now, replace these lines here: https://github.com/silviomoreto/bootstrap-select/blob/master/js/bootstrap-select.js#L957-L965 with

      this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) {
        e.preventDefault();
        e.stopPropagation();

          if (that.multiple && $(e.currentTarget).hasClass('dropdown-header')) {
              var $options = that.$element.find('option'),
                  $optgroup = $(e.currentTarget),
                  optgroupID = $optgroup.data('optgroup'),
                  $optgroupLis = that.$lis.filter('[data-optgroup="' + optgroupID + '"]').not('.divider, .dropdown-header, .disabled, .hidden'),
                  selectAll = $optgroupLis.filter('.selected').length !== $optgroupLis.length;

              if (!$options.eq($optgroupLis.data('originalIndex')).parent().data('maxOptions')) {
                  $optgroupLis.each(function(index) {
                      var $this = $(this);
                      $options.eq($this.data('originalIndex')).prop('selected', selectAll);
                  });

                  $optgroupLis.toggleClass('selected', selectAll);

                  that.render(false);
              }
          }

        if (that.options.liveSearch) {
          that.$searchbox.focus();
        } else {
          that.$button.focus();
        }
      });

I was going to implement this, but thought it would be best to first discuss how best to handle optgroups with maxOptions set. With this code, if an optgroup has maxOptions set, clicking the optgroup label doesn't do anything. Anybody have any ideas for a different way to handle this?

I agree with your suggestion. I don't see a better way to deal with the limited number of options. Maybe it's also a good idea to add some kind of notification in that case, so that users are also aware of it?

@caseyjhol :+1: thanks a lot :-)

Hi @caseyjhol,
Are there a new solution for this? I know that now we have a new version
Selectpicker.VERSION = '1.7.2';
Or someone else have another solution for this?
thanks a lot:)

+1 for this

@caseyjhol, can you reconfirm the lines to be replaced for the actual version? I really would like to try this feature. And for the conflict, maybe the best option should be document it. There some of this kinda of stuff on Bootstrap and they just document it. Well this being said there is not a practical solution that don't create a conflict with another feature. Thanks.

+1
In regard to how to deal with maxOptions... just check the first maxOptions items.

@caseyjhol Hey, I've got the majority of this working on a fork at the moment. The only thing I have left to do is fix some CSS a bit so it looks clickable and add to the documentation.


Brief explanation of maxOptions problem

When I first read this, I didn't fully understand the issue of maxOptions until I started building it and ran into the same issue. So this is a brief explanation to catch up anyone else who needs it:

  • If you use the data-max-options="3" attribute to the <optgroup> it will only let you select three options, even if you have more.
  • If you add the ability to select all options under an <optgroup> that has a max options that is less than the total options, which options do you choose to select?

The current functionality I have working is the follows:

Selects the first few if maxOptions is set

I incorporated the idea from @weatherbell to select the first maxOptions. I like this idea a lot because if the user is confused, the first thing the user will try to do is select the next few which will then force the warning: Group limit reached ({n} item max). So any confusion is self-fixing.

It's a data attribute

In order for the programmer to add the "select header" functionality, they must add the data-select-all-headers="true" attribute to their <select> like so:

<select id="selectHeader" class="selectpicker form-control" multiple data-select-all-headers="true" title="Selecting a header will select all it can.">
<!-- Options with groups -->
</select>

Requiring this to be opt-in solves a number of issues. It solves the point made by @Khrysller that we should document it. By requiring it to be opt-in, we force the programmer to read the documentation explaining the oddities caused by maxOptions. It also puts both the burden and freedom of usability on the programmer.


I'm going to continue working on the documentation and css unless anyone has any disagreements with this plan.

Note that the existing Select All ability currently ignores the maxOptions. In practice, it doesn't really make sense to have a "select all" while at the same time having a limit on the number of options you can select. I highly doubt anyone would be trying to use both these features at once, so going to extra lengths to make them somehow work together may not be particularly helpful.

Personally I would say the sensible thing to do is either ignore the maxOptions (as it currently does for the main select all) or force the select all option off if maxOptions is defined.

I had problems with the above code and the function "change". Solved by adding "onselect" directly on the tag <select>:

<select class="form-control selectpicker" name="mynames[]"
        multiple="multiple" onchange="changeSelectPicker(this)">
    <option value="1">Count 1</option>
    <option value="2">Count 2</option>
    <option value="3">Count 3</option>
    <option value="4">Count 4</option>
</select>
// PROBLEM
$('.selectpicker').on("changed.bs.select", function (e, clickedIndex) {
    console.log(this); // NO RETURN
});
// PROBLEM
$('.selectpicker').on("change", function (this) {
    console.log(this); // NO RETURN
});
// SOLVED
function changeSelectPicker(that) {
    var valuesArr = $(that).val();
    console.log(valuesArr); // return selected values ["1", "3", "4"]
}

@Flaque Have you done anything else with this? Can you post what you have?

Sorry, @KrunchMuffin. I haven't done anything more than the original PR. I guess I missed this. I might be able to look into it, but no promises.

Ah, sorry, didn't see the PR. What I am really after is the ability to use Select All while minding the maxOptions. I may be able to do something with your code.

Any plans on integrating these changes? Is there a workaround available to gain this functionality or at least trap the click event on the optgroup?

For actual version this works for me, simply add this:

$("li.dropdown-header").click(function(e){
        e.preventDefault();
        e.stopPropagation();

          if (that.multiple && $(e.currentTarget).hasClass('dropdown-header')) {
              var $options = that.$element.find('option'),
                  $optgroup = $(e.currentTarget),
                  optgroupID = $optgroup.data('optgroup'),
                  $optgroupLis = that.$lis.filter('[data-optgroup="' + optgroupID + '"]').not('.divider, .dropdown-header, .disabled, .hidden'),
                  selectAll = $optgroupLis.filter('.selected').length !== $optgroupLis.length;

              if (!$options.eq($optgroupLis.data('originalIndex')).parent().data('maxOptions')) {
                  $optgroupLis.each(function(index) {
                      var $this = $(this);
                      $options.eq($this.data('originalIndex')).prop('selected', selectAll);
                  });

                  $optgroupLis.toggleClass('selected', selectAll);

                  that.render(false);
              }
          }

        if (that.options.liveSearch) {
          that.$searchbox.focus();
        } else {
          that.$button.focus();
        }
      });

in line 1358 (https://github.com/silviomoreto/bootstrap-select/blob/master/js/bootstrap-select.js#L1358)

@caseyjhol Are there any solutions to achieve the same in the version compatible with bootstrap-4.0 i.e. bootstrap-select-v1.13.0-beta version?

I am also interested in this feature for bootstrap-4.0 i.e. bootstrap-select-v1.13.0-beta version.

jerearaujo03 thanks for sharing - your code works fine if you have ONE selectlist on your page. If you have more, this works for me:

this.$menuInner.find( "li.dropdown-header" ).each(function(index) {
    $(this).on('click',function(e) {
        e.preventDefault();
        e.stopPropagation();

        if (that.multiple && $(e.currentTarget).hasClass('dropdown-header')) {
            var $options = that.$element.find('option'),
            $optgroup = $(e.currentTarget),
            optgroupID = $optgroup.data('optgroup'),
            $optgroupLis = that.$lis.filter('[data-optgroup="' + optgroupID + '"]').not('.divider, .dropdown-header, .disabled, .hidden'),
            selectAll = $optgroupLis.filter('.selected').length !== $optgroupLis.length;

            if (!$options.eq($optgroupLis.data('originalIndex')).parent().data('maxOptions')) {
                $optgroupLis.each(function(index) {
                    var $this = $(this);
                    $options.eq($this.data('originalIndex')).prop('selected', selectAll);
                });
                $optgroupLis.toggleClass('selected', selectAll);
                that.render(false);
            }
        }
        if (that.options.liveSearch) {
            that.$searchbox.focus();
        } else {
            that.$button.focus();
        }
    });
});

Great!

It's not work for me ...

The previous posted solution does not work on the current version which has a different code base.
Can we have a updated solution or a built in feature for toggling select all items under an option group please?
Will pay/donate for this!

I did this, just add this bottom of the page:

$(".dropdown-header").each(function (index, header) {

        var header = $(header);

        header.click(function () {
            var dataoptgroup = $(this).attr("data-optgroup");

            var group_lis = $('li[data-optgroup=' + dataoptgroup + ']').filter('li[data-original-index]');

            group_lis.each(function (index, option) {
                $(option).find("a").click()
            });
        });
    });

I did this, just add this bottom of the page:

$(".dropdown-header").each(function (index, header) {

        var header = $(header);

        header.click(function () {
            var dataoptgroup = $(this).attr("data-optgroup");

            var group_lis = $('li[data-optgroup=' + dataoptgroup + ']').filter('li[data-original-index]');

            group_lis.each(function (index, option) {
                $(option).find("a").click()
            });
        });
    });

v1.13.12 works for me:

add this in this.$menuInner.on('click', '.divider, .dropdown-header', function (e)
line 2521:

if(that.multiple){ var group_id= $(this).attr("class").split(' ')[1]; $("li."+group_id+' a').trigger('click')};

Hi, i really need that too.
There is solutions but no PR ?

I made this workaround for 1.13.12.

Just call this function after initializing the select and pass it the jq selector

function enableSelectOptgroup(select){
    const $select = $(select);
    $select.on("show.bs.select rendered.bs.select", (e: JQueryEventObject) => {
        const $optgroup = $(e.target)
            .closest(".bootstrap-select")
            .find("li.dropdown-header");

        $optgroup.not(".e_init").on("click", (event: JQueryEventObject) => {
            event.stopPropagation();
            const label = $(event.currentTarget).children().text();
            const $optgroup_select = $select.find(`optgroup[label="${label}"]`);
            const to_select = $optgroup_select.children("option").length !== $optgroup_select.children("option:selected").length;
            $optgroup_select.children("option").prop("selected", to_select);
            $select.selectpicker("refresh");
            $select.trigger("change");
        });

        $optgroup.addClass("e_init");
    });
}

Here is my solution, tested on boostrap-4.5 and bootstrap-select-1.13.18

    $('#myPicker').selectpicker().on('loaded.bs.select', enableBoostrapSelectOptgroup);

    function enableBoostrapSelectOptgroup() {

        let that = $(this).data('selectpicker'),
            inner = that.$menu.children('.inner');

        // remove default event
        inner.off('click','.divider, .dropdown-header');
        // add new event
        inner.on('click','.divider, .dropdown-header', function (e){
            // original functionality
            e.preventDefault();
            e.stopPropagation();
            if (that.options.liveSearch) {
                that.$searchbox.trigger('focus');
            } else {
                that.$button.trigger('focus');
            }

            // extended functionality
            let position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0,
                clickedData = that.selectpicker.current.data[$(this).index() + position0];

            // copied parts from changeAll function
            let selected = null;
            for (let i = 0, data = that.selectpicker.current.data, len = data.length; i < len; i++) {
                let element = data[i];
                if(element.type === 'option' && element.optID === clickedData.optID) {
                    if(selected === null) {
                        selected = !element.selected;
                    }
                    element.option.selected = selected;
                }
            }
            that.setOptionStatus();
            that.$element.triggerNative('change');
        });
    }

I used parts of original code so it should work for anyone.
Function could be injected in bootstrap-select.js in this.$menuInner.on('click', '.divider, .dropdown-header', function (e) {...} or authors could add it to their next build.

Could anyone create a fiddle for it because I have tried every solution here and it doesn't work. So I might be placing the function on a wrong place or I am using not compatible version. Thanks if anyone could suggest I am using bootstrap 4 and bootstrap-select v1.13.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

M1chae1 picture M1chae1  路  3Comments

anton164 picture anton164  路  4Comments

DjSunrise picture DjSunrise  路  3Comments

edwolfe807 picture edwolfe807  路  3Comments

qiyuan4f picture qiyuan4f  路  4Comments