Перейти к следующему элементу в указателе вкладки

Я пытаюсь переместить фокус на следующий элемент в последовательности вкладок на основе текущего элемента, у которого есть фокус. Пока в поисках ничего не нашлось.

function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    currentElementId = "";
    currentElement.nextElementByTabIndex.focus();
}

Конечно, nextElementByTabIndex - это ключевая часть, чтобы это работало. Как мне найти следующий элемент в последовательности вкладок? Решение должно быть основано на JScript, а не на чем-то вроде JQuery.


person JadziaMD    schedule 26.08.2011    source источник
comment
зачем тебе эта строчка currentElementId = "";?   -  person    schedule 26.08.2011
comment
Я не думаю, что какие-либо браузеры предоставляют информацию о порядке табуляции, а алгоритм, используемый самими браузерами, слишком сложен для воспроизведения. Возможно, вы можете ограничить свои требования, например рассматривать только теги input, button и textarea и игнорировать атрибут tabindex.   -  person Wladimir Palant    schedule 26.08.2011
comment
Нам нужен ваш .newElementByTabIndex код, потому что он не работает.   -  person 0x499602D2    schedule 26.08.2011
comment
Опять же, возможно, ограничение на отдельные теги не нужно - можно проверить, существует ли метод focus().   -  person Wladimir Palant    schedule 26.08.2011
comment
@Omeid Herat Он предназначен для предотвращения случайного использования другими функциями в то время, когда они не должны.   -  person JadziaMD    schedule 26.08.2011
comment
@David Это функция, которой не существует, поэтому мой вопрос. : D   -  person JadziaMD    schedule 26.08.2011
comment
Я часто возвращаюсь к этому вопросу с похожими проблемами, и ни один из ответов здесь не подходит мне. Я выяснил, что работать с фокусом во время событий вкладки сложно. Обычно это можно решить, изменив tabIndex элементов, в которые вы не хотите вкладываться, на -1, когда порядок табуляции актуален, а затем обратно на 0, когда порядок табуляции больше не актуален.   -  person Rúnar Berg    schedule 19.09.2020


Ответы (18)


Без jquery: во-первых, добавьте в элементы с вкладками class="tabable", это позволит нам выбрать их позже. (Не забудьте префикс селектора класса "." В приведенном ниже коде)

var lastTabIndex = 10;
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFOcusIn
    var curIndex = currentElement.tabIndex; //get current elements tab index
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    var tabbables = document.querySelectorAll(".tabable"); //get all tabable elements
    for(var i=0; i<tabbables.length; i++) { //loop through each element
        if(tabbables[i].tabIndex == (curIndex+1)) { //check the tabindex to see if it's the element we want
            tabbables[i].focus(); //if it's the one we want, focus it and exit the loop
            break;
        }
    }
}
person Brian Glaz    schedule 26.08.2011
comment
Решение без необходимости добавлять имя к каждому элементу (поскольку есть способ сделать многие, если это возможно) было бы идеальным. - person JadziaMD; 26.08.2011
comment
хорошо, это для формы? Если все элементы, которые вам нужны, являются элементами ввода, вы можете заменить строку var tabbables = document.getElementsByName("tabable"); на var tabbables = document.getElementsByTagName("input");. - person Brian Glaz; 26.08.2011
comment
var tabbables = document.querySelectorAll("input, textarea, button") // IE8 +, получить ссылку на все вкладки без изменения HTML. - person Greg; 05.12.2012
comment
class = tabbable вместо использования атрибута name - person Chris F Carroll; 15.06.2015
comment
@ChrisFCarroll прав - особенно если это в форме, вам нужны разные имена для каждого элемента формы b / c, который передается при отправке формы. Поэтому используйте class = tabbable, а не name = tabbable. - person jbyrd; 01.09.2015
comment
Обратите внимание, что при использовании flexbox порядок элементов в DOM отличается от визуального в браузере. Простой выбор следующего элемента с вкладками не работает, когда вы меняете порядок элементов с помощью flexbox. - person Haneev; 12.05.2017

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

