Search code examples
javascriptjquerytextattributestabindex

Target only last level element with text


I'm trying to apply tabindex="0" to a bunch of elements that :

  • have text in it
  • does't still have a tabindex.

This is my code :

$(elems).not("[disabled]").each(function() {
    let n = $(this).attr('tabindex');
    if ((typeof n == typeof undefined || n == !1) && ($(this).text().trim().length)) {
        $(this).attr('tabindex', '0')
    }
});

The user is blind and travels through the page with TAB, the text is read with vocal synthesis.

1# This case is okay

    <div>Example text</div>
--> <div tabindex="0">Example text</div>

2# This case is problematic (first focus on div then focus on p so "Example text" read twice)

    <div>
        <p>Example text</p>
    </div>

--> <div tabindex="0">
        <p tabindex="0">Example text</p>
    </div>

2# This case is more problematic ("First text Second text" read then "Second text" again)

<div tabindex="0">First text
    <p tabindex="0">Second text</p>
</div>

I want "First text" to be read first, then "Second text".

I have a lot of solutions but heavy and inelegants. If you have a simple one, thank you in advance !

Basicaly, I want to apply a tabindex only on the TAG with text, except if it's text formating tab (b,i,u,strong...). Example:

<div>
  <p tabindex="0">This is <b>great</b> !</p>
</div>

Solution

  • From what I can read of your question, it sounds like the main issue is that jQuery's text() function is returning too much text in cases where an element has child elements. You particularly want to be able to pick out the "foo" and "baz" separate from the "bar" in the following example:

    <div>foo<p>bar</p>baz</div>
    

    If that's the case, then you need to stop working with "elements" and start working with Nodes. Nodes are much more fine-grained and give you greater insight into what the DOM actually looks like. For example, the above will be parse into roughly the following node tree:

    element: div
        text: "foo"
        element: p
            text: "bar"
        text: "baz"
    

    Nodes are more difficult to work with, because there are so many more of them of different types with different features. That's a cost you generally have to take on when you need more control, though.

    Here's one example of how you might accomplish your goal, but you'll probably need to adapt it to your specific needs.

    var root = document.getElementById('root');
    
    processChildNodes( root );
    
    // Log the resulting HTML to the console
    // so that we can see the tab index attribute:
    console.log( root.innerHTML );
    
    function processChildNodes(parent){
      var child;
      // Get either the first child of the parent or the next sibling of
      // the current child.
      // If we don't have any children or we run out of siblings, then
      // we're done here.
      while ( child = child ? child.nextSibling : parent.firstChild ){
        // If the node is disabled, then skip it.
        // Maybe this should be data-disabled?
        switch (child.nodeType){
          case Node.ELEMENT_NODE:
            // If the current node is an element, then set the tabIndex
            // and process the children.
            if ( !child.hasAttribute('disabled') ){
              child.setAttribute( 'tabindex',  '0' );
              processChildNodes( child );
            }
            break;
          case Node.TEXT_NODE:
            // If the current node is text, then "read" the text.
            // You don't have an example of how this is supposed to work,
            // so I'll just print it to the console.
            var text = (child.nodeValue || "").trim();
            if (text.length > 0) console.log( 'reading: ' + text );
            break;
        }
      }
    }
    <div id="root">
      <div>Example text 1</div>
    
      <div>
        <p>Example text 2</p>
      </div>
    
      <div>
          First text
          <p>Second text</p>
          Third text
          <p>
            Fourth text
            <span>Fifth text</span>
          </p>
          <p disabled>
            Skip this one.
          </p>
      </div>
    </div>