Select2 v4 unable to tab into, press enter, and then select, with Firefox (aka mouseless access)
Asked Answered
S

4

13

I am currently unable to tab into a Select2 enabled <select> element in Firefox (38.0.5) - in other words, cannot access to select <option> in a mouseless manner. In Chrome, you can tab through a form and press enter in order to start selecting an item in the Select2 select element. I have not tested in other browsers, but before submitting an actual bug report I want to verify whether others are experiencing the same issue?

You can duplicate on the demo page.

  • Select2 v4.0.0
  • Twitter Bootstrap 3.3.4 (although have not added any additional items to style for Bootstrap)
  • Firefox v38.0.5
Shabbygenteel answered 3/6, 2015 at 21:19 Comment(3)
I wasn't able to reproduce this on the documentation website, but I'd recommend creating a ticket on GitHub about it (and include the step-by-step instructions to reproduce).Quijano
After disabling and enabling all add-ons, it turns out Vimperator does not play nicely with Select2 whereas it works fine with Select2 v3.x (moreover, v3.x allows you to press enter, but also the up/down keys to open up the <select> - I miss the latter).Shabbygenteel
For what it's worth (if only to save someone else time figuring out whether it's a known issue or not) I've submitted a ticket on GitHub regarding this conflict with Vimperator at github.com/select2/select2/issues/3435.Shabbygenteel
I
13

While its not a perfect solution, the following is what we are using to simulate normal keyboard navigation on an HTML form that contains a Select2 element.

/**
 * WARNING: untested using Select2's option ['selectOnClose'=>true]
 *
 * This code was written because the Select2 widget does not handle
 * tabbing from one form field to another.  The desired behavior is that
 * the user can use [Enter] to select a value from Select2 and [Tab] to move
 * to the next field on the form.
 *
 * The following code moves focus to the next form field when a Select2 'close'
 * event is triggered.  If the next form field is a Select2 widget, the widget
 * is opened automatically.
 *
 * Users that click elsewhere on the document will cause the active Select2
 * widget to close.  To prevent the code from overriding the user's focus choice
 * a flag is added to each element that the users clicks on.  If the flag is
 * active, then the automatic focus script does not happen.
 *
 * To prevent conflicts with multiple Select2 widgets opening at once, a second
 * flag is used to indicate the open status of a Select2 widget.  It was
 * necessary to use a flag instead of reading the class '--open' because using the
 * class '--open' as an indicator flag caused timing/bubbling issues.
 *
 * To simulate a Shift+Tab event, a flag is recorded every time the shift key
 * is pressed.
 */