Попробуй сначала

Во-первых, я хотел бы посмотреть, можете ли вы просто запустить keypress событие для клавиши Tab на элементе, который в данный момент находится в фокусе. Для разных браузеров это может быть другой способ.

Если это не сработает, вам придется работать усерднее ...

Ссылаясь на реализацию jQuery, вы должны:

  1. Слушайте Tab и Shift + Tab
  2. Знайте, какие элементы доступны для вкладок
  3. Понять, как работает порядок табуляции

1. Слушайте Tab и Shift + Tab

Прослушивание Tab и Shift + Tab, вероятно, хорошо освещено в других местах в Интернете, поэтому я пропущу эту часть.

2. Знайте, какие элементы доступны для вкладок.

Сложнее узнать, какие элементы доступны для вкладок. По сути, элемент доступен для вкладок, если он является фокусируемым и не имеет установленного атрибута tabindex="-1". Итак, мы должны спросить, какие элементы можно сфокусировать. Следующие элементы доступны для фокусировки:

  • input, select, textarea, button и object элементы, которые не отключены.
  • a и area элементы, которые имеют href или имеют числовое значение для tabindex.
  • любой элемент, для которого задано числовое значение tabindex.

Более того, элемент доступен для фокусировки, только если:

  • Ни один из его предков не display: none.
  • Вычисленное значение visibility равно visible. Это означает, что ближайший предок, для которого установлено visibility, должен иметь значение visible. Если ни один из предков не имеет установленного visibility, то вычисленное значение равно visible.

Более подробная информация представлена ​​в другом ответе на переполнение стека.

3. Понять, как работает порядок табуляции.

Порядок табуляции элементов в документе контролируется атрибутом tabindex. Если значение не установлено, tabindex фактически 0.

Порядок tabindex для документа: 1, 2, 3,…, 0.

Первоначально, когда элемент body (или ни один элемент) не имеет фокуса, первым элементом в порядке табуляции является самый низкий ненулевой tabindex. Если несколько элементов имеют одинаковый tabindex, вы переходите по порядку документов, пока не дойдете до последнего элемента с этим tabindex. Затем вы переходите к следующему самому низкому tabindex, и процесс продолжается. Наконец, закончите с этими элементами с нулевым (или пустым) tabindex.

person Chris Calo    schedule 24.05.2012
comment
82 голоса без единого комментария, чтобы указать, какое из предложенных решений действительно работает? - person Thomas; 21.06.2021
comment
Мне интересно то же самое ???? не знаю, какое исправление правильное - person Chris Calo; 22.06.2021

Вот кое-что, что я построил для этой цели:

focusNextElement: function () {
    //add all elements we want to include in our selection
    var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
    if (document.activeElement && document.activeElement.form) {
        var focussable = Array.prototype.filter.call(document.activeElement.form.querySelectorAll(focussableElements),
        function (element) {
            //check for visibility while always include the current activeElement 
            return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
        });
        var index = focussable.indexOf(document.activeElement);
        if(index > -1) {
           var nextElement = focussable[index + 1] || focussable[0];
           nextElement.focus();
        }                    
    }
}

Функции:

  • настраиваемый набор фокусируемых элементов
  • не нужен jQuery
  • работает во всех современных браузерах
  • быстрый и легкий
person Mx.    schedule 03.02.2016
comment
Это наиболее эффективное и экономичное решение. Спасибо! Вот мой полный рабочий сценарий: stackoverflow.com/a/40686327/1589669 - person eapo; 19.11.2016
comment
Я добавил ниже фрагмент, чтобы включить сортировку по явному TabIndex focussable.sort (sort_by_TabIndex) - person DavB.cs; 07.03.2019
comment
Лучший ! Он должен быть таким сложным: nextElementSibling может быть не фокусируемым, следующий фокусируемый может не быть братом или сестрой. - person Tinmarino; 29.11.2019
comment
Хороший подход, но он должен допускать любой ввод, не относящийся к типу hidden, а также охватывающий textarea и select. - person Lucero; 13.08.2020

