Brian Leishman
Brian Leishman

Reputation: 8555

Hide Options On Search with jQuery Select2

I'm using the Select2 plugin, and I can't for the life of me figure this out.

Basically what I am doing is allowing users to pick 5 colors from a list of colors and the option "Other". So the 10 or so colors are displayed with the option to chose "Other" at the bottom. These "Other" options have a color picker bound to them when chosen so the user can pick their other color.

Now the only way I could allow them to do this was to create 5 options that all have the name "Other" so the user can chose other 5 times if they please. My problem is when the user begins to type a query in the search box, I have no way of properly hiding the "Other" options that aren't the first one being displayed.

I'm doing this with the normal results being open as such

$('.multi-select').select2('misc options').on("select2:open", function () {
    $('.select2-results__option[aria-selected="false"]:contains("Other"):not(:eq(0))').hide();
    $('.select2-results__option[aria-selected="false"]:contains("Other"):eq(0)').show();
});

But, when I bind the same function to the change of the search input element like this

$('.select2-search__field').on('keyup', function () {
    $('.select2-results__option[aria-selected="false"]:contains("Other"):not(:eq(0))').css({
           'display': 'none',
           'opacity': 0
     });
     $('.select2-results__option[aria-selected="false"]:contains("Other"):eq(0)').css({
           'display': 'block',
           'opacity': 1
     });
});

I get a crazy flashing of the elements, additional to the top "Other" option floating very high above the select object if Select2 decides that it is appropriate for the results to appear above due to window space restrictions.

The flashing can be attributed to the fact that the plugins binds the show event on 'keydown' (and 'keypress') of the object, so my binding to the same trigger gets overwritten, making me bind to 'keyup' which causes the boxes to show in between the key press and release.

I'm not above modifying the plugin to do this, however I can't figure out what I'd need to edit inside of the plugin to get this to work.


Misc. things I've tried include setting a css selector to always make the first box per Select2 object containing "Other", but there's no css selector for something like ":contain".

Applying a class to the the <li></li> that Select2 creates that specify if it is an "Other" option, but this controlled by the plugin, so I don't have control over that.

Upvotes: 2

Views: 5753

Answers (2)

Kevin Brown-Silva
Kevin Brown-Silva

Reputation: 41671

Instead of modifying the "Other" result in order to achieve what you are looking for, I would recommend using a combination of the select2:select event and maximumSelectionLength options.

So you would start off with markup that includes the default list of colors and one "Other" option.

<select multiple="multiple">
  <option value="red">Red</option>
  <option value="blue">Blue</option>
  <option value="green">Green</option>
  <option value="yellow">Yellow</option>
  <option value="custom">Other</option>
</select>

Note that I've set the value for the "Other" option to custom, you can pick whatever you wish as long as it wouldn't conflict with a color. This is just going to be the "flag" option that will be used to determine when to display the color picker.

In order to eventually limit the number of selections that can be made to just five colors, you can initialize Select2 with the maximumSelectionLength option set to 5. This will tell Select2 to only allow five selections, and you can find an example in the documentation.

var $element = $("select").select2({
  maximumSelectionSize: 5
});

So now that you have the number of selections limited, we can move on to implementing the color picker through the "Other" option. We can detect when the "Other" option is selected by listening to the select2:select option, which is triggered whenever an option is selected.

$element.on("select2:select", function (evt) {
  // handle the "Other" option
});

From there you need to specifically detect the "Other" option, so we can check the data object that is passed in through the select2:select event for an id of custom, which is what we set earlier. As you want to keep the option of selecting multiple custom colors open, we are going to immediately unselect the "Other" option when it is selected.

function (evt) {
  // check for the custom id
  if (evt.params.data.id === 'custom') {
    // unselect the element, so other custom colors can be picked
    evt.params.data.element.selected = false;

    // update the selection
    $(this).trigger('change');
  }
}

From there, you need to implement the part where the user can pick a color. For the purposes of this, I just used a prompt that asks for the color. Because prompt is synchronous, we can just wait for the selection to be given. But your color picker is most likely asynchronous, so you may have to place the rest in a different event handler.

// prompt the user for a color somehow
var color = prompt("Pick a color, any color");

Once the color has been selected, you are going to need to create a custom <option selected> for it. This is so the value will be sent to the server, and so Select2 can know what color has been selected.

// create a new option
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement/Option
var option = new Option(color, color, null, true);

And all you need to do is add it to the original <select> and trigger the change event on it so Select2 (and other components) will know that the value changed.

// add the option to the original select, before the "Other" option
$(option).insertBefore(evt.params.data.element);

// update the selection
$element.trigger('change');

Now Select2 will display the new custom color alongside of any other selections that have been made. Users will also be able to select additional colors by selecting the "Other" option multiple times.

So, putting it all together gives you the following code

var $element = $("select").select2({
  maximumSelectionLength: 5
});

$element.on('select2:select', function (evt) {
  // check for the custom id
  if (evt.params.data.id === 'custom') {
    // unselect the element, so other custom colors can be picked
    evt.params.data.element.selected = false;

    // update the selection
    $(this).trigger('change');

    // prompt the user for a color somehow
    var color = prompt("Pick a color, any color");

    // create a new option
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement/Option
    var option = new Option(color, color, null, true);

    // add the option to the original select, before the "Other" option
    $(option).insertBefore(evt.params.data.element);

    // update the selection
    $element.trigger('change');
  }
});

Which you can test live at the following jsbin: http://jsbin.com/beluximesi/1/edit?html,js,output

Upvotes: 2

Brian Leishman
Brian Leishman

Reputation: 8555

I figured it out. Since the value of the fields were being used as the ID for the field in the back end, I was able to append the ID with "-other-color" so I had a way of selecting it in CSS which allowed to to surpass the flickering.

.select2-results__options li[id*="-other-color"][aria-selected="false"] ~ li[id*="-other-color"][aria-selected="false"]{
    display:none!important;
}

Since there are more children in the list than just other color options, you need to use the tilde selector to select everything after the selector based on the attributes being selected.

Upvotes: 0

Related Questions