7

I have a form with many fields and I have given every single input, select and button a tabindex number. That works, but I'd like to do it programatically.

The default tabindex order isn't correct because i have a two-column layout with groups in each column. I want to go top-down by group. How can I write a body.onload function so that it would assign all input, select and button tags a tabindex number based upon the containing div? For example, for the div I want to be cycled through first, all the input, select and button tags could have a tabindex=1, and all the input, select and button tags in the second div could have tabindex=2, and so on.

Thanks!

Here's a simplified example

<style>
  .a { display: inline-block;
       width:200px;
       border: 1px solid black;
  }
</style>


<div class="a">
    <div id="Div01" title="these inputs should have tabindex=1">
        <p>Div 01</p>
        <input id="Div01Field1" type="text" value="Me first"/>
        <input id="Div01Field3" type="text" value="Me second"/>
        <input id="Div01Field2" type="text" value="Me third"/>
        <hr>
    </div>
    <div id="Div03" title="these inputs should have tabindex=3">
        <p>Div 03</p>
        <input id="Div03Field1" type="text" value="Me seventh"/>
        <input id="Div03Field2" type="text" value="Me eighth"/>
        <input id="Div03Field3" type="text" value="Me ninth"/>
        <hr>
    </div>
    <div id="Div05" title="these inputs should have tabindex=5">
        <p>Div 05</p>
        <input id="Div05Field1" type="text" value="Me thirteenth"/>
        <input id="Div05Field2" type="text" value="Me fourteenth"/>
        <input id="Div05Field3" type="text" value="Me fifteenth"/>
    </div>
</div>
<div class="a">
    <div id="Div02" title="these inputs should have tabindex=2">
        <p>Div 02</p>
        <input id="Div02Field1" type="text" value="Me fourth"/>
        <input id="Div02Field2" type="text" value="Me fifth"/>
        <input id="Div02Field3" type="text" value="Me sixth"/>
        <hr>
    </div>
    <div id="Div04" title="these inputs should have tabindex=4">
        <p>Div 04</p>
        <input id="Div04Field1" type="text" value="Me tenth"/>
        <input id="Div04Field2" type="text" value="Me eleventh"/>
        <input id="Div04Field3" type="text" value="Me twelfth"/>
        <hr>
    </div>
    <div id="Div06" title="these inputs should have tabindex=6">
        <p>Div 06</p>
        <input id="Div06Field1" type="text" value="Me sixteenth"/>
        <input id="Div06Field2" type="text" value="Me seventeenth"/>
        <input id="Div06Field3" type="text" value="Me eighteenth"/>
    </div>
</div>
Michael Swarts
  • 3,813
  • 8
  • 31
  • 41
  • 1
    This might help: http://stackoverflow.com/questions/3059203/tab-index-on-div – Sudhir Bastakoti Nov 27 '11 at 00:56
  • I'd prefer a straight JavaScript function to a jquery solution. But yes, essentially that's the right idea. Because divs cannot be tab-indexed the way I had imagined, I need to apply tabindex numbers to elements based upon which div they are contained in, and set a div order that the function would follow. – Michael Swarts Nov 27 '11 at 01:01
  • `tabindex=0` doesn't work the way you describe. From the [WCAG 2.0 techniques](http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H4.html): When the interactive elements are navigated using the tab key, the elements are given focus in increasing order of the value of their tabindex attribute. _Elements that have a tabindex value higher than zero will receive focus before elements without a tabindex or a tabindex of 0._ – steveax Nov 27 '11 at 02:27
  • Thanks Steveax – I did not know that! But the goal is the same, just starting from 1... :) – Michael Swarts Nov 27 '11 at 03:02
  • It would be helpful to see the HTML markup. Maybe create a simplified fiddle? – steveax Nov 27 '11 at 03:44
  • tab index can have the unintended consequence of making a page less accessible (by the user input jumping around in a disorderly manner). The default tabbing index is the interactive elements in the order that they appear in the page. Unless you have a *very* good reason to break this flow, I recommend against setting tab indices. – zzzzBov Nov 29 '11 at 01:22
  • Just added a bounty if you're interested. :) – Michael Swarts Nov 29 '11 at 01:22
  • @zzzzBov: Yep, I do, that's why I put up a bounty! – Michael Swarts Nov 29 '11 at 01:23

4 Answers4

6

A more flexible version of Mike's code which sets the tabIndex to the number used in the Div id's. This also needs no modification when you change the page structure.

Any div with no id or with an id which does not match the prefix-number pattern is ignored.

<script> "use strict"; // place after </body> tag
  (function TabNumbers (pfx) {
    /* For all divs in the document with an id pfx followed by a number,
       set the tabIndex of all immediate children with tags of INPUT,
       SELECT, or BUTTON to the numeric value */
    pfx = new RegExp ('^' + pfx + '(\\d+)$');  
    for (var divs = document.getElementsByTagName ('div'), 
             el, m, i = divs.length; i--;) { // traverse all divs 
      if ((m = divs[i].id.match (pfx))) { // for those with id Div#
        for (el = divs[i].firstChild; el; 
             el = el.nextSibling) { // Traverse their child nodes
          if (el.tagName === 'INPUT' || el.tagName === 'SELECT' || 
              el.tagName === 'BUTTON') {
              el.tabIndex = +m[1];
          }         
        }
      }
    }
  }) ('Div');  
</script>

After some discussion the spec was modified and the following code was accepted :

<script> "use strict"; // place after </body> tag
  (function TabNumbers () {
    var itags = ["INPUT", "SELECT", "BUTTON"]
      , tags
      , tag
      , el  
      , t
      , a
      ;

    while (itags.length) {
      for (tags = document.getElementsByTagName (itags.pop ()), t = tags.length; t--;) {
        el = tag = tags[t];
        while ((el = el.parentNode) && el.tagName) {
          if (el.getAttribute && (a = el.getAttribute ('data-tindex'))) { 
            tag.tabIndex = a;
            break;
          }
        }
      }     
    }
  }) ();    
