在标签索引中关注下一个元素

时间:2011-08-26 17:04:59

标签: javascript jscript.net

我正在尝试根据具有焦点的当前元素将焦点移动到制表符序列中的下一个元素。到目前为止,我的搜索中没有任何内容。

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

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

当然nextElementByTabIndex是这个工作的关键部分。如何在标签序列中找到下一个元素?解决方案需要使用JScript而不是JQuery。

21 个答案:

答案 0 :(得分:60)

我从来没有实现过这个,但我已经研究过类似的问题了,这就是我想要的。

首先尝试

首先,我会看到你是否可以简单地fire a keypress event获取当前具有焦点的元素上的Tab键。对于不同的浏览器,可能有不同的方法。

如果这不起作用,你将不得不更加努力......

引用jQuery实现,您必须:

  1. 收听标签和Shift +标签
  2. 了解哪些元素可以标记
  3. 了解标签顺序的工作原理
  4. 1。听取Tab和Shift + Tab

    倾听Tab和Shift + Tab可能在网络上的其他地方都有很好的涵盖,所以我会跳过那部分。

    2。知道哪些元素是可以标记的

    了解哪些元素可以制表是比较棘手的。基本上,如果元素是可聚焦的并且没有设置属性tabindex="-1",则该元素是可制表的。那么我们必须要问哪些要素是可集中的。以下要素是可关注的:

    • inputselecttextareabuttonobject元素未被禁用。
    • aarea元素,其hreftabindex设置的数值。
    • 具有tabindex设置数值的任何元素。

    此外,只有在以下情况下才能聚焦元素:

    • 它的祖先都不是display: none
    • visibility的计算值为visible。这意味着设置visibility的最近祖先必须具有值visible。如果没有祖先设置visibility,则计算值为visible

    更多细节在另一个Stack Overflow answer中。

    3。了解Tab键顺序的工作方式

    文档中元素的Tab键顺序由tabindex属性控制。如果未设置任何值,则tabindex实际上为0

    文档的tabindex顺序为:1,2,3,...,0。

    最初,当body元素(或没有元素)具有焦点时,Tab键顺序中的第一个元素是最低的非零tabindex。如果多个元素具有相同的tabindex,那么您将按文档顺序进行操作,直到找到具有该tabindex的最后一个元素。然后移动到下一个最低tabindex并继续该过程。最后,使用零(或空)tabindex完成这些元素。

答案 1 :(得分:23)

这是我为此目的而建立的东西:

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

特点:

  • 可配置的可聚焦元素集
  • 不需要jQuery
  • 适用于所有现代浏览器
  • 快速&轻质

答案 2 :(得分:21)

我创建了一个simple jQuery plugin就是这样做的。它使用jQuery UI的':tabbable'选择器来查找下一个'tabbable'元素并选择它。

使用示例:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});

答案 3 :(得分:19)

没有jquery: 首先,在您的可标签元素上添加class="tabable",我们稍后会选择它们。 (不要忘记下面代码中的“。”类选择器前缀)

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

答案 4 :(得分:6)

答案的核心在于找到下一个元素:

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

用法:

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

注意我不关心tabIndex的优先级。

答案 5 :(得分:2)

正如上面的评论所述,我认为任何浏览器都不会公开Tab键顺序信息。这里简化了浏览器以Tab键顺序获取下一个元素的操作:

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

这只考虑了一些标签而忽略了tabindex属性,但可能已经足够,具体取决于你想要实现的目标。

答案 6 :(得分:1)

这是我关于SO的第一篇文章,所以我没有足够的声誉来评论接受的答案,但我不得不将代码修改为以下内容:

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

将var更改为常量是非关键的。主要的变化是我们摆脱了检查tabindex!=“ - ”的选择器。然后,如果元素具有属性tabindex并且它设置为“-1”,我们不认为它是可聚焦的。

