Сфокусуйте наступний елемент у вкладці індексу


103

Я намагаюся перемістити фокус на наступний елемент у послідовності вкладки на основі поточного елемента, який має фокус. Поки що я нічого не виявив у своїх пошуках.

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

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

Звичайно, що наступнийElementByTabIndex є ключовою частиною для цього. Як знайти наступний елемент у послідовності вкладок? Рішення повинно базуватися на JScript, а не на кшталт JQuery.


3
навіщо вам ця лінія currentElementId = "";?

1
Я не думаю, що будь-які браузери відкривають інформацію про порядок вкладок - а алгоритм, який використовуються самими браузерами, є надто складним для його копіювання. Можливо, ви можете обмежити свої вимоги, наприклад, "вважати лише input, buttonа textareaтеги і ігнорувати tabindexатрибут".
Володимир Палант

Нам потрібно побачити ваш .newElementByTabIndexкод, тому що це не працює.
0x499602D2

2
Знову ж таки, можливо, обмеження на окремі теги є непотрібним - можна перевірити, чи focus()існує метод.
Володимир Палант

1
@David Це функція, яка не існує, тому моє питання. : D
JadziaMD

Відповіді:


23

Без 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;
        }
    }
}

16
Рішення без додавання імені до кожного елемента (так як багато хто може зробити, якщо це можливо) було б ідеальним.
JadziaMD

3
добре, це для форми? Якщо всі елементи , які ви хочете вхідні елементи, ви можете замінити рядок var tabbables = document.getElementsByName("tabable");з var tabbables = document.getElementsByTagName("input");замість
Брайан GLAZ

var tabbables = document.querySelectorAll("input, textarea, button")// IE8 +, знайдіть посилання на всі таблиці, не змінюючи свій HTML.
Грег

2
class = "tabbable", а не використовувати атрибут імені
Chris F Carroll

4
Зауважте, що за допомогою flexbox порядок елементів відрізняється в DOM, ніж візуально в браузері. Вибір наступного елемента з таблицями не працює, коли ви змінюєте порядок елементів за допомогою flexbox.
Ханєєв

75

Я ніколи цього не реалізовував, але розглядав подібну проблему, і ось, що я спробував би.

Спробуйте це спочатку

По-перше, я побачив би, чи можете ви просто запустити 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.


37

Ось що я будую для цього:

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
  • працює у всіх сучасних браузерах
  • швидкий і легкий

2
Це найефективніше та зручніше для використання рішення. Дякую! Ось мій повний робочий сценарій: stackoverflow.com/a/40686327/1589669
ЕА

Я додав фрагмент нижче, щоб включити сортування за явним TabIndex focusvable.sort (sort_by_TabIndex)
DavB.cs

1
Кращий ! Він повинен бути таким складним: nextElementSiblingможливо, він не може бути орієнтованим, наступний фокусується може не бути рідним братом.
Tinmarino

Хороший підхід, але він повинен передбачати будь-який вхід, який не є типом, hiddenа також охоплює textareaі select.
Lucero

23

Я створив простий плагін jQuery, який робить саме це. Він використовує селектор ": tabbable" інтерфейсу jQuery для пошуку наступного елемента "tabbable" і вибирає його.

Приклад використання:

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

8

Основа відповіді лежить у пошуку наступного елемента:

  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.


3
Що робити, якщо замовлення tabindex суперечить порядку замовлення документа? Я думаю, що масив повинен бути відсортований за номером tabindex, а потім за порядком документів
Chris F Carroll

Так, це було б більш "специфічно". Я не впевнений у
кращих

Що робити, якщо елемент, який не є одним із цих тегів, має атрибут tabindex?
Метт Пеннінгтон

1
@MattPennington Це було б проігноровано. Фільтр - це (спроба) прискорити пошук, сміливо адаптуватися.
Андре Верланг

3

Як згадувалося в коментарі вище, я не думаю, що жоден браузер відкриває інформацію про замовлення вкладок. Тут спрощене наближення того, що робить браузер, щоб отримати наступний елемент у порядку вкладки:

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атрибут, але може бути достатньо залежно від того, що ви намагаєтеся досягти.


3

Здається, ви можете перевірити tabIndexвластивість елемента, щоб визначити, чи він фокусується. Елемент, який не фокусується, має tabindexзначення "-1".