</script>

Tested on Chrome

HBP
  • 15,685
  • 6
  • 28
  • 34
  • This version could be more helpful to me if the first part of the Div ID could be anything and not just 'Div'. It would also be nice for the function to skip over Divs whose IDs do NOT end with a 2-digit number sequence. Thanks for your help! – Michael Swarts Dec 01 '11 at 09:54
  • Am I misunderstanding you that the whole block of code should be placed after the tag? (I'm not getting it to work that way.) – Michael Swarts Dec 02 '11 at 01:06
  • It should, what browser are you using? Here is a jsFiddle using your original HTML example : http://jsfiddle.net/4dhkh/ – HBP Dec 02 '11 at 07:37
  • I just reviewed the code and realised that my comment is confusing. When I said "after the tag" I meant at the end of the document. It is clearer to say "after the tag" as per the changes I have made. Sorry for the confusion. the jsFiddle example shows the correct placement. – HBP Dec 03 '11 at 01:29
  • I'm having difficulty getting it to work. I'll keep trying and update you if I figure out, but for now, pasting that whole block of code after the body closing tag seems to have no effect. – Michael Swarts Dec 03 '11 at 11:53
  • More info: If I put 'console.log('here');' between 'for (el = divs[i].firstChild; el; el = el.nextSibling) { // Traverse their child nodes' and 'if (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'BUTTON') { el.tabIndex = +m[1];' the 'here' never shows up in the console log. It does if I put it earlier in your code block. – Michael Swarts Dec 03 '11 at 12:01
  • I can do that. Just a moment on it. I just tested your fiddle a bit however and if I change the Div IDs arbitrarily, it no longer works. Even if I keep the numbers at the end of the IDs the same, so there's something wrong with the way the script is assigning tabIndex numbers. – Michael Swarts Dec 03 '11 at 13:33
  • Nothing wrong, the rule is that only divs with id's consisting of 'Div' followed by a number are processed. If you want to change the prefix string 'Div', change it on the last line of the script. I can set this up to process ALL divs whose IDs end with a number but that could make changes where you don't want them. Another prossibility is to add a CLASS attribute of say 'tindex' to the DIVs you want to process and simply ignore the IDs. That is probably the way I would have designed it. – HBP Dec 03 '11 at 16:05
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/5563/discussion-between-hans-b-pufal-and-mike) – HBP Dec 03 '11 at 19:22
  • Crashed unexpectedly last night. When we sync up again, we'll chat. I'm totally open to the class idea. – Michael Swarts Dec 03 '11 at 20:54
3

If the containers Div01 etc can have sortable ids like in your example then you can do this

jquery solution

var groups = $('div[id^="Div"]').sort(function(a,b){
    return (a.id > b.id) ? 1 : -1;
});

groups.find(':input').each(function(idx){
    $(this).prop('tabindex', idx+1);
});

Demo at http://jsfiddle.net/gaby/sNekS/


Alternatively (and likely more correctly) you can just rearrange your divs so that they are correctly sorted in the source and still show in left/right groups when rendered (using float:left on the inner divs), and use no scripting at all..

Demo at http://jsfiddle.net/gaby/sNekS/1/


Vanilla Javascript solution (after adding class group to the Div## elements and class input to the input/select/etc elements)

var gnodes = document.getElementsByClassName('group'); // find elements with group class - non-sortable
var groups = []; // create empty array to hold groups - sortable
for (var i = 0, l = gnodes.length; i<l; i++){ // place elements in array so we can sort it
    groups.push( gnodes[i] );
}
groups.sort(function(a,b){ // sort the array based on id
    return (a.id > b.id) ? 1 : -1;
});

var counter = 1; // incremental number to define the tabindex
for (var i = 0, l = groups.length; i<l; i++){
    var group = groups[i],
        elements = group.getElementsByClassName('input'); // find all input elements  of this group (must add class to all of them)
    for (var e = 0, len = elements.length; e < len; e++){
        elements[e].setAttribute('tabindex',counter++); // set tabindex
    }
}

Demo at http://jsfiddle.net/gaby/sNekS/3/

Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
0

Assuming that you're using Prototype (which you probably aren't), it would look like this :

Event.observe(window, 'dom:load', function() {
    var inputs = [$$('#div1 input, #div1 a'), $$('#div2 input')];

    var i = 0;
    inputs.each(function(inputList) {
        inputList.each(function(input) {
            i++;
            input.tabIndex = i;
        });
    });
});

Note: untested

Tom van der Woerdt
  • 29,532
  • 7
  • 72
  • 105
0
<script type="text/javascript">
  function TabNumbers() { 
      var t = document.getElementById('Div01').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
              t[i].tabIndex = 1;
          }
      }
      var t = document.getElementById('Div02').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
             t[i].tabIndex = 2;
          }
      }
      var t = document.getElementById('Div03').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
             t[i].tabIndex = 3;
          }
      }
      var t = document.getElementById('Div04').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
             t[i].tabIndex = 4;
          }
      }
      var t = document.getElementById('Div05').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
             t[i].tabIndex = 5;
          }
      }
      var t = document.getElementById('Div06').childNodes;
      for (i = 0; i < t.length; i++) { 
          if (t[i].tagName == 'INPUT' || t[i].tagName == 'SELECT' || t[i].tagName == 'BUTTON') {
             t[i].tabIndex = 6;
          }
      }
  }
</script>
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Michael Swarts
  • 3,813
  • 8
  • 31
  • 41