I am searching for a way to dynamically create a tabindex
for each element in my React application. There are multiple ponents 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:
- have a fixed offset for each table (but I have no idea how many inputs may be in a single table and
tabindex
should stay below 32767 so I cant just assume huge gaps) - pass
tabindex
offset on to table and get amount of usedtabindex
from React object to calculate next offset (seems to me like it would break modularity and awkward to implement) - tracking the next
tabindex
through global state (hacky and would probably break when the table is extended) - tracking the next
tabindex
through dom tree (no idea how to implement)
Is there a tool or a mon practice for tabindex
creation I am missing?
I am searching for a way to dynamically create a tabindex
for each element in my React application. There are multiple ponents 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:
- have a fixed offset for each table (but I have no idea how many inputs may be in a single table and
tabindex
should stay below 32767 so I cant just assume huge gaps) - pass
tabindex
offset on to table and get amount of usedtabindex
from React object to calculate next offset (seems to me like it would break modularity and awkward to implement) - tracking the next
tabindex
through global state (hacky and would probably break when the table is extended) - tracking the next
tabindex
through dom tree (no idea how to implement)
Is there a tool or a mon practice for tabindex
creation I am missing?
- 1 i think this valid problem needs to be addressed diferrently - some interesting ideas in web ponents – Ovidiu Dolha Commented May 25, 2017 at 13:08
-
Generally, the best practice is to avoid using positive integers for
tabindex
. Can you give some context on what does you mean by "alter the direction in which the user tabs through those inputs". That probably help us to give you a better answer. – Tharaka Wijebandara Commented May 26, 2017 at 6:08 - I have multiple tables and would like tabbing to traverse them through columns first (top to bottom) and then left to right to the next columnt after the last row is reached. – thammi Commented May 26, 2017 at 14:18
3 Answers
Reset to default 7 +100I 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./ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare./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 remend 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 remend to look at reselect which can use to pute derived state in Redux.
Hope this helps!
For a mon 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);
}
Here is a function that you could call every time new inputs are added to your layout. It assigns a tabIndex
to each input, without any gap, and acmodates tables of various sizes, where each cell can have any number of input elements. You can test it in this jsfiddle.
The input elements are stored in a Map object, where each key is a bination of table, column, and row indices. The keys are then sorted, and the tabIndex
property is set in the order of the sorted keys.
function setTabIndices() {
var tableIndex, rowIndex, colIndex, inputIndex;
var table, row, cell, inputs;
var map = new Map();
var tables = document.getElementsByTagName("table");
for (tableIndex = 0; tableIndex < tables.length; tableIndex++) {
table = tables[tableIndex];
for (rowIndex = 0; rowIndex < table.rows.length; rowIndex++) {
row = table.rows[rowIndex];
for (colIndex = 0; colIndex < row.cells.length; colIndex++) {
cell = row.cells[colIndex];
inputs = cell.getElementsByTagName("input");
for (inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
// Define key based on table, column, and row indices
map.set(format(tableIndex, 4) + format(colIndex, 6) +
format(rowIndex, 6) + format(inputIndex, 3),
inputs[inputIndex]);
}
}
}
}
var input;
var sortedKeys = [...map.keys()].sort(); // Not supported in IE11
for (var tabIndex = 1; tabIndex <= sortedKeys.length; tabIndex++) {
input = map.get(sortedKeys[tabIndex - 1]);
input.tabIndex = tabIndex;
}
}
function format(value, digits) {
return ("0000000000" + value.toString()).slice(-digits);
}
Note: the following line causes trouble in IE, which does not support the spread syntax:
var sortedKeys = [...map.keys()].sort();
If you must support IE, you can call map.forEach
to populate the unsorted array, as shown in this modified jsfiddle.