Я создал простой плагин jQuery, который делает именно это. Он использует селектор ': tabbable' пользовательского интерфейса jQuery, чтобы найти следующий элемент 'tabbable' и выбрать его.

Пример использования:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});
person Mark Lagendijk    schedule 11.09.2013

Суть ответа заключается в поиске следующего элемента:

  function findNextTabStop(el) {
    var universe = document.querySelectorAll('input, button, select, textarea, a[href]');
    var list = Array.prototype.filter.call(universe, function(item) {return item.tabIndex >= "0"});
    var index = list.indexOf(el);
    return list[index + 1] || list[0];
  }

Использование:

var nextEl = findNextTabStop(element);
nextEl.focus();

Заметьте, меня не волнует расстановка приоритетов tabIndex.

person André Werlang    schedule 26.04.2015
comment
Что, если порядок tabindex противоречит порядку документа? Я думаю, что массив должен быть отсортирован по номеру tabindex, а затем по порядку документа - person Chris F Carroll; 15.06.2015
comment
Да, это было бы более совместимо со спецификациями. Я не уверен в крайних случаях, в отношении родительских элементов и т. Д. - person André Werlang; 15.06.2015
comment
Что, если элемент, который не входит в число этих тегов, имеет атрибут tabindex? - person Matt Pennington; 18.05.2020
comment
@MattPennington Это будет проигнорировано. Фильтр (попытка) ускорить поиск, смело приспосабливайтесь. - person André Werlang; 18.05.2020

Tabbable - это небольшой пакет JS, который предоставляет вам список всех элементов с вкладками, в порядке табуляции . Таким образом, вы можете найти свой элемент в этом списке, а затем сосредоточиться на следующей записи списка.

Пакет правильно обрабатывает сложные крайние случаи, упомянутые в других ответах (например, предок не может быть display: none). И это не зависит от jQuery!

На момент написания этой статьи (версия 1.1.1) у него есть предостережения, что он не поддерживает IE8 и что ошибки браузера не позволяют ему правильно обрабатывать contenteditable.

person Nate Sullivan    schedule 23.11.2017

Кажется, вы можете проверить свойство tabIndex элемента, чтобы определить, является ли он фокусируемым. Элемент, который не может быть сфокусирован, имеет tabindex «-1».

Тогда вам просто нужно знать правила для позиций табуляции:

  • tabIndex="1" имеет высшую априорность.
  • tabIndex="2" имеет следующий наивысший приоритет.
  • Следующим идет tabIndex="3" и так далее.
  • tabIndex="0" (или tabbable по умолчанию) имеет самый низкий приоритет.
  • tabIndex="-1" (или без табуляции по умолчанию) не работает как позиция табуляции.
  • Для двух элементов, имеющих одинаковый tabIndex, тот, который появляется первым в DOM, имеет более высокий приоритет.

Вот пример того, как последовательно построить список позиций табуляции, используя чистый Javascript:

function getTabStops(o, a, el) {
    // Check if this element is a tab stop
    if (el.tabIndex > 0) {
        if (o[el.tabIndex]) {
            o[el.tabIndex].push(el);
        } else {
            o[el.tabIndex] = [el];
        }
    } else if (el.tabIndex === 0) {
        // Tab index "0" comes last so we accumulate it seperately
        a.push(el);
    }
    // Check if children are tab stops
    for (var i = 0, l = el.children.length; i < l; i++) {
        getTabStops(o, a, el.children[i]);
    }
}

var o = [],
    a = [],
    stops = [],
    active = document.activeElement;

getTabStops(o, a, document.body);

