I am searching for a way to dynamically create a tabindex for each element in my React application. There are multiple components containing input elements I want to create a tabindex for and I am not sure how to coordinate them and avoid collision.
My React application is consisting of multiple tables containing input elements. At runtime the tables can be extended by the user creating additional input elements. I want to alter the direction in which the user tabs through those inputs, but I am not sure how I can generate a tabindex for each element.
The ways I came up with so far:
tabindex should stay below 32767 so I cant just assume huge gaps)tabindex offset on to table and get amount of used tabindex from React object to calculate next offset (seems to me like it would break modularity and awkward to implement)tabindex through global state (hacky and would probably break when the table is extended)tabindex through dom tree (no idea how to implement)Is there a tool or a common practice for tabindex creation I am missing?
I don't know whether you have already figured out this or not. But there is a trick that you can use here.
You don't need to have a different tabindex for each cell in the table to make the tab navigation that you want. You just need different tabindex for each column in your table. You can repeat the same tabindex for each cell in the same column. Because when tabindex is the same, the browser just use HTML element order to decide the next control and it's what we need in this case. So tabindex assignment for a table will be something like this.
1   2   3   4
1   2   3   4
1   2   3   4
1   2   3   4
This is a significant win. Because for a 10X10 table we don't need 100 different tab indexes. We can do it with just 10 indexes.
Here is a small example to demonstrate this idea with React. You can run it and see.
// Table
class Table extends React.Component {
  generateRows(){
    const offset = this.props.tabIndexOffSet;
    const cols = this.props.cols;
    const data = this.props.data;
    return data.map(function(item) {
        const cells = cols.map(function(colData, index) {
          return (
            <td key={colData.key}>
              <input type="text"
               value={item[colData.key]}
               tabIndex={offset+index} />
            </td>
          );
        });
        return (<tr key={item.id}> {cells} </tr>);
    });
   }
  generateHeaders() {
    var cols = this.props.cols;
    return (
      <tr>
        {
          cols.map(function(colData) {
            return <th key={colData.key}> {colData.label} </th>;
          })
        }
      </tr>
    );
  }
  render(){
    const headerComponents = this.generateHeaders();
    const rowComponents = this.generateRows();;
    return (
      <table>
          <thead> {headerComponents} </thead>
          <tbody> {rowComponents} </tbody>
      </table>
    );
  }
}
// App
class App extends React.Component{
  constructor(){
    super();
    this.state = { 
      cols: [
          { key: 'firstName', label: 'First Name' },
          { key: 'lastName', label: 'Last Name' }
      ],
      data: [
          { id: 1, firstName: 'John', lastName: 'Doe' },
          { id: 2, firstName: 'Clark', lastName: 'Kent' },
          { id: 3, firstName: 'Tim', lastName: 'Walker' },
          { id: 4, firstName: 'James', lastName: 'Bond' }
      ]
    }
  }
  
  render () {
    return (
      <div>
        <Table
          tabIndexOffSet={1}
          cols={this.state.cols}
          data={this.state.data}/>
        <Table
          tabIndexOffSet={3}
          cols={this.state.cols}
          data={this.state.data}/>
      </div>
    );
  }
}
// Render
ReactDOM.render(
  <App/>,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
If the number of columns is manageable with this approach, I recommend you to go with your first option by assigning different offset for each table (also illustrated in the above example).If the number of columns is fixed then it will be easy. Even it's not, you can try to keep some reasonable gaps with fixed hardcoded offsets based on your use case.
If this doesn't work in that way, the next thing you can do is trying calculating offset values from your data. I assume that you will have some sort of data which are represented by this tables, like an object array or array of arrays, probably in your application state. So you can derive offsets for tables based on these data. If you are using a state management library like Redux it will be really easy and I recommend to look at reselect which can use to compute derived state in Redux.
Hope this helps!
For a common left-to-right-down DOM related representation it could something like:
var el = document.documentElement,
    rebuildIndex = function () {
        document.getElementsByTagName('input').forEach(function (input, idx) {
            input.setAttribute('tabindex', idx);
        });
    };
// Firefox, Chrome
if (support.DOMSubtreeModified) {
    el.addEventListener('DOMSubtreeModified', rebuildIndex, false);
// Safari, Opera
} else {
    el.addEventListener('DOMNodeInserted', rebuildIndex, false);
    el.addEventListener('DOMNodeRemoved', rebuildIndex, false);
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With