Focus Next Element In Tab Index
Asked Answered
C

23

157

I am trying to move the focus to the next element in the tab sequence based upon the current element which has focus. Thus far I have not turned up anything in my searches.

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

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

Of course the nextElementByTabIndex is the key part for this to work. How do I find the next element in the tab sequence? The solution would need to be based using JScript and not something like JQuery.

Christophe answered 26/8, 2011 at 17:4 Comment(7)
why do you have this line currentElementId = ""; ?Treatise
I don't think that any browsers expose the tab order information - and the algorithm used by the browsers themselves is too complicated to replicate. Maybe you can restrict your requirements, e.g. "consider only input, button and textarea tags and ignore tabindex attribute".Curassow
We need to see your .newElementByTabIndex code because that's whats not working.Neural
Then again, maybe the restriction to particular tags is unnecessary - one can check whether the focus() method exists.Curassow
@Omeid Herat It's there to prevent other functions from accidentally using it at a time when they shouldn't.Christophe
@David That's the function that doesn't exist, therefore my question. :DChristophe
I’ve been coming back to this question with similar issues frequently now and none of the answers here exactly right for me. What I’ve found out is that dealing with the focus during tab events is hard. Usually this can be solved by changing the tabIndex of the elements you don’t want to be tabbed into to -1 when the tab order is relevant, and then back to 0 when the tab order is not relevant any more.Norris
P
29

Without jquery: First of all, on your tab-able elements, add class="tabable" this will let us select them later. (Do not forget the "." class selector prefix in the code below)

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;
        }
    }
}
Pendentive answered 26/8, 2011 at 17:51 Comment(6)
A solution without having to add a name to every element (as there are way to many to make if feasible) would be ideal.Christophe
ok, is this for a form? If all of the elements you want are input elements, you can replace the line var tabbables = document.getElementsByName("tabable"); with var tabbables = document.getElementsByTagName("input"); insteadPendentive
var tabbables = document.querySelectorAll("input, textarea, button") // IE8+ , get a reference to all tabbables without modifying your HTML.Weimaraner
class="tabbable" rather than use the name attributeRomish
@ChrisFCarroll is right - especially if this is in a form, you need different names for each form element b/c that's what gets passed when the form is submitted. So use class="tabbable", not name="tabbable".Clayborne
Note that using flexbox the order of the elements are different in the DOM than visually in the browser. Just picking the next tabbable element does not work when you change the order of elements using flexbox.Sherborn
P
90

I've never implemented this, but I've looked into a similar problem, and here's what I would try.

Referencing the jQuery implementation, you must:

  1. Listen for Tab and Shift+Tab
  2. Know which elements are tab-able
  3. Understand how tab order works

1. Listen for Tab and Shift+Tab

Listening for Tab and Shift+Tab are probably well-covered elsewhere on the web, so I'll skip that part.

2. Know which elements are tab-able

Knowing which elements are tab-able is trickier. Basically, an element is tab-able if it is focusable and does not have the attribute tabindex="-1" set. So then we must ask which elements are focusable. The following elements are focusable:

  • input, select, textarea, button, and object elements that aren't disabled.
  • a and area elements that have an href or have a numerical value for tabindex set.
  • any element that has a numerical value for tabindex set.

Furthermore, an element is focusable only if:

  • None of its ancestors are display: none.
  • The computed value of visibility is visible. This means that the nearest ancestor to have visibility set must have a value of visible. If no ancestor has visibility set, then the computed value is visible.

More details are in another Stack Overflow answer.

3. Understand how tab order works

The tab order of elements in a document is controlled by the tabindex attribute. If no value is set, the tabindex is effectively 0.

The tabindex order for the document is: 1, 2, 3, …, 0.

Initially, when the body element (or no element) has focus, the first element in the tab order is the lowest non-zero tabindex. If multiple elements have the same tabindex, you then go in document order until you reach the last element with that tabindex. Then you move to the next lowest tabindex and the process continues. Finally, finish with those elements with a zero (or empty) tabindex.

Pamphleteer answered 24/5, 2012 at 2:21 Comment(3)
82 upvotes without a single comment to indicate which of the suggested solutions actually works?Heshvan
I spent in vain the better half of a day to dispatch a Tab key event to move the focus. So I don't know if you just can't fool the browser into moving the keyboard focus via synthetic DOM events or if you could but I just didn't do it right. In the end my solution was to get a reference to each of those UI elements I want to move the focus between, put them in an array, and move focus from entry to entry when the user uses ArrowUp and ArrowDown. Was good enough for my usecase.Permanency
Thanks, @Martin. After your comment, I tried dispatching synthetic events myself and couldn't get it to work, so I removed that part of the answer. 👍🏼Pamphleteer
E
76

