Select2 4.0.0 AJAX - 使用 Tab 选择突出显示的选项

Posted

技术标签:

【中文标题】Select2 4.0.0 AJAX - 使用 Tab 选择突出显示的选项【英文标题】:Select2 4.0.0 AJAX - Choose highlighted option with Tab 【发布时间】:2016-02-03 02:08:05 【问题描述】:

我通过 AJAX 获取用户 ID 和名称并使用 Select2 搜索它们,但我的用户要求能够通过按 Tab 从预先输入的下拉列表中进行选择,有效地将其视为按 Enter 键。这是我的 select2 声明:

$("#user-select").select2(
    ajax: 
        url: "/api/User",
        method: "get",
        data: function (params) 
            return 
                search: params.term
            ;
        ,
        beforeSend: function () 
            $(".loading-results").text("Loading...");
        ,
        processResults: function (data) 
            return 
                results: data
            ;
        ,
        cache: true
    ,
    allowClear: true,
    placeholder: "Enter a User ID or Name",
    templateResult: function (data) 
        return "(" + data.id + ") " + data.name;
    ,
    templateSelection: function (data) 
        return "(" + data.id + ") " + data.name;
    

“.select2-search__field”似乎是下拉菜单可见时的焦点元素,并且突出显示的元素获得类“select2-results__option--highlighted”。

我尝试了一些解决方案,但似乎没有任何效果,尤其是因为该元素会在下拉菜单打开时出现和消失。不幸的是,我在尝试中丢失了代码,但它们主要包括在焦点输入上点击 Tab 时执行 preventDefault,然后在突出显示的元素上触发单击事件或在输入上触发 enter 键。

我也尝试过调整 selectOnClose 选项,但总的来说这似乎有问题,当我让它正常运行时会导致无限循环,更不用说根据按下的键来覆盖它了。

[编辑] 所选解决方案有效,但不考虑指定的 templateResult,而是显示“()未定义”。因此,我对其进行了调整,将突出显示的答案添加为覆盖 Select 的选定选项,然后在该 Select 上调用 change 事件。

...(与初始选择2相同)

).on('select2:close', function (evt) 
    var context = $(evt.target);

    $(document).on('keydown.select2', function (e) 
        if (e.which === 9)  // tab
            var highlighted = context.data('select2').$dropdown.find('.select2-results__option--highlighted');

            if (highlighted) 
                var data = highlighted.data('data');

                var id = data.id;
                var display = data.name;

                $("#user-select").html("<option value='" + id + "' selected='selected'>" + display + "</option>");
                $("#user-select").change();
            
            else 
                context.val("").change();
            
        
    );

【问题讨论】:

你想通了吗?遇到同样的问题。 没有骰子,在我在这里发布之前,我已经用尽了大部分的故障排除能力。如果您知道其他任何我应该标记的内容以帮助人们找到可能能够回答的人,请告诉我,我会将其编辑到帖子中。 欣赏它 - 我也花了一些时间敲打它,但没有运气。如果我有什么发现我会告诉你的:) 【参考方案1】:

我也一直在努力寻找解决此问题的方法。 主要问题是 select2 事件没有提供任何关于按下哪个键的信息。

所以我想出了这个技巧来访问 select2 上下文中的 keydown 事件。 我一直在尽我所能测试它,它似乎工作得很好。

selectElement
.select2( options ... )
.on('select2:close', function(evt) 
    var context = $(evt.target);

    $(document).on('keydown.select2', function(e) 
        if (e.which === 9)  // tab
            var highlighted = context
                              .data('select2')
                              .$dropdown
                              .find('.select2-results__option--highlighted');
            if (highlighted) 
                var id = highlighted.data('data').id;
                context.val(id).trigger('change');
            
        
    );

    // unbind the event again to avoid binding multiple times
    setTimeout(function() 
        $(document).off('keydown.select2');
    , 1);
);

【讨论】:

聪明!因此,您仍然可以在关闭事件进行时捕获 keydown 事件,然后以编程方式设置它。如果您正在模板化,您仍然需要手动设置模板结果的值。我会接受你的并更新问题以显示我的修改。 OOF 这很丑,但似乎仍然是最好的选择(如果我知道得更好,那该死的)。非常感谢。 感谢您的代码。这对我帮助很大!谢谢! 看来此解决方案不适用于多选。 JP 的答案 (***.com/a/35493230/1299792) 解决了多选问题,每页限制为一个字段。【参考方案2】:

selectOnClose 功能在 4.0.3 中似乎很稳定,而且解决方案要简单得多:

$("#user-select").select2(
  ...
  selectOnClose: true
);

模板的使用可能会干扰此功能,我没有使用模板,所以我没有测试过。

【讨论】:

使用 selectOnClose,即使您单击页面正文中的其他位置(而不是单击选择中的选项),也会选择该选项。如果这对您来说是个问题,您能找到解决方法吗? 在我的情况下这不是问题,所以我还没有找到解决方法。我的猜测是,您必须使用 @Sniffdk 的解决方案才能仅响应 TAB 而不是其他关闭选择的方式。 感谢您抽出宝贵时间回复。对于处于相同情况的其他任何人,Sniffdk 的解决方案不适用于多选。 JP 的解决方案可以,但通常的 DOM 事件不会被触发。 selectOnClose 很难看,因为它强制至少选择一个,并且还会在 esc 上选择。 对于任何创建数据输入屏幕的人来说,这是必须具备的。数据输入类型不喜欢使用鼠标......永远。至少,不是我目前正在使用的那些。先生干得好!【参考方案3】:

对于任何希望让选项卡选择与多选一起使用的人,这对我有用:

$("#selected_ids").select2( multiple: true ).on('select2:open', function(e)  selectOnTab(e) );

function selectOnTab(event)

  var $selected_id_field = $(event.target);

  $(".select2-search__field").on('keydown', function (e) 
    if (e.which === 9) 
      var highlighted = $('.select2-results__option--highlighted');

      if (highlighted) 
        var data = highlighted.data('data');
        var vals = $selected_id_field.val();
        if (vals === null)
          vals = [];
        
        vals.push(data.id)
        $selected_id_field.val(vals).trigger("change")
      
    
  );

目前这将我限制为每页一个字段,但它正在完成这项工作。

感谢 MikeOShay 和 Sniffdk 对此进行深入研究。 目前有一个未解决的问题可以为我们解决这个问题:

https://github.com/select2/select2/issues/3359

【讨论】:

感谢您的解决方案。只是给其他人的说明,调用trigger("change") 不会触发通常在选择选项时看到的 DOM 事件(select2:openselect2:close 等)。 我看到了一些可疑的 keydown 行为,有时在按下某个键时它会被调用五次,有时根本不会。你知道出了什么问题吗? @mmking 轶事,并参考this 之类的讨论,我相信这是 keydown 事件的正常行为。【参考方案4】:

在尝试了所有这些解决方案之后,这个似乎抓住了最多的案例并且对我来说效果最好。请注意,我使用的是 select2 4.0.3,但不喜欢 selectOnClose,如果您有多个 select2 框和多个它可能会造成严重破坏!

var fixSelect2MissingTab = function (event) 
    var $selected_id_field = $(event.target);

    var selectHighlighted = function (e) 
        if (e.which === 9) 
            var highlighted = $selected_id_field.data('select2').$dropdown.find('.select2-results__option--highlighted');

            if (highlighted) 
                var data = highlighted.data('data');
                if (data) 
                    var vals = $selected_id_field.val();
                    if (vals === null) 
                        vals = [];
                    
                    if (vals.constructor === Array) 
                        vals.push(data.id);
                     else 
                        vals = data.id;
                    
                    $selected_id_field.val(vals).trigger("change");
                
            
        
    ;

    $('.select2-search__field').on('keydown', selectHighlighted);       


$(document).on('select2:open', 'select', function (e)  fixSelect2MissingTab(e) );
$(document).on('select2:close', 'select', function (e) 
    //unbind to prevent multiple
    setTimeout(function () 
        $('.select2-search__field').off('keydown');
    , 10);
);

这个解决方案的好处是它是通用的,可以应用在框架代码中,即使是动态添加的 select2 框也可以工作。

【讨论】:

【参考方案5】:

我正在使用带有 vue 的 select2 版本 4.0.6-rc.1,这是我为保证绑定安全所做的:

selectElement
.select2( options ... )
.on("select2:close", function(evt) 
      var context = $(evt.target);

      $(document).on("keydown.select2", function(e) 
          if (e.which === 9) 
              var highlighted = context
                  .data("select2")
                  .$dropdown.find(".select2-results__option--highlighted");

              if (highlighted) 
                  $.fn.select2.amd.require(["select2/utils"], function(Utils) 
                      var data = Utils.__cache[highlighted.data().select2Id].data;
                      var $select2 = context.data('select2');
                      $select2.trigger("select", data: data);
                  );
              
          
      );

      setTimeout(function() 
        $(document).off("keydown.select2");
      , 1);
  );

对我来说,关键是 Utils 帮助器,它是库的一部分,从当前元素的缓存中检索列表,然后使用新值强制选择。

祝你好运! :)

【讨论】:

【参考方案6】:

我发现 Sniffdk 接受的答案不再适用于最新的 jquery 和 select2 库。它给了我一个Uncaught TypeError: Cannot read property 'id' of undefined

我想出了以下可行的解决方案(用于单选 select2 下拉菜单):