// Use simple loops for maximum browser support
for (var i = 0, l = o.length; i < l; i++) {
    if (o[i]) {
        for (var j = 0, m = o[i].length; j < m; j++) {
            stops.push(o[i][j]);
        }
    }
}
for (var i = 0, l = a.length; i < l; i++) {
    stops.push(a[i]);
}

Сначала мы проходим DOM, собирая все позиции табуляции по порядку с их индексами. Затем мы составляем окончательный список. Обратите внимание, что мы добавляем элементы с tabIndex="0" в самый конец списка после элементов с tabIndex, равными 1, 2, 3 и т. Д.

Чтобы получить полностью рабочий пример, где вы можете перемещаться по клавишам с помощью клавиши "Enter", посмотрите эту скрипку.

person chowey    schedule 13.11.2013

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

var allowedTags = {input: true, textarea: true, button: true};

var walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  {
    acceptNode: function(node)
    {
      if (node.localName in allowedTags)
        return NodeFilter.FILTER_ACCEPT;
      else
        NodeFilter.FILTER_SKIP;
    }
  },
  false
);
walker.currentNode = currentElement;
if (!walker.nextNode())
{
  // Restart search from the start of the document
  walker.currentNode = walker.root;
  walker.nextNode();
}
if (walker.currentNode && walker.currentNode != walker.root)
  walker.currentNode.focus();

Это учитывает только некоторые теги и игнорирует атрибут tabindex, но может быть достаточно в зависимости от того, чего вы пытаетесь достичь.

person Wladimir Palant    schedule 26.08.2011

Это мой первый пост на SO, поэтому у меня недостаточно репутации, чтобы прокомментировать принятый ответ, но мне пришлось изменить код следующим образом:

export function focusNextElement () {
  //add all elements we want to include in our selection
  const focussableElements = 
    'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled])'
  if (document.activeElement && document.activeElement.form) {
      var focussable = Array.prototype.filter.call(
        document.activeElement.form.querySelectorAll(focussableElements),
      function (element) {
          // if element has tabindex = -1, it is not focussable
          if ( element.hasAttribute('tabindex') && element.tabIndex === -1 ){
            return false
          }
          //check for visibility while always include the current activeElement 
          return (element.offsetWidth > 0 || element.offsetHeight > 0 || 
            element === document.activeElement)
      });
      console.log(focussable)
      var index = focussable.indexOf(document.activeElement);
      if(index > -1) {
         var nextElement = focussable[index + 1] || focussable[0];
         console.log(nextElement)
         nextElement.focus()
      }                    
  }
}

Изменение var на постоянное некритично. Основное изменение заключается в том, что мы избавились от селектора, проверяющего tabindex! = "-1". Позже, если элемент имеет атрибут tabindex И ему присвоено значение «-1», мы НЕ считаем его фокусируемым.

Причина, по которой мне нужно было изменить это, заключалась в том, что при добавлении tabindex = "- 1" к <input> этот элемент все еще считался фокусируемым, потому что он соответствует селектору "input [type = text]: not ([disabled])". Мое изменение эквивалентно следующему: «если у нас не отключен текстовый ввод, и у нас есть атрибут tabIndex, а значение этого атрибута равно -1, то нас не следует рассматривать в качестве фокусируемых.

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

person BrushyAmoeba    schedule 28.11.2017

Для компонента можно задать свойство tabindex. Он указывает, в каком порядке входные компоненты должны повторяться при выборе одного и нажатии табуляции. Значения выше 0 зарезервированы для настраиваемой навигации, 0 - «в естественном порядке» (поэтому будет вести себя по-другому, если установлено для первого элемента), -1 означает, что клавиатура не фокусируется:

<!-- navigate with tab key: -->
<input tabindex="1" type="text"/>
<input tabindex="2" type="text"/>

Его также можно установить для чего-то другого, кроме полей ввода текста, но не очень очевидно, что он там будет делать, если вообще что-нибудь. Даже если навигация работает, возможно, лучше использовать «естественный порядок» для чего-либо еще, чем очень очевидные элементы пользовательского ввода.