Here's something I build for this purpose:

function focusNextElement() {
  //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();
    }
  }
}

Features:

  • configurable set of focusable elements
  • no jQuery needed
  • works in all modern browsers
  • fast & lightweight
Expiratory answered 3/2, 2016 at 9:45 Comment(5)
This is the the most effective and resource-friendly solution. Thank you! Here is my full working script: https://mcmap.net/q/55781/-focus-on-next-tabindex-of-html-element-onenter-keypress-by-jqueryFagan
I added a snippet below to include sorting by explicit TabIndex focussable.sort(sort_by_TabIndex)Xanthine
The best ! It must be that complex: the nextElementSibling may be not focusable, the next focusable may not be a sibling.Manila
Good approach, but it should allow for any input which is not of type hidden and also cover textarea and select.Torey
There's a lot of solution to simulate a "Tab", but actually the real answer is here.Flamenco
P
29

Without jquery: First of all, on your tab-able elements, add class="tabable" this will let us select them later. (Do not forget the "." class selector prefix in the code below)

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;
        }
    }
}
Pendentive answered 26/8, 2011 at 17:51 Comment(6)
A solution without having to add a name to every element (as there are way to many to make if feasible) would be ideal.Christophe
ok, is this for a form? If all of the elements you want are input elements, you can replace the line var tabbables = document.getElementsByName("tabable"); with var tabbables = document.getElementsByTagName("input"); insteadPendentive
var tabbables = document.querySelectorAll("input, textarea, button") // IE8+ , get a reference to all tabbables without modifying your HTML.Weimaraner
class="tabbable" rather than use the name attributeRomish
@ChrisFCarroll is right - especially if this is in a form, you need different names for each form element b/c that's what gets passed when the form is submitted. So use class="tabbable", not name="tabbable".Clayborne
Note that using flexbox the order of the elements are different in the DOM than visually in the browser. Just picking the next tabbable element does not work when you change the order of elements using flexbox.Sherborn
A
28

I created a simple jQuery plugin which does just this. It uses the ':tabbable' selector of jQuery UI to find the next 'tabbable' element and selects it.

Example usage:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});
Andaman answered 11/9, 2013 at 19:41 Comment(2)
This is by far the best solution.Rola
A short preview for those that don't want to install a plug in: var selectables = $(':focusable'); var currentIndex = selectables.index($(':focus')); selectables.eq(currentIndex + 1).focus(); The plugin has error checking and more options.Rola
C
15

The core of the answer lies on finding the next element:

  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];
  }

Usage:

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

Notice I don't care about prioritizing tabIndex.

Copacetic answered 26/4, 2015 at 20:32 Comment(5)
What if the tabindex order goes against the document order? I think the array has to be sorted by tabindex number then by document orderRomish
Yep, that would be more "spec-compliant". I'm not sure about edge-cases, regarding parent elements, etcAelber
What if an item that isn't one of those tags has a tabindex attribute?Concoct
@MattPennington It would be ignored. The filter is (an attempt) to speed up the search, feel free to adapt.Aelber
This is honestly the best path forward. It's simple and while it does not cure the internet of it's problems, it will allow you to tab to the "nearest" focusable element. We're not covering EVERY scenario and that is perfectly fine. If the example here isn't enough, then modify it to your liking. Also, DOM ordering is king here. Quadruple check your DOM structure.Amylase
R
6

It seems that you can check the tabIndex property of an element to determine if it is focusable. An element that is not focusable has a tabindex of "-1".

Then you just need to know the rules for tab stops:

  • tabIndex="1" has the highest priorty.
  • tabIndex="2" has the next highest priority.
  • tabIndex="3" is next, and so on.
  • tabIndex="0" (or tabbable by default) has the lowest priority.
  • tabIndex="-1" (or not tabbable by default) does not act as a tab stop.
  • For two elements that have the same tabIndex, the one that appears first in the DOM has the higher priority.

Here is an example of how to build the list of tab stops, in sequence, using pure 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]);
}

We first walk the DOM, collecting up all tab stops in sequence with their index. We then assemble the final list. Notice that we add the items with tabIndex="0" at the very end of the list, after the items with a tabIndex of 1, 2, 3, etc.

