I am working on an HTML grade book for a client. I am generating the gradebook with PHP, and then outputting a HTML table as seen in the example below. Each <td>
contains a div with an <input>
for the teacher to type in the student's score.
Here's what I'm trying to accomplish: how can I make it so the teacher can use the arrow keys on the keyboard to navigate inside of the gradebook? IE: The teacher should be able to click a cell, type in a grade, and then hit the left/right/up/down arrow key to move to the appropriate input and type in the next grade.
I have seen numerous examples on here about how to use javascript to accomplish this task in highlighting different <td>
cells, but I cannot figure out how I would go about allowing the teacher to navigate inputs with her arrow keys. Any advice would be much appreciated.
body {
margin: 0;
position: absolute;
top: 105px; left: 0px;
width: 100%;
height: calc(100vh - 105px);
background-color: #FCFCFC;
display: grid;
grid-template-rows: 1fr;
grid-template-areas:
"master"}
.master {
grid-area: master;
overflow-x: scroll;}
table {border-collapse: collapse}
th, td {
background-color: white;
max-width: 110px;
border: 1px solid lightgray;}
th {overflow: hidden;}
thead{
top: 0;
position: sticky;
z-index: 1;}
tr td:nth-child(1),
tr th:nth-child(1){
position: sticky;
left: 0;}
thead th.navigator { /* Top left cell with navigation controls */
padding: 10px;
z-index: 3;}
tr td:first-child, tr td:nth-child(2) { /* First two columns of each row */
white-space: nowrap;
max-width: fit-content !important;}
td input {
border: none;
outline: none;
text-align: center;
max-width: 80%;
font-size: 18px;
padding: 6px 0px;
cursor: cell;}
th select {
outline: none;
-webkit-appearance: none;
padding: 8px 12px;
box-sizing: border-box;
border-radius: 8px;
width: 100%;
border: 1px solid lightgray}
tr:focus-within td:not(.gray) {background-color: #E9DCF9}
tr:focus-within td:not(.gray) input {background-color: #E9DCF9}
.due {
font-size: 11px;
color: darkgray;}
.assign {padding: 20px}
.assign span {
cursor: pointer;
font-size: 15px;
overflow: hidden;
color: #581F98}
.avg {padding: 10px}
.studentInfo {
display: flex;
align-items: center;
margin: 10px 12px 10px 6px;}
.studentInfo img {
width: 25px;
margin-right: 10px;
clip-path: circle();}
.red {background-color: red;}
.gray, .gray input {background-color: #F2F2F2;}
.score {
display: flex;
justify-content: center;
align-items: center;}
<table>
<thead>
<tr>
<th class='navigator' colspan='2' rowspan='4'>
<form method='GET'>
<select name='subID' onchange='this.form.submit()'>
<option value='1' >Reading</option>
<option value='2' >Social Studies</option>
</select>
<select name='week' onchange='this.form.submit()' disabled>
<option value='all'>Entire Quarter</option>
</select>
</form>
</th>
<tr>
<th class='due'><span title='Monday'>10/11</span> to <span title='Wednesday'>10/13</span></th>
<th class='due'><span title='Wednesday'>10/20</span> to <span title='Friday'>10/22</span></th>
<th class='due'><span title='Monday'>10/18</span> to <span title='Friday'>10/22</span></th>
<th class='due'><span title='Wednesday'>10/20</span> to <span title='Friday'>10/22</span></th>
</tr>
<tr>
<th class='assign'>
<span title='Assignment ID: 130' onclick='assignInfo("130");'>📚 Quiz</span>
</th>
<th class='assign'>
<span title='Assignment ID: 146' onclick='assignInfo("146");'>📚 Homework</span>
</th>
<th class='assign'>
<span title='Assignment ID: 145' onclick='assignInfo("145");'>💻 Test</span>
</th>
<th class='assign'>
<span title='Assignment ID: 147' onclick='assignInfo("147");'>✏️ Project</span>
</th>
</tr>
<tr>
<th class='avg gray'><span title='9.111/10'>91%</span></th>
<th class='avg gray'><span title='8.672/10'>87%</span></th>
<th class='avg gray'><span title='4.348/5'>87%</span></th>
<th class='avg gray'><span title='8.007/10'>80%</span></th>
</tr>
</thead>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 11'><img src='../../resources/pics/students/11.jpg'></span>
<span>John Doe</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='11' title='97.5/110'>89%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='11' data-workID='7280' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='11' data-workID='7282' data-curScore='9' value='9'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='11' data-workID='7340' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='11' data-workID='7280' data-curScore='10' value='10'>
</div>
</td>
</tr>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 12'><img src='../../resources/pics/students/12.jpg'></span>
<span>Jane Doe</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='12' title='97.5/110'>69%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='12' data-workID='7250' data-curScore='6' value='6'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='12' data-workID='7211' data-curScore='9' value='9'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='12' data-workID='7110' data-curScore='4' value='4'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='12' data-workID='7233' data-curScore='10' value='10'>
</div>
</td>
</tr>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 13'><img src='../../resources/pics/students/13.jpg'></span>
<span>Sally Martin</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='13' title='97.5/110'>100%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='13' data-workID='6250' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='13' data-workID='6211' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='13' data-workID='7610' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='13' data-workID='7933' data-curScore='10' value='10'>
</div>
</td>
</tr>
</table>
It's not perfect but it should give you a place to start. You'll have to add some error handling and handle edge cases.
document.addEventListener( 'keydown', ( event ) => {
const currentInput = document.activeElement;
const currentTd = currentInput.parentNode.parentNode;
const currentTr = currentTd.parentNode;
const index = Array.from(currentTr.children).indexOf(currentTd);
switch (event.key) {
case "ArrowLeft":
// Left pressed
currentTd.previousElementSibling.getElementsByTagName('input')[0].focus();
break;
case "ArrowRight":
// Right pressed
currentTd.nextElementSibling.getElementsByTagName('input')[0].focus();
break;
case "ArrowUp":
// Up pressed
Array.from( currentTr.previousElementSibling.children )[index].getElementsByTagName('input')[0].focus();
break;
case "ArrowDown":
// Down pressed
Array.from( currentTr.nextElementSibling.children )[index].getElementsByTagName('input')[0].focus();
break;
}
} )
body {
margin: 0;
position: absolute;
top: 105px; left: 0px;
width: 100%;
height: calc(100vh - 105px);
background-color: #FCFCFC;
display: grid;
grid-template-rows: 1fr;
grid-template-areas:
"master"}
.master {
grid-area: master;
overflow-x: scroll;}
table {border-collapse: collapse}
th, td {
background-color: white;
max-width: 110px;
border: 1px solid lightgray;}
th {overflow: hidden;}
thead{
top: 0;
position: sticky;
z-index: 1;}
tr td:nth-child(1),
tr th:nth-child(1){
position: sticky;
left: 0;}
thead th.navigator { /* Top left cell with navigation controls */
padding: 10px;
z-index: 3;}
tr td:first-child, tr td:nth-child(2) { /* First two columns of each row */
white-space: nowrap;
max-width: fit-content !important;}
td input {
border: none;
outline: none;
text-align: center;
max-width: 80%;
font-size: 18px;
padding: 6px 0px;
cursor: cell;}
th select {
outline: none;
-webkit-appearance: none;
padding: 8px 12px;
box-sizing: border-box;
border-radius: 8px;
width: 100%;
border: 1px solid lightgray}
tr:focus-within td:not(.gray) {background-color: #E9DCF9}
tr:focus-within td:not(.gray) input {background-color: #E9DCF9}
.due {
font-size: 11px;
color: darkgray;}
.assign {padding: 20px}
.assign span {
cursor: pointer;
font-size: 15px;
overflow: hidden;
color: #581F98}
.avg {padding: 10px}
.studentInfo {
display: flex;
align-items: center;
margin: 10px 12px 10px 6px;}
.studentInfo img {
width: 25px;
margin-right: 10px;
clip-path: circle();}
.red {background-color: red;}
.gray, .gray input {background-color: #F2F2F2;}
.score {
display: flex;
justify-content: center;
align-items: center;}
<table>
<thead>
<tr>
<th class='navigator' colspan='2' rowspan='4'>
<form method='GET'>
<select name='subID' onchange='this.form.submit()'>
<option value='1' >Reading</option>
<option value='2' >Social Studies</option>
</select>
<select name='week' onchange='this.form.submit()' disabled>
<option value='all'>Entire Quarter</option>
</select>
</form>
</th>
<tr>
<th class='due'><span title='Monday'>10/11</span> to <span title='Wednesday'>10/13</span></th>
<th class='due'><span title='Wednesday'>10/20</span> to <span title='Friday'>10/22</span></th>
<th class='due'><span title='Monday'>10/18</span> to <span title='Friday'>10/22</span></th>
<th class='due'><span title='Wednesday'>10/20</span> to <span title='Friday'>10/22</span></th>
</tr>
<tr>
<th class='assign'>
<span title='Assignment ID: 130' onclick='assignInfo("130");'>📚 Quiz</span>
</th>
<th class='assign'>
<span title='Assignment ID: 146' onclick='assignInfo("146");'>📚 Homework</span>
</th>
<th class='assign'>
<span title='Assignment ID: 145' onclick='assignInfo("145");'>💻 Test</span>
</th>
<th class='assign'>
<span title='Assignment ID: 147' onclick='assignInfo("147");'>✏️ Project</span>
</th>
</tr>
<tr>
<th class='avg gray'><span title='9.111/10'>91%</span></th>
<th class='avg gray'><span title='8.672/10'>87%</span></th>
<th class='avg gray'><span title='4.348/5'>87%</span></th>
<th class='avg gray'><span title='8.007/10'>80%</span></th>
</tr>
</thead>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 11'><img src='../../resources/pics/students/11.jpg'></span>
<span>John Doe</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='11' title='97.5/110'>89%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='11' data-workID='7280' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='11' data-workID='7282' data-curScore='9' value='9'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='11' data-workID='7340' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='11' data-workID='7280' data-curScore='10' value='10'>
</div>
</td>
</tr>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 12'><img src='../../resources/pics/students/12.jpg'></span>
<span>Jane Doe</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='12' title='97.5/110'>69%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='12' data-workID='7250' data-curScore='6' value='6'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='12' data-workID='7211' data-curScore='9' value='9'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='12' data-workID='7110' data-curScore='4' value='4'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='12' data-workID='7233' data-curScore='10' value='10'>
</div>
</td>
</tr>
<tr>
<td>
<div class='studentInfo'>
<span title='Student ID: 13'><img src='../../resources/pics/students/13.jpg'></span>
<span>Sally Martin</span>
</div>
</td>
<td class='avg gray'>
<span data-studentAvg='13' title='97.5/110'>100%</span>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='13' data-workID='6250' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='131' data-usid='13' data-workID='6211' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='132' data-usid='13' data-workID='7610' data-curScore='10' value='10'>
</div>
</td>
<td>
<div class='score'>
<input type='text' data-assID='130' data-usid='13' data-workID='7933' data-curScore='10' value='10'>
</div>
</td>
</tr>
</table>