Нет, вам вообще не нужен JQuery или какие-либо сценарии для поддержки этого настраиваемого пути навигации. Вы можете реализовать его на стороне сервера без какой-либо поддержки JavaScript. С другой стороны, свойство также отлично работает в среде React, но не требует этого.

person Audrius Meskauskas    schedule 20.04.2020

Вот более полный вариант сосредоточения внимания на следующем элементе. Он следует рекомендациям спецификации и правильно сортирует список элементов с помощью tabindex. Также определяется обратная переменная, если вы хотите получить предыдущий элемент.

function focusNextElement( reverse, activeElem ) {
  /*check if an element is defined or use activeElement*/
  activeElem = activeElem instanceof HTMLElement ? activeElem : document.activeElement;

  let queryString = [
      'a:not([disabled]):not([tabindex="-1"])',
      'button:not([disabled]):not([tabindex="-1"])',
      'input:not([disabled]):not([tabindex="-1"])',
      'select:not([disabled]):not([tabindex="-1"])',
      '[tabindex]:not([disabled]):not([tabindex="-1"])'
      /* add custom queries here */
    ].join(','),
    queryResult = Array.prototype.filter.call(document.querySelectorAll(queryString), elem => {
      /*check for visibility while always include the current activeElement*/
      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;
    }),
    indexedList = queryResult.slice().filter(elem => {
      /* filter out all indexes not greater than 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;
    }).sort((a, b) => {
      /* sort the array by index from smallest to largest */
      return a.tabIndex != 0 && b.tabIndex != 0 
        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) 
        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;
    }),
    focusable = [].concat(indexedList, queryResult.filter(elem => {
      /* filter out all indexes above 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;
    }));

  /* if reverse is true return the previous focusable element
     if reverse is false return the next focusable element */
  return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) 
    : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);
}
person svarlitskiy    schedule 15.12.2017

Это потенциальное усовершенствование отличного решения, предлагаемого @Kano и @Mx. Если вы хотите сохранить порядок TabIndex, добавьте эту сортировку посередине:

// Sort by explicit Tab Index, if any
var sort_by_TabIndex = function (elementA, elementB) {
    let a = elementA.tabIndex || 1;
    let b = elementB.tabIndex || 1;
    if (a < b) { return -1; }
    if (a > b) { return 1; }
    return 0;
}
focussable.sort(sort_by_TabIndex);
person DavB.cs    schedule 07.03.2019

У меня есть набор индексов с 0 вкладками, по которым я хотел перемещаться с помощью клавиатуры.
Поскольку в этом случае имел значение только ПОРЯДОК элементов, я сделал это с помощью document.createTreeWalker

Итак, сначала вы создаете фильтр (вам нужны только [видимые] элементы, у которых есть атрибут tabIndex с ЧИСЛЕННЫМ значением.

Затем вы устанавливаете корневой узел, за пределами которого вы не хотите выполнять поиск. В моем случае this.m_tree - это ul-элемент, содержащий переключаемое дерево. Если вместо этого вам нужен весь документ, просто замените this.m_tree на document.documentElement.

Затем вы устанавливаете текущий узел на текущий активный элемент:

ni.currentNode = el; // el = document.activeElement

Затем вы возвращаете ni.nextNode() или ni.previousNode().

Примечание:
это НЕ вернет вкладки в правильном порядке, если у вас tabIndices! = 0 и порядок элементов НЕ является порядком tabIndex. В случае tabIndex = 0 tabOrder всегда соответствует порядку элементов, поэтому это работает (в этом случае).

protected createFilter(fn?: (node: Node) => number): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        return NodeFilter.FILTER_ACCEPT;
    }

    if (fn == null)
        fn = acceptNode;


    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter: NodeFilter = <NodeFilter><any>fn;
    (<any>safeFilter).acceptNode = fn;

    return safeFilter;
}



