Select2 open dropdown on focus

北城以北 提交于 2019-11-27 20:28:27

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

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:

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

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

tonywchen

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>

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');
    });
}

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/

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");
});

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

Tomas Molnar

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

Douglas

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');
        });
    }
});
EricksonG

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");
});

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);  
}

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/

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/

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.

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)
})

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')

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');
        }
    });
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!