最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Tab to next tabindex field on down arrow key - Stack Overflow

programmeradmin2浏览0评论

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
Add a ment  | 

4 Answers 4

Reset to default 3

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

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 call focus() 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;
}
发布评论

评论列表(0)

  1. 暂无评论