Select2在焦点聚集时打开下拉列表

48

我有一个带有多个文本输入框和一些Select2元素的表单。 使用键盘在字段之间切换可以正常工作- Select2元素行为类似于表单元素,当使用tab键时接收到焦点。 我想知道是否可能在Select2元素获得焦点时打开下拉菜单。

我尝试过以下方法:

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

但是使用这段代码会导致下拉菜单在进行选择后再次打开。

17个回答

95

适用于v4.0+的工作代码*(包括4.0.7)

以下代码将在初始焦点上打开菜单,但当选择器在菜单关闭后重新聚焦时,不会陷入无限循环。

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

解释

防止无限聚焦循环

注意:focus事件会被触发两次

  1. 当通过制表键进入字段时
  2. 再次通过带有打开下拉菜单的制表键,以恢复焦点。

focus menu states

我们可以通过查找焦点事件类型之间的差异来防止无限循环。由于我们只想在最初对控件进行聚焦时打开菜单,因此我们必须以某种方式区分以下抬起事件之间的区别:

event timeline

以跨浏览器友好的方式做到这一点很困难,因为不同的浏览器发送不同的事件信息,并且Select2已经对其内部事件的触发进行了许多细微的更改,这些更改打断了之前的流程。

一个看起来行之有效的方法是在菜单的closing事件期间附加事件处理程序,并使用它来捕获即将发生的focus事件并防止其冒泡到DOM。然后,使用委派监听器,我们将仅在focus事件完全冒泡到document时调用实际的聚焦 -> 打开代码。

禁止打开已禁用选项

正如在这个github issue #4025 - Dropdown does not open on tab focus中提到的那样,我们应该检查以确保仅在:enabled选择元素上调用'open',像这样:

$(this).siblings('select<b>:enabled</b>').select2('open');

Select2 DOM遍历

我们需要对DOM进行一些遍历,因此这里是由Select2生成的HTML结构图:

Select2 DOM

Github上的源代码

以下是一些相关的代码片段:

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

过去,打开Select2会触发两次,但在问题#3503中已经修复了这个问题,因此应该可以避免一些卡顿现象。

PR #5357似乎破坏了之前在4.05中工作的焦点代码。

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>

已在Chrome、FF、Edge、IE11上进行测试


1
谢谢您。在快速测试中,它在我的Mac上的Chrome、FF和Safari浏览器中都可以工作。 - mungojerie
1
实际上,这给我造成了一个错误。或者说,我的代码与此的交互导致了一个错误。如果我从select2中点击到文本输入字段,select2会关闭然后重新打开。Chrome浏览器。Select2版本为4.0.7。 - mungojerie
1
@MattOlson,已经找到了一个适用于所有4+版本的4.0.7解决方案。 - KyleMit
1
当尝试通过select2输入后通过表单进行制表时,似乎会破坏tab-index。 - ouija
1
多个怎么样?它在多个上不起作用。 - kodfire
显示剩余7条评论

29

对于 版本3.5.4 (2015年8月30日及之前)

当前的答案仅适用于3.5.4版本及之前的版本,其中select2触发了模糊和聚焦事件 (select2-focus & select2-blur)。它使用$.one附加一次性使用处理程序来捕获初始焦点,然后在模糊时重新附加它以供后续使用。

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

我尝试了@irvin-dominin-aka-edward的两个答案,但也遇到了两个问题(需要点击两次下拉列表和Firefox抛出“事件未定义”)。

我找到了一个解决方案,似乎解决了这两个问题,而且还没有遇到其他问题。这是基于@irvin-dominin-aka-edward的答案,通过修改select2Focus函数,使其不是立即执行代码的其余部分,而是将其包装在setTimeout中。

在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>


对于新的4.0.0版本,您需要更新事件及其目标。 - Anas
演示不起作用。尝试将其更新到v4.0,仍然无法工作:http://jsfiddle.net/brunodd/c7kvsu3o/5/ - brunodd
Select2在下拉列表获得焦点时不会触发原始元素的焦点事件。https://github.com/select2/select2/issues/4025 - CodeMind

25

希望能找到一种适用于页面上所有Select2实例的简单方法。

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

更新:上述代码似乎在IE11/Select2 4.0.3上无法正常工作。

附言:还添加了过滤器来仅选择单选字段。带有multiple属性的选择不需要它,并且如果应用则可能会出现问题。

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

这个运行得很好,但似乎最近的 IE11 更新破坏了它 :) - Matthew Evans
只有这个对我起作用。我不得不将我的jquery和select2 cdns升级到分别为3.6和4.1.0。 - tblev

17

在选择完成后,可能会触发 select2-focus 事件。