For a fully working example, where you can tab around using the "enter" key, check out this fiddle.

Rai answered 13/11, 2013 at 2:40 Comment(0)
J
6

Tabbable is a small JS package that gives you a list of all tabbable elements in tab order. So you could find your element within that list, then focus on the next list entry.

The package correctly handles the complicated edge cases mentioned in other answers (e.g., no ancestor can be display: none). And it doesn't depend on jQuery!

As of this writing (version 1.1.1), it has the caveats that it doesn't support IE8, and that browser bugs prevent it from handling contenteditable correctly.

Jovi answered 23/11, 2017 at 6:51 Comment(1)
See my post for a copy-paste solution using the tabbable package: https://mcmap.net/q/56621/-focus-next-element-in-tab-indexMechanize
C
3

As mentioned in a comment above, I don't think that any browsers expose tab order information. Here a simplified approximation of what the browser does to get the next element in tab order:

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

This only considers some tags and ignores tabindex attribute but might be enough depending on what you are trying to achieve.

Curassow answered 26/8, 2011 at 18:22 Comment(0)
R
2

Here is a more complete version of focusing on the next element. It follows the spec guidelines and sorts the list of elements correctly by using tabindex. Also a reverse variable is defined if you want to get the previous element.

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]);
}
Relieve answered 15/12, 2017 at 22:54 Comment(1)
It may not be obvious to everyone how to use this script. focusNextElement(false).focus();Ta
T
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();
};
Turin answered 23/4, 2019 at 19:3 Comment(0)
P
2

Simplest TAB loop!

// Give the focus onload. (The autofocus attribute does not work on div(s))
document.querySelector('[tabindex]').focus()

document.addEventListener("keydown", (e) => {
  e.preventDefault() // Very important here
  if (document.activeElement && e.code === "Tab") {
    let e = [...document.querySelectorAll('[tabindex]')],
        i = e.indexOf(document.activeElement) + 1;
    i = i === e.length ? 0 : i;
    e[i].focus()
  }
})
<div tabindex="0">Try to</div>
<div tabindex="1">tap, or long press</div>
<div tabindex="2">the TAB key!</div>
Perigon answered 13/4, 2022 at 2:48 Comment(2)
How about catering for the Shift-TAB to go backwards in the sequence?Immunotherapy
Using variable e for event and element array is confusing. Also preventDefault only if TAB is pressed?Immunotherapy
B
1

This is my first post on SO, so I don't have enough reputation to comment the accepted answer, but I had to modify the code to the following:

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

The changing of var to constant is non-critical. The main change is that we get rid of the selector that checks tabindex != "-1". Then later, if the element has the attribute tabindex AND it is set to "-1", we do NOT consider it focussable.

The reason I needed to change this was because when adding tabindex="-1" to an <input>, this element was still considered focussable because it matches the "input[type=text]:not([disabled])" selector. My change is equivalent to "if we are a non-disabled text input, and we have a tabIndex attribute, and the value of that attribute is -1, then we should not be considered focussable.

I believe that when the author of the accepted answer edited their answer to account for the tabIndex attribute, they did not do so correctly. Please let me know if this is not the case

Bootle answered 28/11, 2017 at 0:22 Comment(0)
U
1

There is the tabindex property that can be set on component. It specifies in which order the input components should be iterated when selecting one and pressing tab. Values above 0 are reserved for custom navigation, 0 is "in natural order" (so would behave differently if set for the first element), -1 means not keyboard focusable:

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

It can also be set for something else than the text input fields but it is not very obvious what it would do there, if anything at all. Even if the navigation works, maybe better to use "natural order" for anything else than the very obvious user input elements.

No, you do not need JQuery or any scripting at all to support this custom path of navigation. You can implement it on the server side without any JavaScript support. From the other side, the property also works fine in React framework but does not require it.

Uzzi answered 20/4, 2020 at 16:15 Comment(0)
X
0

This is a potential enhancement to the great solution that @Kano and @Mx offered. If you want to preserve TabIndex ordering, add this sort in the middle:

// 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);
Xanthine answered 7/3, 2019 at 18:32 Comment(0)
G
0

I have a buch of 0-tabIndexes, which I wanted to navigate by keyboard.
Since in that case, only the ORDER of the elements mattered, I did it using document.createTreeWalker