protected createTabbingFilter(): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        if (!node)
            return NodeFilter.FILTER_REJECT;

        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;

        if (window.getComputedStyle(<Element>node).display === "none")
            return NodeFilter.FILTER_REJECT;

        // "tabIndex": "0"
        if (!(<Element>node).hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;

        let tabIndex = parseInt((<Element>node).getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;

        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;

        return NodeFilter.FILTER_ACCEPT;
    }

    return this.createFilter(acceptNode);
}


protected getNextTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker

    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);

    ni.currentNode = el;

    while (currentNode = ni.nextNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}


protected getPreviousTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);
    ni.currentNode = el;

    while (currentNode = ni.previousNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}

Обратите внимание, что цикл while

while (currentNode = ni.nextNode())
{
    // Additional checks here
    // if(condition) return currentNode;
    // else the loop continues;
    return <HTMLElement>currentNode; // everything is already filtered down to what we need here
}

существует только в том случае, если вы хотите, если у вас есть дополнительные критерии, которые вы не можете фильтровать в фильтре, переданном в createTreeWalker.

Обратите внимание, что это TypeScript, вам нужно удалить все токены за двоеточиями (:) и между угловыми скобками (‹›), например <Element> или :(node: Node) => number, чтобы получить действующий JavaScript.

Вот как услуга переданный JS:

"use strict";
function createFilter(fn) {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        return NodeFilter.FILTER_ACCEPT;
    }
    if (fn == null)
        fn = acceptNode;
    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter = fn;
    safeFilter.acceptNode = fn;
    return safeFilter;
}
function createTabbingFilter() {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        if (!node)
            return NodeFilter.FILTER_REJECT;
        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;
        if (window.getComputedStyle(node).display === "none")
            return NodeFilter.FILTER_REJECT;
        // "tabIndex": "0"
        if (!node.hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;
        let tabIndex = parseInt(node.getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;
        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;
        return NodeFilter.FILTER_ACCEPT;
    }
    return createFilter(acceptNode);
}
function getNextTab(el) {
    let currentNode;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.nextNode()) {
        return currentNode;
    }
    return el;
}
function getPreviousTab(el) {
    let currentNode;
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.previousNode()) {
        return currentNode;
    }
    return el;
}
person Stefan Steiger    schedule 22.07.2020

Вы указали свои собственные значения tabIndex для каждого элемента, который хотите просмотреть? если да, вы можете попробовать это:

var lasTabIndex = 10; //Set this to the highest tabIndex you have
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    var curIndex = $(currentElement).attr('tabindex'); //get the tab index of the current element
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    $('[tabindex=' + (curIndex + 1) + ']').focus(); //set focus on the element that has a tab index one greater than the current tab index
}

Вы ведь используете jquery?

person Brian Glaz    schedule 26.08.2011
comment
Мы не используем JQuery, поскольку он нарушает работу приложения. : / - person JadziaMD; 26.08.2011
comment
Хорошо, думаю, я могу переписать это без использования jquery, дайте мне минутку - person Brian Glaz; 26.08.2011
comment
Для каждого интересующего нас элемента установлены значения индекса вкладки. - person JadziaMD; 26.08.2011

Небольшой совет: Не пытайтесь контролировать, где находится фокус во время события табуляции. Вместо этого попытайтесь контролировать, какие элементы доступны, а какие нет, установив tabIndex элементов, которые вы не хотите получать в фокус, на -1. Например.

// `tabContainer` is a container where we want only
// element at a time to be tabbable, e.g. a radio menu.

tabContainer.addEventListener("focusin", () => {
  const desired = findDesiredFocusElement();

  if (!desired) {
    // Just leave the focus be. We have no preference
    // at the moment.
    return;
  }

  // Move the focus to the correct element.
  desired.focus();

  // Remove all undesired elements from the tab order.
  for (const undesired of findUndesiredFocusElements()) {
    // Make it untabbable.
    undesired.tabIndex = -1;
  }
});

