Select2 open dropdown on focus
Asked Answered
D

17

49

I have a form with multiple text inputs and some select2 elements. Using the keyboard to tab between fields works fine - the Select2 element behaves like a form element and receives focus when tabbing. I was wondering if it is possible to open the dropdown when the Select2 element gets focus.

Here's what I've tried so far:

$("#myid").select2().on('select2-focus', function(){
     $(this).select2('open');
});

But using this code makes the dropdown to open again after a selection is made.

Delenadeleon answered 8/1, 2014 at 7:29 Comment(0)
Q
100

Working Code for v4.0+ *(including 4.0.7)

The following code will open the menu on the initial focus, but won't get stuck in an infinite loop when the selection re-focuses after the menu closes.

// on first focus (bubbles up to document), open the menu
$(document).on('focus', '.select2-selection.select2-selection--single', function (e) {
  $(this).closest(".select2-container").siblings('select:enabled').select2('open');
});

// steal focus during close - only capture once and stop propogation
$('select.select2').on('select2:closing', function (e) {
  $(e.target).data("select2").$selection.one('focus focusin', function (e) {
    e.stopPropagation();
  });
});

Explanation

Prevent Infinite Focus Loop

Note: The focus event is fired twice

  1. Once when tabbing into the field
  2. Again when tabbing with an open dropdown to restore focus

focus menu states

We can prevent an infinite loop by looking for differences between the types of focus events. Since we only want to open the menu on the initial focus to the control, we have to somehow distinguish between the following raised events:

event timeline

Doing so it a cross browser friendly way is hard, because browsers send different information along with different events and also Select2 has had many minor changes to their internal firing of events, which interrupt previous flows.

One way that seems to work is to attach an event handler during the closing event for the menu and use it to capture the impending focus event and prevent it from bubbling up the DOM. Then, using a delegated listener, we'll call the actual focus -> open code only when the focus event bubbles all the way up to the document

Prevent Opening Disabled Selects

As noted in this github issue #4025 - Dropdown does not open on tab focus, we should check to make sure we only call 'open' on :enabled select elements like this:

$(this).siblings('select:enabled').select2('open');

Select2 DOM traversal

We have to traverse the DOM a little bit, so here's a map of the HTML structure generated by Select2

Select2 DOM

Source Code on GitHub

Here are some of the relevant code sections in play:

