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

时间:2015-11-03 02:17:28

标签: jquery ajax preventdefault jquery-select2-4

我通过AJAX获取用户ID和名称,并使用Select2搜索它们,但是我的用户已经请求能够通过按Tab键从typeahead下拉列表中进行选择,有效地将其视为按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 - 突出显示"。

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

我也尝试过调整selectOnClose选项,但是这看起来很麻烦,当我让它正常运行时会导致无限循环,更不用说根据按下的键来覆盖它了。

[编辑]
所选解决方案有效,但不会考虑指定的templateResult,而是显示"()undefined"。因此,我调整它以将突出显示的答案添加为上覆选择的所选选项,然后在该选择上调用更改事件。

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

9 个答案:

答案 0 :(得分:10)

我一直试图找到解决这个问题的方法 主要问题是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);
});

答案 1 :(得分:9)

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

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

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

答案 2 :(得分:2)

对于任何想要使用多选项进行制表选择的人来说,这对我有用:

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

答案 3 :(得分:1)

在使用所有这些解决方案之后,这个解决方案似乎能够抓住大多数情况并为我做最好的工作。注意我使用的是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框也是如此。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

您只需更改Select2控件的来源,只需更改一行:

  

否则if(key === KEYS.ENTER)

     

否则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的优秀功能。

答案 6 :(得分:0)

我正在将select2版本4.0.6-rc.1与vue一起使用,这是我为确保绑定安全所做的事情:

    handleScroll = () => {
    const scrolled =window.pageYOffset || document.documentElement.scrollTop;
    const hiddenpart = 100
    const notMoving = 700
    if(scrolled <= hiddenpart) {
        this.section0.style.transform = `translateY(${scrolled}px)`
        this.section1.style.transform = `translateY(${scrolled * 2}px)`
        this.footer.style.transform = `translateY(${scrolled * 2}px)`
    }
    if(scrolled <= notMoving && scrolled > hiddenpart) {

        this.section0.style.transform = `translateY(${scrolled}px)`
        this.section1.style.transform = `translateY(${scrolled + 100}px)`
        this.footer.style.transform = `translateY(${scrolled + 100}px)`
    }
    if(scrolled > notMoving) {
        this.section0.style.transform = `translateY(${700}px)`
        this.section1.style.transform = `translateY(${800}px)`
        this.footer.style.transform = `translateY(${800}px)`
    }
}

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

祝你好运! :)

答案 7 :(得分:0)

类似于@Semen Shekhovtsov的解决方案,但是如果您想让TAB实际跳到下一个字段并进行选择(更像是正常的输入选择),则要稍作改动。 如果阻塞,则将KEYS.ENTER和KEYS.TAB分离为自己的else,并省略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();
}

答案 8 :(得分:0)

我将此处提出的一些解决方案与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');
});