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

Select2 4.0.0 AJAX - Choose highlighted option with Tab

我正在通过 AJAX 获取用户 ID 和名称并使用 Select2 搜索它们,但我的用户已请求能够从预输入下拉列表中 select按 T​​ab,有效地将其视为按 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" 似乎是下拉列表可见时的焦点元素,并且突出显示的元素获得 class "select2-results__option--highlighted".

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

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

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

上调用更改事件

...(与初始 select2 相同)

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

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

所以我想出了这个 hack 来访问 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);
});

对于任何想要使用 tab-select 与 multi-select 一起工作的人来说,这对我有用:

$("#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

selectOnClose 功能似乎在 4.0.3 中稳定,并且更简单的解决方案:

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

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

你可以简单的改变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 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 框。

我在 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 助手,它是库的一部分,从当前元素的缓存中检索列表,然后用 select 强制新值。

祝你好运! :)

类似于@Semen Shekhovtsov 的解决方案,但如果您希望 TAB 实际跳转到下一个字段并生成 selection(更像是一个普通输入 select将)。 将 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();
}

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

我还希望 TAB 键可以使 selection 立即聚焦到下一个 select2 字段。 SHIFT-TAB 反而会聚焦前一个 select2 字段。

这是我的最终代码(select2 4.0.5,在 FF 和 Chrome 上测试)。我假设您的 select 字段具有 "select2" class:

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

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