So first you create the filter (you only want [visible] elements, which have an attribute "tabIndex" with a NUMERICAL value.

Then you set the root node, beyond which you don't want to search. In my case, this.m_tree is a ul-element containing a toggable tree. If you want the entire document instead, just replace this.m_tree with document.documentElement.

Then you set the current node to the current active element:

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

Then you return ni.nextNode() or ni.previousNode().

Note:
this will NOT return the tabs in the correct order if you have tabIndices != 0 and the element order is NOT the tabIndex order. In case of tabIndex = 0, the tabOrder is always the element order, which is why this works (in that case).

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;
}

Note that the while-loop

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
}

is only there if you want if you have additional criterias which you cannot filter in the filter passed to createTreeWalker.

Note this is TypeScript, you need to remove all tokens behind colons (:), and between angle-brackets (<>), e.g. <Element> or :(node: Node) => number to get valid JavaScript.

Here as a service, the transpiled 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;
}
Gail answered 22/7, 2020 at 14:16 Comment(0)
D
0

A word of advice: Don’t try to control where the focus lands during a tab event. Instead try to control which element are and aren’t tabbable by setting the tabIndex of the elements you don’t want to receive focus to -1. E.g.

// `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;
  }
});

Note: This might not be what is best in your situation, e.g. in your case it might be better to control the tab index in some change events or not to reset the tabIndex state on focusout etc.

More info here.

Drover answered 18/9, 2020 at 22:38 Comment(0)
F
0
function focusNext() {
  var query = '[tabindex]';
  if (document.activeElement) {
    var elements = [...document.querySelectorAll(query)]
    var index = elements.indexOf(document.activeElement);
    index++
    if (index == elements.length) index = 0
    elements[index].focus()
  }
}

If you want to target other elements, you can modify the query to refine the elements.

Flamenco answered 13/2, 2022 at 0:10 Comment(0)
M
0

Here is my working solution using the tabbable library in my React app to make 'Enter' behave like tab

handleKeyDown={(e) => {
    if (e.key === 'Enter') {
        const fElts = tabbable(document.documentElement);
        const currEltIndex = fElts.findIndex(
            (e) => e === document.activeElement,
        );
        const nextElt = fElts[currEltIndex + (e.shiftKey ? -1 : 1)];
        nextElt?.focus();
    }
}}
Mechanize answered 7/12, 2022 at 11:43 Comment(0)
P
-1

Did you specify your own tabIndex values for each element you want to cycle through? if so, you can try this:

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
}

You are using jquery, right?

Pendentive answered 26/8, 2011 at 17:33 Comment(3)
We are not using JQuery as it breaks the application. :/Christophe
Ok, I think I can rewrite is without using jquery, give me a minutePendentive
Every element we're interested in does have their tab index values set.Christophe
B
-1

You just need to change the tab index. The tabindex global attribute indicates that its elements can be focused, and where it participates in sequential keyboard navigation (usually with the Tab key, hence the name).

<p>Click anywhere in this pane, then try tabbing through the elements.</p>

<label>First in tab order:<input type="text"></label>

<div tabindex="0">Tabbable due to tabindex.</div>

<div>Not tabbable: no tabindex.</div>

<label>Third in tab order:<input type="text"></label>

know more

Bagwell answered 22/10, 2021 at 3:48 Comment(0)
A
-4

I checked above solutions and found them quite lengthy. It can be accomplished with just one line of code:

currentElement.nextElementSibling.focus();

or

currentElement.previousElementSibling.focus();

here currentElement may be any i.e. document.activeElement or this if current element is in function's context.

I tracked tab and shift-tab events with keydown event Here is a snippet that relies on "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 == '';
    }
});

once you have cursor direction then you can use nextElementSibling.focus or previousElementSibling.focus methods

Allred answered 10/1, 2019 at 23:39 Comment(1)
Unfortunately sibling order is not related to tab order, except by happy coincidence, and there's no guarantee the the prev/next sibling will even be focusable.Deboer
N
-4

If you use the library "JQuery", you can call this:

Tab:

$.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>

I modify jquery.tabbable PlugIn to complete.

Nonmetallic answered 22/3, 2019 at 10:20 Comment(1)
Duplicate of this answer, which was posted by the creator of that jQuery plugin.Toady
I
-5

i use this code, which relies on the library "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');
    }
});
Infantilism answered 6/9, 2020 at 12:38 Comment(1)
hi, It would be great if you explain the code a bit, it will help new readers to understand better. ThanksMeghanmeghann

© 2022 - 2024 — McMap. All rights reserved.