I have built a webpage using HTML/JS with D3.js to allow data entry/editing. The data is output to the page as a table and there are input boxes to allow the user to update information and then send it back to the server.
What I’m looking for is a way to provide traditional keyboard navigation between rows. The columns are handled by the built-in tab order functionality, but I’d like to be able to use up and down arrow keys to go between rows. There is a data-field
attribute that is used to identify the column (for feeding new values back to the server), so the idea is that pressing “up” would move the focus to the previous element in the DOM that has the same data-field
value, and similarly for the down button going to the next element.
The table layout is something like this:
<table id="data-table">
<tr>
<th>Row 1 header</th>
<td><input type="text" data-field="field-1" data-id="row-1" /></td>
<td><input type="text" data-field="field-2" data-id="row-1" /></td>
<td><input type="text" data-field="field-3" data-id="row-1" /></td>
</tr>
<tr>
<th>Row 2 header</th>
<td><input type="text" data-field="field-1" data-id="row-2" /></td>
<td><input type="text" data-field="field-2" data-id="row-2" /></td>
<td><input type="text" data-field="field-3" data-id="row-2" /></td>
</tr>
<tr>
<th>Row 3 header</th>
<td><input type="text" data-field="field-1" data-id="row-3" /></td>
<td><input type="text" data-field="field-2" data-id="row-3" /></td>
<td><input type="text" data-field="field-3" data-id="row-3" /></td>
</tr>
</table>
Using D3.js, I’m figuring that the event handling process would be something like
d_field = d3.select('input:focus').attr('data-field'); \ alternatively use event.currentTarget
d_col = d3.selectAll('input[data-field="'+d_field+'"]');
From there, it would be checking the d_col
selection to see which element refers to the same node as the d_field
selection, then moving to the next or previous element and applying the focus to it. I’m just stumbling over that check and move part.
New Answer Based on Comment
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js"></script>
</head>
<body>
<table id="data-table">
<tr>
<th>Row 1 header</th>
<td><input type="text" data-field="field-1" data-id="row-1" /></td>
<td><input type="text" data-field="field-2" data-id="row-1" /></td>
<td><input type="text" data-field="field-3" data-id="row-1" /></td>
</tr>
<tr>
<th>Row 2 header</th>
<td><input type="text" data-field="field-1" data-id="row-2" /></td>
<td><input type="text" data-field="field-2" data-id="row-2" /></td>
<td><input type="text" data-field="field-3" data-id="row-2" /></td>
</tr>
<tr>
<th>Row 3 header</th>
<td><input type="text" data-field="field-1" data-id="row-3" /></td>
<td><input type="text" data-field="field-2" data-id="row-3" /></td>
<td><input type="text" data-field="field-3" data-id="row-3" /></td>
</tr>
</table>
<script>
d3.select('#data-table')
.selectAll('input')
.on('keyup', function(d, i, j) {
if (
d.key === 'ArrowUp' ||
d.key === 'ArrowDown' ||
d.key === 'ArrowLeft' ||
d.key === 'ArrowRight'
) {
let targ;
if (d.key === 'ArrowRight')
targ = this.parentElement.nextElementSibling ?
this.parentElement.nextElementSibling.firstChild :
null;
if (d.key === 'ArrowLeft')
targ = this.parentElement.previousElementSibling ?
this.parentElement.previousElementSibling.firstChild :
null;
if (d.key === 'ArrowUp')
targ = this.parentElement.parentElement.previousElementSibling ?
this.parentElement.parentElement.previousElementSibling
.children[this.parentElement.cellIndex].firstChild :
null;
if (d.key === 'ArrowDown')
this.parentElement.parentElement.nextElementSibling ?
(targ = this.parentElement.parentElement.nextElementSibling
.children[this.parentElement.cellIndex].firstChild) :
null;
if (targ && targ.focus) targ.focus();
}
});
</script>
</body>
</html>
Old Answer
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js"></script>
</head>
<body>
<table id="data-table">
<tr>
<th>Row 1 header</th>
<td><input type="text" data-field="field-1" data-id="row-1" /></td>
<td><input type="text" data-field="field-2" data-id="row-1" /></td>
<td><input type="text" data-field="field-3" data-id="row-1" /></td>
</tr>
<tr>
<th>Row 2 header</th>
<td><input type="text" data-field="field-1" data-id="row-2" /></td>
<td><input type="text" data-field="field-2" data-id="row-2" /></td>
<td><input type="text" data-field="field-3" data-id="row-2" /></td>
</tr>
<tr>
<th>Row 3 header</th>
<td><input type="text" data-field="field-1" data-id="row-3" /></td>
<td><input type="text" data-field="field-2" data-id="row-3" /></td>
<td><input type="text" data-field="field-3" data-id="row-3" /></td>
</tr>
</table>
<script>
d3.select('#data-table')
.selectAll('input')
.on('keyup', function (d, i, j) {
if (
d.key === 'ArrowUp' ||
d.key === 'ArrowDown' ||
d.key === 'ArrowLeft' ||
d.key === 'ArrowRight'
) {
let cur = d3.select(this),
rowString = cur.attr('data-id'),
rowNum = +rowString.substring(rowString.length - 1),
fieldString = cur.attr('data-field'),
fieldNum = +fieldString.substring(fieldString.length - 1);
if (d.key === 'ArrowUp') rowNum--;
else if (d.key === 'ArrowDown') rowNum++;
else if (d.key === 'ArrowLeft') fieldNum--;
else if (d.key === 'ArrowRight') fieldNum++;
let targ = d3
.select(
`input[data-field='field-${fieldNum}'][data-id='row-${rowNum}']`
)
.node();
if (targ) targ.focus();
}
});
</script>
</body>
</html>
2