Focus Next Element In Tab Index

后端 未结 20 1923
失恋的感觉
失恋的感觉 2020-11-27 03:06

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

相关标签:
20条回答
  • 2020-11-27 03:31

    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);
    
    0 讨论(0)
  • 2020-11-27 03:31

    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.

    0 讨论(0)
  • 2020-11-27 03:31

    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

    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

    0 讨论(0)
  • 2020-11-27 03:33

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

    Try this first

    First, I would see if you could simply fire a keypress event for the Tab key on the element that currently has focus. There may be a different way of doing this for different browsers.

    If that doesn't work, you'll have to work harder…

    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.

    0 讨论(0)
  • 2020-11-27 03:33

    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;
    });
    
    0 讨论(0)
  • 2020-11-27 03:33

    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.

    0 讨论(0)
提交回复
热议问题