Select2 4.0.0 AJAX - выберите выделенный параметр с помощью Tab

Я извлекаю идентификаторы и имена пользователей через AJAX и использую 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».

Я пробовал несколько решений, но, похоже, ничего не сработало, особенно потому, что этот элемент появляется и исчезает каждый раз, когда открывается раскрывающийся список. К сожалению, я потерял код из-за своих попыток, но они в основном состояли из выполнения preventDefault при нажатии Tab на сфокусированном вводе, а затем запуска события щелчка на выделенном элементе или срабатывания клавиши ввода на вводе.

Я также пытался настроить параметр 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();
            }
        }
    });

person MikeOShay    schedule 03.11.2015    source источник
comment
ты уже понял это? Столкнулся с теми же проблемами.   -  person grant    schedule 19.11.2015
comment
Нет кости, я уже исчерпал большую часть своих возможностей по устранению неполадок, прежде чем публиковать здесь. Если вы знаете что-то еще, я должен отметить это, чтобы помочь людям найти это, кто мог бы ответить, дайте мне знать, и я отредактирую это в сообщении.   -  person MikeOShay    schedule 20.11.2015
comment
оцените это - я тоже потратил некоторое время на это, но безуспешно. Я дам вам знать, если я что-нибудь выясню :)   -  person grant    schedule 20.11.2015


Ответы (9)


Я обнаружил, что принятый ответ 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');
        }
    });
}
person inappropriately optimized    schedule 27.02.2020
comment
Спасибо огромное спасибо вам за этот удивительный ответ. Это спасло мой день. Еще раз спасибо. - person NewbieCoder; 25.03.2020

Я пытался найти решение и этой проблемы.
Основная проблема заключается в том, что события select2 не дают никакого представления о том, какая клавиша была нажата.

Поэтому я придумал этот хак для доступа к событию keydown внутри контекста select2.
Я тестировал его в меру своих возможностей, и, похоже, он работает отлично.

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);
});
person Sniffdk    schedule 27.11.2015
comment
Умная! Таким образом, вы по-прежнему ловите событие нажатия клавиши, пока происходит событие закрытия, а затем устанавливаете его программно. Вам все равно нужно вручную установить значение templateresult, если вы его шаблонируете. Я приму ваш и обновлю вопрос, чтобы показать свои изменения. - person MikeOShay; 28.11.2015
comment
OOF это уродливо, но все же кажется лучшим вариантом (черт возьми, если я знаю лучше). Большое спасибо. - person neanderslob; 18.03.2016
comment
Спасибо за ваш код. Это мне очень помогло! Спасибо! - person dns_nx; 06.07.2016
comment
Похоже, это решение не работает для множественного выбора. Ответ JP (stackoverflow.com/a/35493230/1299792) касается множественного выбора с ограничением до одного поля на странице. - person Marklar; 18.01.2017

Функция selectOnClose кажется стабильной в 4.0.3 и имеет гораздо более простое решение:

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

Возможно, этой функции мешает использование шаблонов, я их не использую, поэтому не проверял.

person Dylan Smith    schedule 29.11.2016
comment
с selectOnClose параметр выбирается, даже если вы щелкаете в другом месте в теле страницы (не щелкая параметр в выборе). Если для вас это было проблемой, смогли ли вы найти способ ее обойти? - person Marklar; 18.01.2017
comment
В моем случае это не было проблемой, поэтому я не искал способ обойти это. Я предполагаю, что вам придется использовать решение @Sniffdk, чтобы отвечать только на TAB, но не на другие способы закрытия выбора. - person Dylan Smith; 18.01.2017
comment
Спасибо что нашли время ответить. Для всех остальных в той же ситуации решение Sniffdk не работает для множественного выбора. Решение JP работает, но обычные события DOM не запускаются. - person Marklar; 19.01.2017
comment
selectOnClose уродлив в том смысле, что он вызывает хотя бы один выбор, а также выбирает по esc. - person mmking; 20.06.2017
comment
Это необходимо для всех, кто создает экран ввода данных. Типы ввода данных не любят использовать мышь... никогда. По крайней мере, не те, с которыми я сейчас работаю. Молодец, сэр! - person Mike Devenney; 30.11.2018

Для тех, кто хочет, чтобы выбор вкладок работал с множественным выбором, это сработало для меня:

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

person JP.    schedule 18.02.2016
comment
Спасибо за решение. Просто примечание для других: вызов trigger("change") не будет запускать события DOM (select2:open, select2:close и т. д.), которые обычно появляются при выборе параметра. - person Marklar; 18.01.2017
comment
Я вижу какое-то сомнительное поведение при нажатии клавиши, иногда оно вызывается пять раз при нажатии клавиши, а иногда и вовсе. Вы хоть представляете, что случилось? - person mmking; 20.06.2017
comment
@mmking Как ни странно, а также из ссылок на такие обсуждения, как all-keys">this Я считаю, что это нормальное поведение для события keydown. - person JP.; 21.06.2017

После того, как я поиграл со всеми этими решениями, это, похоже, подходит для большинства случаев и работает лучше всего для меня. Примечание. Я использую 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.

person adameska    schedule 09.02.2018

Я использую версию select2 4.0.6-rc.1 с vue, вот что я сделал, чтобы сохранить привязку в безопасности:

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, который является частью библиотеки, извлекает список из кеша текущего элемента, а затем принудительно выбирает новое значение.

Удачи! :)

person levieraf    schedule 27.10.2018

Вы можете просто изменить источник управления Select2, только одну строку:

иначе если (ключ === KEYS.ENTER)

иначе если (ключ === KEYS.ENTER || ключ === 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.

person Semen Shekhovtsov    schedule 26.12.2016
comment
Я полагаю, что это решение было отправлено как запрос на вытягивание, но сопровождающий select2 сказал, что это критическое изменение и оно не будет объединено: github.com/select2/select2/pull/4325#issuecomment-229234605 - person Marklar; 18.01.2017
comment
@Marklar, все тесты и с этим изменением вроде все в порядке, проблем нет вообще. Я не знаю, кто назвал это «критическим изменением» и почему. До сих пор я использовал предложенное решение без каких-либо проблем. - person Semen Shekhovtsov; 19.01.2017
comment
Спасибо, что нашли время ответить и предоставить решение. Я предполагаю, что это может быть критическое изменение, о котором следует помнить, если вы когда-нибудь столкнетесь с какими-либо необъяснимыми проблемами. Если вы посмотрите на ссылку, которую я предоставил в своем последнем комментарии, вы увидите, что Кевин Браун (сопровождающий select2) заявил, что это критическое изменение, и что selectOnClose был его рекомендацией. - person Marklar; 20.01.2017

Подобно решению @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();
}
person Steven Nocker    schedule 20.03.2019

Я смешал некоторые из предложенных здесь решений с другими, чтобы открыть раскрывающийся список 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');
});
person Augusto Destrero    schedule 20.06.2019