Тоді вам просто потрібно знати правила зупинок вкладок:

  • tabIndex="1" має найвищий пріоритет.
  • tabIndex="2" має наступний найвищий пріоритет.
  • tabIndex="3" є наступним тощо.
  • tabIndex="0" (або за замовчуванням вкладки) має найнижчий пріоритет.
  • 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"в самому кінці списку, після пунктів з tabIndex1, 2, 3 тощо.

На прикладі повністю працюючого, де ви можете розмістити вкладку за допомогою клавіші "enter", ознайомтеся з цією загадкою .


2

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

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

На цей текст (версія 1.1.1), він має застереження, що він не підтримує IE8, і що помилки браузера перешкоджають contenteditableправильному поводженню .


2
function focusNextElement(){
  var focusable = [].slice.call(document.querySelectorAll("a, button, input, select, textarea, [tabindex], [contenteditable]")).filter(function($e){
    if($e.disabled || ($e.getAttribute("tabindex") && parseInt($e.getAttribute("tabindex"))<0)) return false;
    return true;
  }).sort(function($a, $b){
    return (parseFloat($a.getAttribute("tabindex") || 99999) || 99999) - (parseFloat($b.getAttribute("tabindex") || 99999) || 99999);
  });
  var focusIndex = focusable.indexOf(document.activeElement);
  if(focusable[focusIndex+1]) focusable[focusIndex+1].focus();
};

1

Це моя перша публікація на 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()
      }                    
  }
}

Зміна вар на постійну не критична. Основна зміна полягає в тому, що ми позбавляємось від селектора, який перевіряє tabindex! = "-1". Потім, якщо елемент має атрибут tabindex AND він встановлений на "-1", ми НЕ вважаємо його фокусируемим.

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

Я вважаю, що коли автор прийнятої відповіді відредагував свою відповідь на рахунок атрибута tabIndex, вони зробили це не правильно. Будь ласка, дайте мені знати, якщо це не так


1

Є властивість tabindex, яку можна встановити на компоненті. Він визначає, в якому порядку слід вводити вхідні компоненти під час вибору одного та натискання вкладки. Значення вище 0 зарезервовані для користувацької навігації, 0 - у "природному порядку" (так би поводилося інакше, якщо встановлено для першого елемента), -1 означає, що клавіатура не фокусується:

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

Він також може бути встановлений для чогось іншого, ніж поля для введення тексту, але не дуже очевидно, що б там було зроблено, якщо взагалі щось було. Навіть якщо навігація працює, можливо, краще використовувати "природний порядок" для чого-небудь іншого, ніж дуже очевидні елементи введення користувача.

Ні, вам не потрібен JQuery або будь-який сценарій взагалі для підтримки цього користувальницького шляху навігації. Ви можете реалізувати його на стороні сервера без будь-якої підтримки JavaScript. З іншого боку, властивість також чудово працює в рамках React, але цього не вимагає.


0

Ось більш повна версія зосередження уваги на наступному елементі. Він дотримується вказівок специфікації та правильно сортує список елементів за допомогою 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]);
}

0

Це потенційне покращення чудового рішення, яке пропонували @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);

0

Ви можете зателефонувати так:

Вкладка:

$.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 для завершення.


Дублікат цієї відповіді , який опублікував автор цього плагіна jQuery.
mbomb007

0

Некромантування.
У мене є сукупність 0-tabIndexes, якими я хотів орієнтуватися по клавіатурі.
Оскільки в цьому випадку мав значення лише ЗАМОВЛЕННЯ елементів, я це робив, використовуючи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;
}

-1

Ви вказали власні значення 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, правда?


Ми не використовуємо JQuery, оскільки він порушує програму. : /
JadziaMD

Гаразд, я думаю, що можу переписати це, не використовуючи jquery, дайте хвилинку
Брайан Глаз

Для кожного елемента, який нас цікавить, встановлено значення індексу вкладки.
JadziaMD

-1

Я перевірив вищезазначені рішення та виявив їх досить тривалими. Це може бути виконано лише одним рядком коду:

currentElement.nextElementSibling.focus();

або

currentElement.previousElementSibling.focus();

тут currentElement може бути будь-яким, тобто document.activeElement або цим, якщо поточний елемент знаходиться в контексті функції.

Я відслідковував події вкладки та зсуву з подіями клавіш

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методи


1
На жаль, порядок рідних братів не пов'язаний з порядком вкладки, за винятком щасливого збігу обставин, і немає гарантії, що попередній / наступний брат навіть буде орієнтованим.
Лоуренс Дол
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.