我需要更改这个的原因是因为在将{tabindex =“ - 1”添加到<input>时,此元素仍然被认为是可聚焦的,因为它匹配“input [type = text]:not([disabled] ])“选择器。我的更改等同于“如果我们是一个非禁用的文本输入,并且我们有一个tabIndex属性,并且该属性的值为-1,那么我们不应该被认为是可聚焦的。

我相信,当接受的答案的作者编辑了他们对tabIndex属性的说明的答案时,他们没有正确地这样做。如果不是这样,请告诉我

答案 7 :(得分:1)

可以在组件上设置tabindex属性。它指定了选择一个组件并按Tab时应按哪些顺序迭代输入组件。大于0的值保留用于自定义导航,0为“自然顺序”(因此,如果为第一个元素设置,其行为会有所不同),-1表示键盘无法聚焦:

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

还可以为文本输入字段以外的其他内容设置它,但是如果有的话,它在此处执行的操作不是很明显。即使导航有效,将“自然顺序”用于其他任何事物也可能比非常明显的用户输入元素更好。

否,您完全不需要JQuery或任何脚本来支持此自定义导航路径。您可以在服务器端实现它,而无需任何JavaScript支持。另一方面,该属性在React框架中也可以正常工作,但不需要。

答案 8 :(得分:1)

我检查了上述解决方案,发现它们很长。 只需一行代码即可完成:

currentElement.nextElementSibling.focus();

currentElement.previousElementSibling.focus();

此处currentElement可以是任何文档,即document.activeElement;如果当前元素在函数的上下文中,则可以为this。

我使用keydown事件跟踪选项卡和Shift-tab事件

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 == '';
    }
});

一旦您有了光标方向,便可以使用nextElementSibling.focuspreviousElementSibling.focus方法

答案 9 :(得分:1)

Tabbable是一个小型JS包,它为您提供了一个列表,列出了所有可列表元素,以Tab键顺序。因此,您可以在该列表中找到您的元素,然后专注于下一个列表条目。

包正确处理其他答案中提到的复杂边缘情况(例如,没有祖先可以是display: none)。它并不依赖于jQuery!

在撰写本文时(版本1.1.1),它有一些警告,它不支持IE8,浏览器错误阻止它正确处理contenteditable

答案 10 :(得分:1)

您似乎可以检查元素的tabIndex属性以确定它是否可以对焦。一个不可聚焦的元素的tabindex为“-1”。

然后你只需要知道制表位的规则:

  • tabIndex="1"的成绩最高。
  • tabIndex="2"具有次优先级。
  • 接下来是
  • tabIndex="3",依此类推。
  • tabIndex="0"(默认情况下为tabbable)优先级最低。
  • tabIndex="-1"(默认情况下不会列表)不会作为制表位。
  • 对于具有相同tabIndex的两个元素,DOM中首先出现的元素具有更高的优先级。

以下是如何使用纯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]);
}

我们首先走DOM,按索引收集所有制表位。然后我们组装最终列表。请注意,我们在列表的最后添加tabIndex="0"的项目,在tabIndex为1,2,3等的项目之后添加

有关完整的工作示例,您可以使用“回车”键进行选项卡,请查看此fiddle

答案 11 :(得分:0)

您是否为要循环的每个元素指定了自己的tabIndex值? 如果是这样,你可以试试这个:

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
}

你正在使用jquery,对吧?

答案 12 :(得分:0)

这是关注下一个元素的更完整版本。它遵循规范指南,并使用tabindex对元素列表进行正确排序。如果要获取前一个元素,还会定义一个反向变量。

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

答案 13 :(得分:0)

希望这有用。

<input size="2" tabindex="1" id="one"
  maxlength="2" onkeyup="toUnicode(this)" />

<input size="2" tabindex="2" id="two"
  maxlength="2" onkeyup="toUnicode(this)" />

<input size="2" tabindex="3" id="three"
 maxlength="2" onkeyup="toUnicode(this)" />

然后使用简单的javascript

function toUnicode(elmnt)
{
  var next;
 if (elmnt.value.length==elmnt.maxLength)
{
next=elmnt.tabIndex + 1;
//look for the fields with the next tabIndex
var f = elmnt.form;
for (var i = 0; i < f.elements.length; i++)
{
  if (next<=f.elements[i].tabIndex)
  {
    f.elements[i].focus();
    break;
    }
   }
  }
}

