I have a webform and want the users to be able to go to the next tabindex field when the down arrow is pressed (behave like the tab key). The following code works when enter key is pressed but if I change the keycode to 40 this code will not work for the down arrow key.
Any help greatly appreciated.
<div>
<input name="line[]" class="" type="text" tabindex="1"/>
</div>
<div>
<input name="line[]" class="" type="text" tabindex="2"/>
</div>
<div>
<input name="line[]" class="" type="text" tabindex="3"/>
</div>
//tab to next tabindex on enter key
<script>
var id;
$(document).ready(function(eOuter) {
$('input').bind('keypress', function(eInner) {
if (eInner.keyCode == 13){ //enter key
var tabindex = $(this).attr('tabindex');
tabindex++; //increment tabindex
$('[tabindex=' + tabindex + ']').focus();
$('#Msg').text($(this).attr('id') + " tabindex: " + tabindex + " next element: " + $('*').attr('tabindex').id);
}
});
});
</script>
I have a webform and want the users to be able to go to the next tabindex field when the down arrow is pressed (behave like the tab key). The following code works when enter key is pressed but if I change the keycode to 40 this code will not work for the down arrow key.
Any help greatly appreciated.
<div>
<input name="line[]" class="" type="text" tabindex="1"/>
</div>
<div>
<input name="line[]" class="" type="text" tabindex="2"/>
</div>
<div>
<input name="line[]" class="" type="text" tabindex="3"/>
</div>
//tab to next tabindex on enter key
<script>
var id;
$(document).ready(function(eOuter) {
$('input').bind('keypress', function(eInner) {
if (eInner.keyCode == 13){ //enter key
var tabindex = $(this).attr('tabindex');
tabindex++; //increment tabindex
$('[tabindex=' + tabindex + ']').focus();
$('#Msg').text($(this).attr('id') + " tabindex: " + tabindex + " next element: " + $('*').attr('tabindex').id);
}
});
});
</script>
Share
Improve this question
asked Sep 27, 2016 at 20:32
user3058870user3058870
831 gold badge2 silver badges8 bronze badges
1
- Do not use tabindex values of 1 or greater to change the default keyboard navigation order. as per webaim/techniques/keyboard/#problems – Mohsin Saeed Commented Mar 10, 2020 at 11:31
4 Answers
Reset to default 3The arrow keys don't fire consistently on the keypress
event, you want to use keydown
instead
$('input').on('keydown', function(eInner) {
if (eInner.which === 40) { //arrow down
FIDDLE
Your message has some issues as well, the elements don't have ID's, jQuery's attr
returns a primitive that has no id
property etc.
100% working for up arrow
and down arrow
$(document).ready(function(eOuter) {
$('input').on('keydown', function(eInner) {
var keyValue = eInner.which; //enter key
if (keyValue == 40 || keyValue == 38){
var tabindex = $(this).attr('tabindex');
if(keyValue == 40){ //down arrow 40
tabindex++;
}else{ //up arrow 38
tabindex--;
}
$('[tabindex=' + tabindex + ']').focus();
}
});
});
For anyone who wonders how this work in React/Typescript codes, here is a code snippet that works for me (to allow using ArrowDown and ArrowUp keys to achieve the effect of tab through a ul list):
<ul>
{props.suggestions.map((o, i, a) => (
<li
key={o.id}
tabIndex={props.tabIndex}
onClick={(e) => (props.onSelect ? props.onSelect(o) : undefined)}
onBlur={props.onBlur}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (props.onSelect) props.onSelect(o);
}
if (e.key === 'ArrowDown') {
e.currentTarget.nextSibling && e.currentTarget.nextSibling.focus();
}
if (e.key === 'ArrowUp') {
e.currentTarget.previousSibling && e.currentTarget.previousSibling.focus();
}
}}
/>
))}
</ul>
The key is e.currentTarget.nextSibling.focus() and e.currentTarget.previousSibling.focus()
This was harder than I thought, but if it's important to you, as it is to us, here's what you need to do...
- walk the dom to create and maintain your own ordered list of elements that can receive focus.
- create a variable,
active
, that will track the currently focussed input. - listen for focus events so you can update the value of
active
if the user focusses an input by tabbing or clicking. - listen for the arrow keys, and use
active
to determine the next or previous element in your list and callfocus()
on it. - blur. Tabbing resets to zero if the user clicks outside an input. If you want this for arrow keys, you'll have to add it. (The code below doesn't manage this)
The code below does all this (except the last point) in react. It should be easily adaptable to any other setting. It builds its list once, 500ms after it's called. This is adequate for us for the moment, but if you're adding and removing inputs from your DOM, you'll need to manage this yourself.
import { useEffect, useRef, useState } from "react";
import { IHasHistory, withHistory } from "./traceableEvents";
function addArrowNavigation() {
function onFocus(e: FocusEvent & IHasHistory) {
if (!e.history.includes("focusManager.OnFocus")) {
const activeIndex = inputsRef.current.findIndex((anInput) => anInput === e.target);
setActive(activeIndex);
console.log(activeIndex + " active");
e.history.push("focusManager.OnFocus");
}
}
// stores list of all elements in the page that
// can receive focus, in order of their appearance.
const [inputs, setInputs] = useState([]);
const inputsRef = useRef(inputs)
useEffect(() => { inputsRef.current = inputs })
// stores the currently focussed input
const [active, setActive] = useState<number | undefined>(undefined);
const activeRef = useRef(active)
useEffect(() => { activeRef.current = active })
function registerTabbable(doc: Document) {
const inputs = [];
function traverse(el: HTMLElement) {
if (el.tabIndex >= 0) {
inputs.push(el);
}
if (el.childNodes) {
for (const node of el.childNodes)
if (node instanceof HTMLElement)
traverse(node);
}
}
for (const node of doc.childNodes)
if (node instanceof HTMLElement)
traverse(node);
console.log(inputs);
setInputs(inputs);
}
useEffect(() => {
document.addEventListener("keydown", keyPress);
document.addEventListener("focusin", (e) =>
onFocus(withHistory(e))
);
setTimeout(() => {
registerTabbable(document);
}, 500);
// Don't forget to clean up
return function cleanup() {
document.removeEventListener("keydown", keyPress);
};
}, []);
const keyPress = (e: KeyboardEvent) => {
console.log(e.keyCode);
if ([38, 40].includes(e.keyCode)) e.preventDefault();
switch (e.keyCode) {
// DOWN ARROW
case 40: {
const goTo = activeRef.current === undefined ? 0 : (activeRef.current + 1) % inputsRef.current.length
inputsRef.current[goTo].focus();
break;
}
// UP ARROW
case 38: {
const goTo = activeRef.current === undefined ? 0 : (activeRef.current - 1) % inputsRef.current.length
inputsRef.current[goTo].focus();
break;
}
}
}
}
The traceableEvents ponent, referenced above, is as follows...
export interface IHasHistory {
history: string[];
}
export function withHistory<TargetShape>(target: any): TargetShape & IHasHistory {
if (!target.history) {
(target as IHasHistory).history = [];
}
return target;
}