Mathew
Mathew

Reputation: 123

Javascript: navigate table inputs with arrow keys

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>

Upvotes: 4

Views: 5032

Answers (2)

user3707094
user3707094

Reputation: 371

<!DOCTYPE html>
<html>

<head>
    <title>
        Navigation through input fields by arrow keys
   </title>

<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script>


<script type="text/javascript">
    $(function() {
    var index=0;
    $(document).mousedown( function( e ) { 
    console.log(e.target);
        if( e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA" )
        {
            var current_field = e.target.id;
            var current_element = current_field.substr(10,1);
            var current_position= parseInt(current_element);
            
            
            index = (current_position-1) * 6;
            
            if(current_field.includes('content'))
                index= index+0;
            else if(current_field.includes('component'))
                index= index+1;
            else if (current_field.includes('method'))
                index= index+2;
            else if (current_field.includes('delivery_lead'))
                index= index+3;
            else if (current_field.includes('total_hours'))
                index= index+4;
            else if (current_field.includes('included_in_otj'))
                index= index+5;
            console.log(index);
            
        }
    })
    var elements = document.getElementsByClassName("arrow-togglable");
    var currentIndex = 0;

    document.onkeydown = function(e) {
        currentIndex = index;
        console.log("currentIndex1:"+currentIndex);
        switch (e.keyCode) {
            case 37:
              currentIndex = (currentIndex == 0) ? elements.length - 1 : --currentIndex;
              elements[currentIndex].focus();
              //alert(currentIndex);
              index= currentIndex;
              break;
            case 38:
              currentIndex = (currentIndex == 0) ? elements.length - 1 : currentIndex-6;
              elements[currentIndex].focus();
              index= currentIndex;
              //alert(currentIndex);
              break;
            case 39:
              currentIndex = ((currentIndex + 1) == elements.length) ? 0 : ++currentIndex;
              elements[currentIndex].focus();
              index= currentIndex;
              break;
            case 40:
              currentIndex = ((currentIndex + 1) == elements.length) ? 0 : currentIndex+6;
              elements[currentIndex].focus();
              index= currentIndex;
              break;
            
        }
        console.log("currentIndex2:"+currentIndex);
    };
});
</script>
</head>
<body >
  <div class="container" >
    <div class='h4_div'>                    
        <h4 class='col-xs-12 blue_h4'>Navigate through input fields</h4> 
    </div>
    <div  style='text-align: center;margin-top:0px;margin-bottom:5px;'> 
        <table  class='col-xs-12 cs_table' id='planned_off_year1'>
            <tr  style ='width:90%; '>
                <td class='td_office_box_label_2'> Content</td>
                <td class='td_office_box_label_2'> Component</td>
                <td class='td_office_box_label_2'> Method</td>
                <td class='td_office_box_label_2'> Delivery Lead</td>
                <td class='td_office_box_label_2'> Total hours</td>
                <td class='td_office_box_label_2'> Included in OTJ</td>
            </tr>
            <tr style ='width:90%; '>
              <td ><input id="year1_term1_content" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term1_component" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term1_method"class="arrow-togglable" style='width:100%'value='' ></td>
              <td ><input id="year1_term1_delivery_lead" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term1_total_hours" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term1_included_in_otj" class="arrow-togglable" style='width:100%' value='' ></td>
            </tr>
            <tr style ='width:90%; '>
              <td ><input id="year1_term2_content" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term2_component" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term2_method" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term2_delivery_lead" class="arrow-togglable" style='width:100%' value=''></td>
              <td ><input id="year1_term2_total_hours" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term2_included_in_otj" class="arrow-togglable" style='width:100%' value='' ></td>
            </tr>
            <tr style ='width:90%; '>
              <td ><input id="year1_term3_content" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term3_component" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term3_method" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term3_delivery_lead" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term3_total_hours" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term3_included_in_otj" class="arrow-togglable" style='width:100%' value='' ></td>
            </tr>
            <tr style ='width:90%; '>
              <td ><input id="year1_term4_content" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term4_component" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term4_method" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term4_delivery_lead" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term4_total_hours" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term4_included_in_otj" class="arrow-togglable" style='width:100%' value='' ></td>
            </tr>
            <tr style ='width:90%; '>
              <td ><input id="year1_term5_content" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term5_component" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term5_method" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term5_delivery_lead" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term5_total_hours" class="arrow-togglable" style='width:100%' value='' ></td>
              <td ><input id="year1_term5_included_in_otj" class="arrow-togglable" style='width:100%' value='' ></td>
            </tr>
            
        </table>
    </div>
            
</div>
            
</body>
</html>

Upvotes: 0

Getter Jetter
Getter Jetter

Reputation: 2081

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>

Upvotes: 5

Related Questions