tabContainer.addEventListener("focusout", (event) => {
  for (const element of findRelevantFocusElements()) {
    // Give each element back their focus capability.
    element.tabIndex = 0;
  }
});

Примечание. Это может быть не лучшим вариантом в вашей ситуации, например в вашем случае может быть лучше контролировать индекс вкладки в некоторых change событиях или нет для сброса состояния tabIndex на focusout и т. д.

Дополнительная информация здесь.

person Rúnar Berg    schedule 18.09.2020

Я использую этот код, который опирается на библиотеку JQuery:

    $(document).on('change', 'select', function () {
    let next_select = $(this);
// console.log(next_select.toArray())
    if (!next_select.parent().parent().next().find('select').length) {
        next_select.parent().parent().parent().next().find('input[type="text"]').click()
        console.log(next_select.parent().parent().parent().next());
    } else if (next_select.parent().parent().next().find('select').prop("disabled")) {
        setTimeout(function () {
            next_select.parent().parent().next().find('select').select2('open')
        }, 1000)
        console.log('b');
    } else if (next_select.parent().parent().next().find('select').length) {
        next_select.parent().parent().next().find('select').select2('open')
        console.log('c');
    }
});
person OmidDarvishi    schedule 06.09.2020
comment
привет, Было бы здорово, если бы вы немного объяснили код, это поможет новым читателям лучше понять. Спасибо - person Sunil Lulla; 06.09.2020

Если вы используете библиотеку JQuery, вы можете вызвать это:

Вкладка:

$.tabNext();

Shift + Tab:

$.tabPrev();

<!DOCTYPE html>
<html>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script>
(function($){
    'use strict';

    /**
     * Focusses the next :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
     * Does not take into account that the taborder might be different as the :tabbable elements order
     * (which happens when using tabindexes which are greater than 0).
     */
    $.focusNext = function(){
        selectNextTabbableOrFocusable(':focusable');
    };

    /**
     * Focusses the previous :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
     * Does not take into account that the taborder might be different as the :tabbable elements order
     * (which happens when using tabindexes which are greater than 0).
     */
    $.focusPrev = function(){
        selectPrevTabbableOrFocusable(':focusable');
    };

    /**
     * Focusses the next :tabable element.
     * Does not take into account that the taborder might be different as the :tabbable elements order
     * (which happens when using tabindexes which are greater than 0).
     */
    $.tabNext = function(){
        selectNextTabbableOrFocusable(':tabbable');
    };

    /**
     * Focusses the previous :tabbable element
     * Does not take into account that the taborder might be different as the :tabbable elements order
     * (which happens when using tabindexes which are greater than 0).
     */
    $.tabPrev = function(){
        selectPrevTabbableOrFocusable(':tabbable');
    };

    function tabIndexToInt(tabIndex){
        var tabIndexInded = parseInt(tabIndex);
        if(isNaN(tabIndexInded)){
            return 0;
        }else{
            return tabIndexInded;
        }
    }

    function getTabIndexList(elements){
        var list = [];
        for(var i=0; i<elements.length; i++){
            list.push(tabIndexToInt(elements.eq(i).attr("tabIndex")));
        }
        return list;
    }

    function selectNextTabbableOrFocusable(selector){
        var selectables = $(selector);
        var current = $(':focus');

        // Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex+1; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return a-b});
        if(currentTabIndex === tabIndexList[tabIndexList.length-1]){
            currentTabIndex = -1;// Starting from 0
        }

        // Find next TabIndex of all element
        var nextTabIndex = tabIndexList.find(function(element){return currentTabIndex<element;});
        for(var i=0; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === nextTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
    }

    function selectPrevTabbableOrFocusable(selector){
        var selectables = $(selector);
        var current = $(':focus');

        // Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return b-a});
        if(currentTabIndex <= tabIndexList[tabIndexList.length-1]){
            currentTabIndex = tabIndexList[0]+1;// Starting from max
        }

        // Find prev TabIndex of all element
        var prevTabIndex = tabIndexList.find(function(element){return element<currentTabIndex;});
        for(var i=selectables.length-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === prevTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
    }

    /**
     * :focusable and :tabbable, both taken from jQuery UI Core
     */
    $.extend($.expr[ ':' ], {
        data: $.expr.createPseudo ?
            $.expr.createPseudo(function(dataName){
                return function(elem){
                    return !!$.data(elem, dataName);
                };
            }) :
            // support: jQuery <1.8
            function(elem, i, match){
                return !!$.data(elem, match[ 3 ]);
            },

        focusable: function(element){
            return focusable(element, !isNaN($.attr(element, 'tabindex')));
        },

        tabbable: function(element){
            var tabIndex = $.attr(element, 'tabindex'),
                isTabIndexNaN = isNaN(tabIndex);
            return ( isTabIndexNaN || tabIndex >= 0 ) && focusable(element, !isTabIndexNaN);
        }
    });

    /**
     * focussable function, taken from jQuery UI Core
     * @param element
     * @returns {*}
     */
    function focusable(element){
        var map, mapName, img,
            nodeName = element.nodeName.toLowerCase(),
            isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex'));
        if('area' === nodeName){
            map = element.parentNode;
            mapName = map.name;
            if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map'){
                return false;
            }
            img = $('img[usemap=#' + mapName + ']')[0];
            return !!img && visible(img);
        }
        return ( /^(input|select|textarea|button|object)$/.test(nodeName) ?
            !element.disabled :
            'a' === nodeName ?
                element.href || isTabIndexNotNaN :
                isTabIndexNotNaN) &&
            // the element and all of its ancestors must be visible
            visible(element);

        function visible(element){
            return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function(){
                return $.css(this, 'visibility') === 'hidden';
            }).length;
        }
    }
})(jQuery);
</script>