function pickSelect2OptionOnTab() 
    let $select;
    let optionSelected;
    let select2Closing = false;

    $('select').on('select2:closing', function(event) 
        select2Closing = true;
        $select = $(event.target);
        optionSelected = $('.select2-results__option--highlighted').text();
        setTimeout(function() 
            select2Closing = false;
        , 1);
    );

    $(document).bind('keydown', function(event) 
        if (event.key === 'Tab' && select2Closing) 
            const val = $select.find('option').filter(function() 
                return $(this).text() === optionSelected;
            ).first().prop('value');
            $select.val(val);
            $select.trigger('change');
        
    );

【讨论】:

非常感谢,非常感谢您的精彩回答。它拯救了我的一天。再次感谢。【参考方案7】:

你可以简单的改变Select2控件的来源,只有一行:

else if (key === KEYS.ENTER)

else if (key === KEYS.ENTER || key === KEYS.TAB)

从这里:

 this.on('keypress', function (evt) 
   var key = evt.which;

   if (self.isOpen()) 
     if (key === KEYS.ESC || key === KEYS.TAB ||
         (key === KEYS.UP && evt.altKey)) 
       self.close();

       evt.preventDefault();
      else if (key === KEYS.ENTER) 
       self.trigger('results:select', );

       evt.preventDefault();

到这里

this.on('keypress', function (evt) 
  var key = evt.which;

  if (self.isOpen()) 
    if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) 
      self.close();

      evt.preventDefault();
     else if (key === KEYS.ENTER || key === KEYS.TAB) 
      self.trigger('results:select', );

      evt.preventDefault();

可以在源文件 src/js/select2/core.js 中进行更改,也可以在编译后的版本中进行更改。当我应用此更改时,我已修改 src/js/select2/core.js 并执行 gruntfile.js 以再次编译 select2 库。此解决方案不是一种解决方法,而是 select2 的不错功能。

【讨论】:

我相信这个解决方案是作为拉取请求提交的,但 select2 的维护者说这是一个重大更改,不会被合并:github.com/select2/select2/pull/4325#issuecomment-229234605 @Marklar ,所有的测试和一切似乎都很好,这个变化完全没有问题。我不知道谁称其为“重大变化”以及为什么。到目前为止,我一直在使用建议的解决方案,没有任何问题。 感谢您抽出宝贵时间回复并提供解决方案。我想如果您遇到任何无法解释的问题,请记住这可能是一个重大变化。如果您查看我在上一条评论中提供的链接,您会看到 Kevin Brown(select2 的维护者)表示这是一个重大更改,并且 selectOnClose 是他的建议。【参考方案8】:

类似于@Semen Shekhovtsov 的解决方案,但如果您希望 TAB 实际跳转到下一个字段并进行选择(更像是普通的输入选择),则需要有所不同。 将 KEYS.ENTER 和 KEYS.TAB 分离到它们自己的 else if 块中,并省略 evt.preventDefaults()。 按照下面 core.js 或 select2.full.js 中的说明(如果您不想重新编译它)。

if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) 
  self.close();

  evt.preventDefault();
 else if (key === KEYS.ENTER)
  self.trigger('results:select', );
  evt.preventDefault();
 else if (key === KEYS.TAB)
  self.trigger('results:select', );
  // leave out the prevent default if you want it to go to the next form field after selection
  //evt.preventDefault();

【讨论】:

【参考方案9】:

我混合了这里提出的一些解决方案,others 在元素获得焦点时打开 select2 下拉菜单。

我还希望 TAB 键进行选择并立即聚焦下一个 select2 字段。 SHIFT-TAB 将聚焦前一个 select2 字段。

这是我的最终代码(select2 4.0.5,在 FF 和 Chrome 上测试)。我假设您的选择字段具有“select2”类:

$('.select2').select2().on('select2:close', function (e) 
    var target = $(e.target);

    $(document).on('keydown.select2', function(e) 
        if (e.which === 9)  // tab
            var highlighted = target
                              .data('select2')
                              .$dropdown
                              .find('.select2-results__option--highlighted');
            if (highlighted) 
                // select the option
                var id = highlighted.data('data').id;
                target.val(id);
                target.trigger('change');
                // focus the next (or the previous) field with "select2" class
                var set = $('.select2');
                var current_index = set.index(target);
                var next_index = current_index + 1;
                if (e.shiftKey) 
                    next_index = current_index - 1;
                
                var next = set.eq(next_index)
                next.focus();
            
        
    );

    // unbind the event again to avoid binding multiple times
    setTimeout(function() 
        $(document).off('keydown.select2');
    , 1);

);

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

【讨论】:

以上是关于Select2 4.0.0 AJAX - 使用 Tab 选择突出显示的选项的主要内容,如果未能解决你的问题,请参考以下文章

Select2:使用ajax数据源时添加和选择手动值

使用带有无限数据和过滤器的 SELECT2 4.0.0

用 Select2 换行选定的文本

select2 noresult 捕获输入

Select2 v4.0.0 性能问题

Select2 v4.0 使 optgroups 可选