Reputation: 83
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>
Upvotes: 6
Views: 18008
Reputation: 28992
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...
active
, that will track the currently focussed input.active
if the user focusses an input by tabbing or clicking.active
to determine the next or previous element in your list and call focus()
on it.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 component, 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;
}
Upvotes: 0
Reputation: 127
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()
Upvotes: 2
Reputation: 552
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();
}
});
});
Upvotes: 3
Reputation: 318182
The 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
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.
Upvotes: 3