答案 14 :(得分:0)

这是对 @Kano @Mx 提供的出色解决方案的潜在增强。如果要保留TabIndex的顺序,请在中间添加以下排序:

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

答案 15 :(得分:0)

您可以这样称呼:

标签:

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
    If Target.Column = 2 and Target.Row>2 Then 'after a value changes in B column
        Application.EnableEvents = False ' to prevent this change from being seen as another change and causing a loop
        Target.EntireRow.Cut
        sh.Range("A2").EntireRow.Insert Shift:=xlDown 'A2 because I'm assuming you have a header in your first row
        Application.EnableEvents = True 'next sheet changes will cause this to run again
    End If
End Sub

Shift + Tab:

plot(1, 1, xlab= expression("Scattering factor,"~italic(q)~paste("(1/",italic(Å),")")))

$.tabNext();

我修改了jquery.tabbable插件以完成。

答案 16 :(得分:0)

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

答案 17 :(得分:0)

不断创新。
我有大量的0-tabIndexes,我想通过键盘进行导航。
因为在那种情况下,只有元素的ORDER才重要,所以我使用Page 1

因此,首先创建过滤器(您只需要[可见]元素,这些元素的属性“ tabIndex”具有NUMERICAL值。

然后,您设置根节点,您不想在该根节点上进行搜索。 在我的情况下,document.createTreeWalker是包含可切换树的ul元素。 如果您想要整个文档,只需将this.m_tree替换为this.m_tree

然后将当前节点设置为当前活动元素:

document.documentElement

然后您返回ni.currentNode = el; // el = document.activeElement ni.nextNode()

注意:
如果您具有tabIndices!= 0并且元素顺序不是tabIndex顺序,则这将不会以正确的顺序返回选项卡。如果tabIndex = 0,则tabOrder始终是元素顺序,因此(在这种情况下)它是可行的。

ni.previousNode()

请注意,while循环

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

仅在您有其他条件时才存在,您无法在传递给createTreeWalker的过滤器中进行过滤。

请注意,这是TypeScript,您需要删除冒号(:)后面和尖括号(<>)之间的所有标记,例如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 } <Element>以获得有效的JavaScript。

翻译后的JS作为服务:

:(node: Node) => number

答案 18 :(得分:0)

我使用此代码

    $(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');
    }
});

答案 19 :(得分:0)

请参阅此纯js npm库以获取此类标签导航策略。
keyboard-navigator
这个小图书馆可处理Tab键,箭头键导航,保持对dom更新的关注,模式关注陷阱,后备关注。

答案 20 :(得分:0)

一个建议:不要在制表事件期间控制焦点到达的位置。相反,您可以通过将您不想接受的元素的Monad设置为c:/my_data/file1.pdf来控制哪些元素不可选项。例如

@Controller
public class Controll {

    @RequestMapping(value="/signup",method=RequestMethod.GET)
    public String signup(@ModelAttribute("signuper") Signuper signuper, ModelMap model){

        if(signuper.getSignupFName()!=null){  
            //dosomething
            return "emailForm";              
        }
      
        //dosomething
        return "signupForm";
    }

    @RequestMapping(value="/signup",method=RequestMethod.POST)
    public String signup(@Valid Signuper signuper,BindingResult result, ModelMap model,
        RedirectAttributes attributes)
    {
        //dosomething
        attributes.addFlashAttribute("signuper",signuper);
        return "redirect:signup";
    }

    @RequestMapping(value="/signup",method=RequestMethod.POST, params = "email")
    public String signupE(@Valid EmailVerify emailVerify,BindingResult result,ModelMap model) {
        //dosomething
        return "signupSuccess";
    }
}

注意:这可能不是您所处的最佳状态,例如在您的情况下,最好在某些tabIndex事件中控制标签索引,或者-1等上重置// `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; } }); 状态。

更多信息here