.on('mousedown' ... .trigger('toggle')
.on('toggle' ... .toggleDropdown()
.toggleDropdown ... .open()
.on('focus' ... .trigger('focus'
.on('close' ... $selection.focus()

It used to be the case that opening select2 fired twice, but it was fixed in Issue #3503 and that should prevent some jank

PR #5357 appears to be what broke the previous focus code that was working in 4.05

Working Demo in jsFiddle & Stack Snippets:

$('.select2').select2({});

// on first focus (bubbles up to document), open the menu
$(document).on('focus', '.select2-selection.select2-selection--single', function (e) {
  $(this).closest(".select2-container").siblings('select:enabled').select2('open');
});

// steal focus during close - only capture once and stop propogation
$('select.select2').on('select2:closing', function (e) {
  $(e.target).data("select2").$selection.one('focus focusin', function (e) {
    e.stopPropagation();
  });
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/css/select2.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.js"></script>

<select class="select2" style="width:200px" >
  <option value="1">Apple</option>
  <option value="2">Banana</option>
  <option value="3">Carrot</option>
  <option value="4">Donut</option>
</select>

Tested on Chrome, FF, Edge, IE11

Quianaquibble answered 13/3, 2018 at 16:38 Comment(12)
Thank you for this. Works for me in Chrome, FF, and Safari on mac, in quick testing.Muscle
Actually, this has caused a bug for me. Or, how my code interacts with this causes a bug. If I click out of select2, into a text input field, select2 closes and then re-opens. Chrome. Select2 v4.0.7.Muscle
If I adjust your fiddle to using v4.0.7, it does not work anymore. jsfiddle.net/nwLbqfa4Muscle
But! Reverting to Select2 v4.0.5 and Kyle's code still works. Beware v4.0.7, all ye who want to open select2 on focus! It will open, but never close.... :)Muscle
Confirmed -- does not work on 4.0.7 (the latest version at the time of this writing).Tripartite
@MattOlson, figured out a solution for 4.0.7 that works across all 4+ versionsQuianaquibble
@mungojerie, thanks for the diagnostics. Should have a solution in place. Check it out and let me know.Quianaquibble
This seems to break tab-index when trying to tab through form to next field after select2 input.Jobe
@ouija, can you add a demo with this happening?Quianaquibble
Works awesome in 4.0.13. Exactly what I saw looking for to make my app tab friendly after adding Select2. Thank you so much KyleMit. If I had any complaint it is that I cant select/highlight my desired item and then hit only tab. I have to hit Enter to select the item from the dropdown.Consul
How about on multiple? It doesn't work on multiple.Imbecile
I'm wondering, instead of just stealing focus during close, can we forward the focus to the next element in the form?Tai
T
29

For Version 3.5.4 (Aug 30, 2015 and earlier)

The current answer is only applicable to versions 3.5.4 and before, where select2 fired blur and focus events (select2-focus & select2-blur). It attaches a one-time use handler using $.one to catch the initial focus, and then reattaches it during blur for subsequent uses.

$('.select2').select2({})
  .one('select2-focus', OpenSelect2)
  .on("select2-blur", function (e) {
    $(this).one('select2-focus', OpenSelect2)
  })

function OpenSelect2() {
  var $select2 = $(this).data('select2');
  setTimeout(function() {
    if (!$select2.opened()) { $select2.open(); }
  }, 0);  
}

I tried both of @irvin-dominin-aka-edward's answers, but also ran into both problems (having to click the dropdown twice, and that Firefox throws 'event is not defined').

I did find a solution that seems to solve the two problems and haven't run into other issue yet. This is based on @irvin-dominin-aka-edward's answers by modifying the select2Focus function so that instead of executing the rest of the code right away, wrap it in setTimeout.

Demo in jsFiddle & Stack Snippets

$('.select2').select2({})
  .one('select2-focus', OpenSelect2)
  .on("select2-blur", function (e) {
    $(this).one('select2-focus', OpenSelect2)
  })

function OpenSelect2() {
  var $select2 = $(this).data('select2');
  setTimeout(function() {
    if (!$select2.opened()) { $select2.open(); }
  }, 0);  
}
body {
  margin: 2em;
}

.form-control {
  width: 200px;  
  margin-bottom: 1em;
  padding: 5px;
  display: flex;
  flex-direction: column;
}

select {
  border: 1px solid #aaa;
  border-radius: 4px;
  height: 28px;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.js"></script>

  
  <div class="form-control">
    <label for="foods1" >Normal</label>
    <select id="foods1" >
      <option value=""></option>
      <option value="1">Apple</option>
      <option value="2">Banana</option>
      <option value="3">Carrot</option>
      <option value="4">Donut</option>
    </select>
</div>

<div class="form-control">
  <label for="foods2" >Select2</label>
  <select id="foods2" class="select2" >
    <option value=""></option>
    <option value="1">Apple</option>
    <option value="2">Banana</option>
      <option value="3">Carrot</option>
      <option value="4">Donut</option>
    </select>
  </div>
Talkingto answered 5/3, 2014 at 21:51 Comment(3)
for the new version 4.0.0 you need to update the event and their targets.Banta
demo is not working. Tried to update it to v4.0 and still not working: jsfiddle.net/brunodd/c7kvsu3o/5Barty
Select2 doesn't trigger the focus event on the original element when the dropdown is focused. github.com/select2/select2/issues/4025Archival
V
26

Something easy that would work on all select2 instances on the page.

$(document).on('focus', '.select2', function() {
    $(this).siblings('select').select2('open');
});

UPDATE: The above code doesn't seem to work properly on IE11/Select2 4.0.3

PS: also added filter to select only single select fields. Select with multiple attribute doesn't need it and would probably break if applied.

var select2_open;
// open select2 dropdown on focus
$(document).on('focus', '.select2-selection--single', function(e) {
    select2_open = $(this).parent().parent().siblings('select');
    select2_open.select2('open');
});

// fix for ie11
if (/rv:11.0/i.test(navigator.userAgent)) {
    $(document).on('blur', '.select2-search__field', function (e) {
        select2_open.select2('close');
    });
}
Valle answered 31/7, 2015 at 19:17 Comment(2)
This worked great, though it looks like a recent IE11 update seems to have broken it :)Dilative
Only one that worked for me. I had to upgrade my jquery and my select2 cdns to 3.6 and 4.1.0 respectively.Ingathering
C
17

Probably after the selection is made a select2-focus event is triggered.

The only way I found is a combination of select2-focus and select2-blur event and the jQuery one event handler.

So the first time the element get the focus, the select2 is opened for one time (because of one), when the element is blurred the one event handler is attached again and so on.

Code:

$('#test').select2({
    data: [{
        id: 0,
        text: "enhancement"
    }, {
        id: 1,
        text: "bug"
    }, {
        id: 2,
        text: "duplicate"
    }, {
        id: 3,
        text: "invalid"
    }, {
        id: 4,
        text: "wontfix"
    }],
    width: "300px"
}).one('select2-focus', select2Focus).on("select2-blur", function () {
    $(this).one('select2-focus', select2Focus)
})

function select2Focus() {
    $(this).select2('open');
}

Demo: http://jsfiddle.net/IrvinDominin/fnjNb/

UPDATE

To let the mouse click work you must check the event that fires the handler, it must fire the open method only if the event is focus

Code:

function select2Focus() {
    if (/^focus/.test(event.type)) {
        $(this).select2('open');
    }
}

Demo: http://jsfiddle.net/IrvinDominin/fnjNb/4/

UPDATE FOR SELECT2 V 4.0

select2 v 4.0 has changed its API's and dropped the custom events (see https://github.com/select2/select2/issues/1908). So it's necessary change the way to detect the focus on it.

Code:

$('.js-select').select2({
    placeholder: "Select",
    width: "100%"
})

$('.js-select').next('.select2').find('.select2-selection').one('focus', select2Focus).on('blur', function () {
    $(this).one('focus', select2Focus)
})

function select2Focus() {
    $(this).closest('.select2').prev('select').select2('open');
}

Demo: http://jsfiddle.net/IrvinDominin/xfmgte70/

Cerebrate answered 8/1, 2014 at 8:3 Comment(10)
Thanks for your help.This solution works fine for keyboard navigation. But I found a bug: on mouse click on select2 element, it needs two clicks to open the dropdown: the first click will make focus on element and the second will actually open the dropdown.Delenadeleon
I've tested in chrome: works OK. In mozilla I get the following error 'event is not defined'.Delenadeleon
I've searched a little about firefox events, it seems to be a tricky problem. Anyway, many thanks for your help.Delenadeleon
@Delenadeleon glad to help you, but I want to find a way to solve this (for the checkmark too :-)Cerebrate
Example not working. Tried to update to v4.0 still not working: jsfiddle.net/brunodd/fnjNb/71Barty
@Barty select2 4.0 have dropped events support: github.com/select2/select2/issues/1908, I'm checking for a workaroundCerebrate
Thanks @IrvinDominin. Here is what I am using: jsfiddle.net/brunodd/c7kvsu3o/9Barty
However for some reason is not working on IE11 =( Guess this might be something to do with select2 itself github.com/select2/select2/issues/3300Barty
I gave the latest fiddle a shot in the current version of Chrome and the select menu did not open on focus.Doughty
So I've found that the rule has to be to open on focus if a focus or a blur had not happen recently. I have a little flow control library that makes it easier to code logic like that with coroutines. Here's a demo jsfiddle.net/xk5Lyfn0/1Beaudette
A
11

a bit late... but to share my code using select2 4.0.0

$("#my_id").select2();
$("#my_id").next(".select2").find(".select2-selection").focus(function() {
    $("#my_id").select2("open");
});
Alitaalitha answered 22/6, 2015 at 22:29 Comment(3)
This solution is not working in IE =/ It opens, but it never closes (not even clicking outside, tabbing). Tested in IE 11Tideland
This works when you're selecting an individual element by ID, but if you want to target more than one select2 field (and not open just the first field every time), this works: $('.my-class').next('.select2').find('.select2-selection').focus(function (e) { $(this).closest('.select2').prev('select..my-class').select2('open'); }); (Sorry, apparently can't have line breaks in comments.)Myeshamyhre
Thanks man, between the old and the new version all the info is messed up. Works for me, just .select2("open") does the trick.Barlow
E
7

Here is an alternate solution for version 4.x of Select2. You can use listeners to catch the focus event and then open the select.

$('#test').select2({
    // Initialisation here
}).data('select2').listeners['*'].push(function(name, target) { 
    if(name == 'focus') {
        $(this.$element).select2("open");
    }
});

Find the working example here based the exampel created by @tonywchen

Endsley answered 20/6, 2016 at 15:26 Comment(0)
J
5

The problem is, that the internal focus event is not transformed to jQuery event, so I've modified the plugin and added the focus event to the EventRelay on line 2063 of Select2 4.0.3:

EventRelay.prototype.bind = function (decorated, container, $container) {
    var self = this;
    var relayEvents = [
      'open', 'opening',
      'close', 'closing',
      'select', 'selecting',
      'unselect', 'unselecting',
      'focus'
    ]};

Then it is enough to open the select2 when the focus occurs:

$('#select2').on('select2:focus', function(evt){
    $(this).select2('open');
});

Works well on Chrome 54, IE 11, FF 49, Opera 40

Jamille answered 14/10, 2016 at 21:57 Comment(0)
E
5

KyleMit's answer worked for me (thank you!), but I noticed that with select2 elements that allow for searching, trying to tab to the next element wouldn't work (tab order was effectively lost), so I added code to set focus back to the main select2 element when the dropdown is closing:

$(document).on('focus', '.select2', function (e) {
    if (e.originalEvent) {
        var s2element = $(this).siblings('select');
        s2element.select2('open');

        // Set focus back to select2 element on closing.
        s2element.on('select2:closing', function (e) {
            s2element.select2('focus');
        });
    }
});
Emery answered 25/5, 2018 at 18:53 Comment(1)
Hey @Douglas, I'm having trouble trying to reproduce the issue you're addressing here, but 4 upvotes says other people were likely encountering it to. When do you see tab order getting lost?Quianaquibble
L
3

I tried a number of these and finally came up with the following that works for me with Select2 4.0.1. element is the <select> element.

$.data(element).select2.on("focus", function (e) {
    $(element).select2("open");
});
Lammers answered 27/1, 2016 at 15:29 Comment(0)
H
3

For me using Select2.full.js Version 4.0.3 none of the above solutions was working the way it should be. So I wrote a combination of the solutions above. First of all I modified Select2.full.js to transfer the internal focus and blur events to jquery events as "Thomas Molnar" did in his answer.

EventRelay.prototype.bind = function (decorated, container, $container) {
    var self = this;
    var relayEvents = [
      'open', 'opening',
      'close', 'closing',
      'select', 'selecting',
      'unselect', 'unselecting',
      'focus', 'blur'
    ];

And then I added the following code to handle focus and blur and focussing the next element

$("#myId").select2(   ...   ).one("select2:focus", select2Focus).on("select2:blur", function ()
{
    var select2 = $(this).data('select2');
    if (select2.isOpen() == false)
    {
        $(this).one("select2:focus", select2Focus);
    }
}).on("select2:close", function ()
{
    setTimeout(function ()
    {
        // Find the next element and set focus on it.
        $(":focus").closest("tr").next("tr").find("select:visible,input:visible").focus();            
    }, 0);
});
function select2Focus()
{
    var select2 = $(this).data('select2');
    setTimeout(function() {
        if (!select2.isOpen()) {
            select2.open();
        }
    }, 0);  
}
Hyo answered 8/1, 2017 at 9:39 Comment(0)
R
2

I've had the problem which was two pronged:
1. In a form with multiple select2 elements, the dropdown won't open on tab, and you need to press space key to open it
2. Once you have made a selection, the tabindex won't be honored and you have to manually click on the next input field

While the usual suggestions worked, I came up with my own version, since a library script was doing the conversion of normal select to select2, and hence I had no control over this initialization.

Here is the code that worked for me.

Tab to open

$(document).on("focus", ".select2", function() {
    $(this).siblings("select").select2("open");
});

Move to next on selection

var inputs = $("input,select"); // You can use other elements such as textarea, button etc. 
                                //depending on input field types you have used
$("select").on("select2:close",function(){
    var pos = $(inputs).index(this) + 1;
    var next = $(inputs).eq(pos);
    setTimeout( function() {
        next.focus();
        if (next.siblings(".select2").length) { //If it's a select
            next.select2("open");
        }
    }, 500); //The delay is required to allow default events to occur
});

Hope this helps.

Radke answered 20/6, 2017 at 5:1 Comment(1)
This answer worked best for me without breaking acessbility / tab index.Jobe
A
2

an important thing is to keep the multiselect open all the time. The simplest way is to fire open event on 'conditions' in your code:

<select data-placeholder="Choose a Country..." multiple class="select2-select" id="myList">
    <option value="United States">United States</option>
    <option value="United Kingdom">United Kingdom</option>
    <option value="Afghanistan">Afghanistan</option>
    <option value="Aland Islands">Aland Islands</option>
    <option value="Albania">Albania</option>
    <option value="Algeria">Algeria</option>
</select>

javascript:

$(".select2-select").select2({closeOnSelect:false});
$("#myList").select2("open");

fiddle: http://jsfiddle.net/xpvt214o/153442/

Adenoid answered 18/4, 2018 at 16:26 Comment(0)
S
1

This worked for me using Select2 v4.0.3

//Initialize Select2
 jQuery('.js-select').select2();

// Make Select2 respect tab focus
function select2Focus(){
    jQuery(window).keyup(function (e) {
        var code = (e.keyCode ? e.keyCode : e.which);
        if (code == 9 && jQuery('.select2-search__field:focus').length) {
            jQuery('.js-select').select2('open');
        }
    });
}

select2Focus();

Fork of Irvin Dominin's demo: http://jsfiddle.net/163cwdrw/

Serdab answered 21/3, 2017 at 5:39 Comment(0)
D
0

I tried these solutions with the select2 version 3.4.8 and found that when you do blur, the select2 triggers first select2-close then select2-focus and then select2-blur, so at the end we end up reopening forever the select2.

Then, my solution is this one:

$('#elemId').on('select2-focus', function(){
    var select2 = $(this).data('select2');
    if( $(this).data('select2-closed') ){
        $(this).data('select2-closed', false)
        return
    }
    if (!select2.opened()) {
        select2.open()
    }
}).on('select2-close', function(){
    $(this).data('select2-closed', true)
})
Diacaustic answered 6/5, 2014 at 14:32 Comment(0)
C
0

Somehow select2Focus didn't work here with empty selection, couldn't figured out the issue, therefore I added manual control when after focus event auto open get's triggered.

Here is coffeescript:

$("#myid").select2()
  .on 'select2-blur', ->
    $(this).data('select2-auto-open', 'true')
  .on 'select2-focus', ->
    $(this).data('select2').open() if $(this).data('select2-auto-open') != 'false'
  .on 'select2-selecting', ->
    $(this).data('select2-auto-open', 'false')
Collocate answered 6/6, 2014 at 9:22 Comment(0)
D
0

I've tried a pretty ugly solution but it fixed my problem.

    var tabPressed = false;

    $(document).keydown(function (e) {
        // Listening tab button.
        if (e.which == 9) {
            tabPressed = true;
        }
    });

    $(document).on('focus', '.select2', function() {
        if (tabPressed) {
            tabPressed = false;
            $(this).siblings('select').select2('open');
        }
    });
Discriminator answered 18/5, 2016 at 12:3 Comment(0)
S
0

You can use this :

 $(document).on('select2:open', () => {
    document.querySelector('.select2-search__field').focus();
  });
Sanction answered 3/5, 2021 at 4:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.