<a tabindex="5">5</a><br>
<a tabindex="20">20</a><br>
<a tabindex="3">3</a><br>
<a tabindex="7">7</a><br>
<a tabindex="20">20</a><br>
<a tabindex="0">0</a><br>

<script>
var timer;
function tab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabNext();}, 1000);
}
function shiftTab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabPrev();}, 1000);
}
</script>
<button tabindex="-1" onclick="tab()">Tab</button>
<button tabindex="-1" onclick="shiftTab()">Shift+Tab</button>

</body>
</html>

Я изменяю подключаемый модуль jquery.tabbable для завершения.

person iHad 169    schedule 22.03.2019
comment
Дубликат этого ответа, опубликованный создателем этого подключаемого модуля jQuery. - person mbomb007; 23.01.2020

Я проверил приведенные выше решения и нашел их довольно длинными. Это можно сделать с помощью одной строчки кода:

currentElement.nextElementSibling.focus();

or

currentElement.previousElementSibling.focus();

здесь currentElement может быть любым, например, document.activeElement или this, если текущий элемент находится в контексте функции.

Я отслеживал события табуляции и сдвига табуляции с помощью события нажатия клавиши. Вот фрагмент, основанный на JQuery:

let cursorDirection = ''
$(document).keydown(function (e) {
    let key = e.which || e.keyCode;
    if (e.shiftKey) {
        //does not matter if user has pressed tab key or not.
        //If it matters for you then compare it with 9
        cursorDirection = 'prev';
    }
    else if (key == 9) {
        //if tab key is pressed then move next.
        cursorDirection = 'next';
    }
    else {
        cursorDirection == '';
    }
});

как только у вас есть направление курсора, вы можете использовать методы nextElementSibling.focus или previousElementSibling.focus

person Manpreet Singh Dhillon    schedule 10.01.2019
comment
К сожалению, порядок братьев и сестер не связан с порядком табуляции, за исключением счастливого совпадения, и нет никакой гарантии, что предыдущий / следующий брат будет даже фокусируемым. - person Lawrence Dol; 12.12.2019