jQuery(document).ready(function($) {
    var docBody = $(document.body);
    var shiftPressed = false;
    var clickedOutside = false;
    //var keyPressed = 0;

    docBody.on('keydown', function(e) {
        var keyCaptured = (e.keyCode ? e.keyCode : e.which);
        //shiftPressed = keyCaptured == 16 ? true : false;
        if (keyCaptured == 16) { shiftPressed = true; }
    });
    docBody.on('keyup', function(e) {
        var keyCaptured = (e.keyCode ? e.keyCode : e.which);
        //shiftPressed = keyCaptured == 16 ? true : false;
        if (keyCaptured == 16) { shiftPressed = false; }
    });

    docBody.on('mousedown', function(e){
        // remove other focused references
        clickedOutside = false;
        // record focus
        if ($(e.target).is('[class*="select2"]')!=true) {
            clickedOutside = true;
        }
    });

    docBody.on('select2:opening', function(e) {
        // this element has focus, remove other flags
        clickedOutside = false;
        // flag this Select2 as open
        $(e.target).attr('data-s2open', 1);
    });
    docBody.on('select2:closing', function(e) {
        // remove flag as Select2 is now closed
        $(e.target).removeAttr('data-s2open');
    });

    docBody.on('select2:close', function(e) {
        var elSelect = $(e.target);
        elSelect.removeAttr('data-s2open');
        var currentForm = elSelect.closest('form');
        var othersOpen = currentForm.has('[data-s2open]').length;
        if (othersOpen == 0 && clickedOutside==false) {
            /* Find all inputs on the current form that would normally not be focus`able:
             *  - includes hidden <select> elements whose parents are visible (Select2)
             *  - EXCLUDES hidden <input>, hidden <button>, and hidden <textarea> elements
             *  - EXCLUDES disabled inputs
             *  - EXCLUDES read-only inputs
             */
            var inputs = currentForm.find(':input:enabled:not([readonly], input:hidden, button:hidden, textarea:hidden)')
                .not(function () {   // do not include inputs with hidden parents
                    return $(this).parent().is(':hidden');
                });
            var elFocus = null;
            $.each(inputs, function (index) {
                var elInput = $(this);
                if (elInput.attr('id') == elSelect.attr('id')) {
                    if ( shiftPressed) { // Shift+Tab
                        elFocus = inputs.eq(index - 1);
                    } else {
                        elFocus = inputs.eq(index + 1);
                    }
                    return false;
                }
            });
            if (elFocus !== null) {
                // automatically move focus to the next field on the form
                var isSelect2 = elFocus.siblings('.select2').length > 0;
                if (isSelect2) {
                    elFocus.select2('open');
                } else {
                    elFocus.focus();
                }
            }
        }
    });

    /**
     * Capture event where the user entered a Select2 control using the keyboard.
     * http://stackoverflow.com/questions/20989458
     * http://stackoverflow.com/questions/1318076
     */
    docBody.on('focus', '.select2', function(e) {
        var elSelect = $(this).siblings('select');
        if (elSelect.is('[disabled]')==false && elSelect.is('[data-s2open]')==false
            && $(this).has('.select2-selection--single').length>0) {
            elSelect.attr('data-s2open', 1);
            elSelect.select2('open');
        }
    });

});
Incense answered 21/9, 2016 at 0:47 Comment(3)
@Incense : is it you whow write this code? If yes, well done because it is working very well and you should suggest it as an option in the official GIT repo. If not, add credits to the developer. FYI, the select2 element doesn't close automatically on Internet Explorer in all cases (select an option, cancel with escape or tab). If you have an idea to fix this, you are welcome. ThanksAntifreeze
@sdespont: I encountered the same issue too. My solution is to change the select2 class of the last function, from docBody.on('focus', '.select2', function(e), to docBody.on('focus', '.mSelect2', function(e). Surely, you need to replace with the class name you have.Mornay
I installed it last night. Work not perfectly. It doesn't close the last element after click "enter", So I debug for some hours. And found, every element should has "ID" attribute, then works.Vesuvius
B
7

Building on zDaniels excellent answer.

I have created a bower installable version of the code.

$ bower install select2-tab-fix

Source and documentation at

https://github.com/peledies/select2-tab-fix

Barbey answered 15/3, 2017 at 19:52 Comment(0)
B
6

This was really bothering me too, so I set it to open on focus. So when tabbing to the select, it will open automatically.

$(document).ready(function () {
    $(".select2-selection").on("focus", function () {
        $(this).parent().parent().prev().select2("open");
    });
});

Hope that helps, I'm in a different version of Bootstrap and firefox, so let me know if it doesn't!

Balladist answered 29/1, 2016 at 23:13 Comment(1)
The only solution that works with tab controlSmalley
S
0

Use @zDaniels Answer, it is working like a charm. But the only problem is it will not close the last select2. To resolve this, you could write a jquery function with the below line in the on-change event of your last select2 element. I hope this helps to make a tweak.

    $(document).ready(function () {
      $(YOUR_LAST_ELEMENT).on("change", function () {
        $(this).parent().parent().prev().select2("close");
      });
    });
Schoenfeld answered 17/3, 2020 at 0:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.