47

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.

andreivictor
  • 7,628
  • 3
  • 48
  • 75

17 Answers17

92

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

KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 1
    Thank you for this. Works for me in Chrome, FF, and Safari on mac, in quick testing. – mungojerie Mar 12 '19 at 19:00
  • 1
    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. – mungojerie May 17 '19 at 10:40
  • If I adjust your fiddle to using v4.0.7, it does not work anymore. https://jsfiddle.net/nwLbqfa4/ – mungojerie May 17 '19 at 10:45
  • 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.... :) – mungojerie May 17 '19 at 10:56
  • Confirmed -- does not work on 4.0.7 (the latest version at the time of this writing). – Matt Olson May 20 '19 at 15:36
  • 1
    @MattOlson, figured out a solution for 4.0.7 that works across all 4+ versions – KyleMit May 30 '19 at 16:56
  • @mungojerie, thanks for the diagnostics. Should have a solution in place. Check it out and let me know. – KyleMit May 30 '19 at 16:57
  • 1
    This seems to break tab-index when trying to tab through form to next field after select2 input. – ouija Jul 26 '20 at 09:36
  • @ouija, can you add a demo with this happening? – KyleMit Jul 26 '20 at 23:16
  • 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. – gecclesinc Nov 12 '20 at 02:18
  • 1
    How about on multiple? It doesn't work on multiple. – kodfire May 07 '21 at 08:22
  • I'm wondering, instead of just stealing focus during close, can we forward the focus to the next element in the form? – Jules Colle Jan 06 '23 at 22:44
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>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
tonywchen
  • 291
  • 1
  • 2
  • 4
  • for the new version 4.0.0 you need to update the event and their targets. – Anas May 27 '15 at 12:00
  • demo is not working. Tried to update it to v4.0 and still not working: http://jsfiddle.net/brunodd/c7kvsu3o/5/ – brunodd Oct 15 '15 at 11:14
  • Select2 doesn't trigger the focus event on the original element when the dropdown is focused. https://github.com/select2/select2/issues/4025 – CodeMind Jan 17 '18 at 10:17
25

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');
    });
}
rain01
  • 1,194
  • 1
  • 16
  • 25
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/

Irvin Dominin
  • 30,819
  • 9
  • 77
  • 111
  • 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. – andreivictor Jan 08 '14 at 09:18
  • I've tested in chrome: works OK. In mozilla I get the following error 'event is not defined'. – andreivictor Jan 08 '14 at 15:32
  • I've searched a little about firefox events, it seems to be a tricky problem. Anyway, many thanks for your help. – andreivictor Jan 09 '14 at 05:22
  • @andreivictor glad to help you, but I want to find a way to solve this (for the checkmark too :-) – Irvin Dominin Jan 09 '14 at 07:31
  • Example not working. Tried to update to v4.0 still not working: http://jsfiddle.net/brunodd/fnjNb/71/ – brunodd Oct 15 '15 at 11:13
  • @brunodd select2 4.0 have dropped events support: https://github.com/select2/select2/issues/1908, I'm checking for a workaround – Irvin Dominin Oct 15 '15 at 11:48
  • Thanks @IrvinDominin. Here is what I am using: http://jsfiddle.net/brunodd/c7kvsu3o/9/ – brunodd Oct 15 '15 at 11:52
  • However for some reason is not working on IE11 =( Guess this might be something to do with select2 itself https://github.com/select2/select2/issues/3300 – brunodd Oct 15 '15 at 12:51
  • I gave the latest fiddle a shot in the current version of Chrome and the select menu did not open on focus. – neanderslob Mar 18 '16 at 07:42
  • 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 http://jsfiddle.net/xk5Lyfn0/1/ – George Mauer Oct 19 '17 at 16:36
10

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");
});
Claudio Ikeda
  • 111
  • 1
  • 3
  • 1
    This solution is not working in IE =/ It opens, but it never closes (not even clicking outside, tabbing). Tested in IE 11 – cr0ss Oct 14 '15 at 17:59
  • 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.) – hackel Nov 20 '15 at 04:15
  • Thanks man, between the old and the new version all the info is messed up. Works for me, just .select2("open") does the trick. – Obed Parlapiano Jul 17 '17 at 10:22
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

Mathieu de Lorimier
  • 975
  • 3
  • 19
  • 32
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

Sangar82
  • 5,070
  • 1
  • 35
  • 52
Tomas Molnar
  • 111
  • 1
  • 4
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');
        });
    }
});
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Douglas
  • 51
  • 1
  • 2
  • 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? – KyleMit Apr 23 '19 at 20:17
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");
});
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
EricksonG
  • 472
  • 4
  • 10
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);  
}
Markus1980Wien
  • 471
  • 1
  • 5
  • 15
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.

Gaurav
  • 347
  • 4
  • 12
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/

rajeev
  • 1,275
  • 7
  • 27
  • 45
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/

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)
})
Davsket
  • 1,248
  • 1
  • 9
  • 14
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')
Priit
  • 1
  • 2
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');
        }
    });
wmwmwm
  • 91
  • 3
0

You can use this :

 $(document).on('select2:open', () => {
    document.querySelector('.select2-search__field').focus();
  });
Hammad Ahmed khan
  • 1,618
  • 7
  • 19