我找到的唯一方法是将 select2-focusselect2-blur 事件结合起来,并使用 jQuery 的one 事件处理程序。

因此,当元素首次获得焦点时,由于 one 的原因,select2 只会打开一次,当元素失去焦点时,再次附加一个 one 事件处理程序,依此类推。

代码:

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

示例: http://jsfiddle.net/IrvinDominin/fnjNb/

更新

为了让鼠标单击生效,您必须检查触发处理程序的事件,它只能在事件为focus时触发open方法。

代码:

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

演示: http://jsfiddle.net/IrvinDominin/fnjNb/4/

SELECT2 V 4.0 的更新

select2 v 4.0 改变了其 API 并删除了自定义事件 (见https://github.com/select2/select2/issues/1908)。因此需要改变检测其焦点的方式。

代码:

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

演示: http://jsfiddle.net/IrvinDominin/xfmgte70/

我稍微搜索了一下关于Firefox事件的内容,看起来这是一个棘手的问题。不管怎样,非常感谢你的帮助。 - andreivictor
示例不起作用。尝试更新到v4.0仍然无法工作:http://jsfiddle.net/brunodd/fnjNb/71/ - brunodd
@brunodd,select2 4.0 不再支持事件:https://github.com/select2/select2/issues/1908,我正在寻找解决方法。 - Irvin Dominin
感谢@IrvinDominin。这是我正在使用的:http://jsfiddle.net/brunodd/c7kvsu3o/9/。 - brunodd
然而,由于某种原因,在IE11上无法正常工作 =( 猜测这可能与select2本身有关 https://github.com/select2/select2/issues/3300 - brunodd
显示剩余5条评论

10

有点晚了...不过分享一下我使用select2 4.0.0的代码

$("#my_id").select2();
$("#my_id").next(".select2").find(".select2-selection").focus(function() {
    $("#my_id").select2("open");
});

1
这个解决方案在IE中不起作用 =/ 它可以打开,但是无法关闭(即使在外部点击或切换标签页也不行)。已在IE 11中进行了测试。 - cr0ss
当您通过ID选择单个元素时,此方法有效,但如果要针对多个select2字段(并且不仅仅是每次打开第一个字段),则可以使用以下代码: $('.my-class').next('.select2').find('.select2-selection').focus(function (e) { $(this).closest('.select2').prev('select..my-class').select2('open'); }); (抱歉,显然不能在评论中换行。) - hackel
谢谢,老版本和新版本之间所有信息都混乱了。对我来说没问题,只需要使用.select2("open")就可以了。 - Obed Parlapiano

7

这里有一个适用于Select2版本4.x的替代解决方案。你可以使用监听器来捕获焦点事件,然后打开选择框。

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

在@tonywchen创建的示例基础上,您可以在此处找到工作示例链接。请注意,保留了HTML标记。

5
问题是,内部的焦点事件没有转化为jQuery事件,因此我修改了插件,并在Select2 4.0.3的第2063行将焦点事件添加到EventRelay中。
EventRelay.prototype.bind = function (decorated, container, $container) {
    var self = this;
    var relayEvents = [
      'open', 'opening',
      'close', 'closing',
      'select', 'selecting',
      'unselect', 'unselecting',
      'focus'
    ]};

当焦点发生时,只需要打开select2:

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

在Chrome 54,IE 11,FF 49和Opera 40上运行良好。


5

KyleMit的答案对我有效(谢谢!),但是我发现,对于允许搜索的select2元素,尝试通过Tab键切换到下一个元素无法使用(Tab顺序实际上丢失了),因此我添加了代码,以便在下拉菜单关闭时将焦点重新设置回主select2元素:

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

        <b>// Set focus back to select2 element on closing.
        s2element.on('select2:closing', function (e) {
            s2element.select2('focus');
        });</b>
    }
});

嘿@Douglas,我在尝试重现你在这里提到的问题时遇到了困难,但是4个赞表明其他人可能也遇到了类似的问题。你什么时候看到选项卡顺序丢失? - KyleMit

3

我尝试了许多方法,最终找到了以下适用于Select2 4.0.1的方法。元素是<select>元素。

$.data(element).select2.on("focus", function (e) {
    $(element).select2("open");
});

3

我使用的是Select2.full.js版本4.0.3,但以上解决方案都不能正常工作。因此,我结合了上述解决方案。首先,我修改了Select2.full.js文件,将内部的焦点和失去焦点事件转换为jQuery事件,就像"Thomas Molnar"在他的答案中做的那样。

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

然后我添加了以下代码来处理焦点和失焦,并将焦点设置到下一个元素。
$("#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);  
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接