richhallstoke
richhallstoke

Reputation: 1601

How to change focus to the next or previous focusable and enabled input within a table cell (jQuery or vanilla javascript only)?

In an HTML table a reasonable number of cells, spanning numerous rows, include text inputs that may be enabled or not depending on prior selected options in dropdowns. While the tabindex is correctly configured so that users can use TAB or SHIFT+TAB to navigate between these inputs, I have been asked to replicate this behavior using the LEFT and RIGHT arrow keys, which is proving rather difficult to implement.

Simplified version of HTML:

<html><body>
  <table><tr>
    <th></th>
    <th>Sample<br/>01</th>
    <th>Sample<br/>02</th>
    <th>Sample<br/>03</th>
    <th>Sample<br/>04</th>
    <th>Sample<br/>05</th>
  </tr><tr>
    <th>Group 01</th>
    <td>
      <input type="text" tabindex="1" class="sample-value" 
             name="edtG01S01" id="edtG01S01"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S01" id="cbxG01S01"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="2" class="sample-value" 
             name="edtG01S02" id="edtG01S02"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S02" id="cbxG01S02"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="3" class="sample-value" 
             name="edtG01S03" id="edtG01S03" disabled="disabled"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S03" id="cbxG01S03"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="4" class="sample-value" 
             name="edtG01S04" id="edtG01S04"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S04" id="cbxG01S04"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="5" class="sample-value" 
             name="edtG01S05" id="edtG01S05"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S05" id="cbxG01S05"/> &lt;1
    </td>
  </tr><tr>
    <th>Group 02</th>
    <td>
      <input type="text" tabindex="6" class="sample-value" 
             name="edtG02S01" id="edtG02S01"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S01" id="cbxG02S01"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="7" class="sample-value" 
             name="edtG02S02" id="edtG02S02"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S02" id="cbxG02S02"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="8" class="sample-value" 
             name="edtG02S03" id="edtG02S03" disabled="disabled"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S03" id="cbxG02S03"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="9" class="sample-value" 
             name="edtG02S04" id="edtG01S04"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S04" id="cbxG02S04"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="10" class="sample-value" 
             name="edtG02S05" id="edtG02S05"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S05" id="cbxG02S05"/> &lt;1
    </td>
  </tr></table>
</body></html>

The inputs have the class sample-value, and at the moment the closest I've got working is:

$('input.sample-value').keyup(function(event) {
  if (event.which == 39) { /* right arrow key */
    $(this).parent('td').next('td').find('input.sample-value').focus();
    event.preventDefault();
  }
  if (event.which == 37) { /* left arrow key */
    $(this).parent('td').prev('td').find('input.sample-value').focus();
    event.preventDefault();
  }
});

With the above code the left and right arrow keys navigate between inputs as expected, until you come to a disabled input, or until you come to the end of a row. At that point it is unable to skip forward or back to the next focusable input.

Is there a way this code can be adapted to be flexible enough to navigate to the appropriate input.sample-value element regardless of where it sits in table rows or columns, and being able to take into account skipping disabled inputs? The inputs are not disabled at the time of page load but dynamically enabled or disabled in response to selected options in dropdowns preceeding these inputs.

Full working example:

$('input.sample-value').keyup(function(event) {
  if (event.which == 39) { /* right arrow key */
    $(this).parent('td').next('td').find('input.sample-value').focus();
    event.preventDefault();
  }
  if (event.which == 37) { /* left arrow key */
    $(this).parent('td').prev('td').find('input.sample-value').focus();
    event.preventDefault();
  }
});
* {
  font-family: Arial;
}
input.sample-value {
  width: 50px;
}
th, td {
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html><body>
  <table><tr>
    <th></th>
    <th>Sample<br/>01</th>
    <th>Sample<br/>02</th>
    <th>Sample<br/>03</th>
    <th>Sample<br/>04</th>
    <th>Sample<br/>05</th>
  </tr><tr>
    <th>Group 01</th>
    <td>
      <input type="text" tabindex="1" class="sample-value" 
             name="edtG01S01" id="edtG01S01"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S01" id="cbxG01S01"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="2" class="sample-value" 
             name="edtG01S02" id="edtG01S02"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S02" id="cbxG01S02"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="3" class="sample-value" 
             name="edtG01S03" id="edtG01S03" disabled="disabled"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S03" id="cbxG01S03"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="4" class="sample-value" 
             name="edtG01S04" id="edtG01S04"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S04" id="cbxG01S04"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="5" class="sample-value" 
             name="edtG01S05" id="edtG01S05"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG01S05" id="cbxG01S05"/> &lt;1
    </td>
  </tr><tr>
    <th>Group 02</th>
    <td>
      <input type="text" tabindex="6" class="sample-value" 
             name="edtG02S01" id="edtG02S01"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S01" id="cbxG02S01"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="7" class="sample-value" 
             name="edtG02S02" id="edtG02S02"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S02" id="cbxG02S02"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="8" class="sample-value" 
             name="edtG02S03" id="edtG02S03" disabled="disabled"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S03" id="cbxG02S03"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="9" class="sample-value" 
             name="edtG02S04" id="edtG01S04"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S04" id="cbxG02S04"/> &lt;1
    </td>
    <td>
      <input type="text" tabindex="10" class="sample-value" 
             name="edtG02S05" id="edtG02S05"/>
      <br/>
      <input type="checkbox" tabindex="-1" name="cbxG02S05" id="cbxG02S05"/> &lt;1
    </td>
  </tr></table>
</body></html>

Also available as a JSFiddle: https://jsfiddle.net/zoLwv5d9/

Upvotes: 0

Views: 934

Answers (3)

richhallstoke
richhallstoke

Reputation: 1601

By combining code from your post @CarstenLøvboAndersen with code from @gone-coding (How to move focus on next field when enter is pressed?) I've managed to get it skipping across the same row and to the next row and the same in reverse, however unfortunately if there are multiple disabled inputs together it still gets stuck trying to navigate focus to the next focusable input. Further help would be appreciated!

$('input.sample-value').keyup(function(event) {
  if (event.which == 39) { /* right arrow key */
    var next = $(this).parent('td').next('td');

    if (next.length == 0) {
      next = $(this).closest("tr").find("td:first");
    }

    /* If disabled input then skip to the next cell */
    if (next.find("input:disabled").length > 0) next = next.next("td");
    
    next.find('input.sample-value').focus();

    /* Jump to next row */
    var $canfocus = $(':input.sample-value');
    var index = $canfocus.index(this) + 1;
    if (index >= $canfocus.length) index = 0;
    $canfocus.eq(index).focus();
    
    event.preventDefault();
  }
  if (event.which == 37) { /* left arrow key */
    var prev = $(this).parent('td').prev('td');

    if (prev.length == 0) {
      prev = $(this).closest("tr").find("td:last");
    }
    
    /* If disabled input then skip to the previous cell */
    if (prev.find("input:disabled").length > 0) prev = prev.prev("td");

    prev.find('input.sample-value').focus();

    /* Jump to previous row */
    var $canfocus = $(':input.sample-value');
    var index = $canfocus.index(this) - 1;
    if (index >= $canfocus.length) index = 0;
    $canfocus.eq(index).focus();
    
    event.preventDefault();
  }
});
$('#edtG01S01').focus();
* {
  font-family: Arial;
}

input.sample-value {
  width: 50px;
}

th,
td {
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>

<body>
  <table>
    <tr>
      <th></th>
      <th>Sample<br/>01</th>
      <th>Sample<br/>02</th>
      <th>Sample<br/>03</th>
      <th>Sample<br/>04</th>
      <th>Sample<br/>05</th>
    </tr>
    <tr>
      <th>Group 01</th>
      <td>
        <input type="text" tabindex="1" class="sample-value" name="edtG01S01" id="edtG01S01" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S01" id="cbxG01S01" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="2" class="sample-value" name="edtG01S02" id="edtG01S02" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S02" id="cbxG01S02" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="3" class="sample-value" name="edtG01S03" id="edtG01S03" disabled="disabled" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S03" id="cbxG01S03" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="4" class="sample-value" name="edtG01S04" id="edtG01S04" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S04" id="cbxG01S04" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="5" class="sample-value" name="edtG01S05" id="edtG01S05" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S05" id="cbxG01S05" /> &lt;1
      </td>
    </tr>
    <tr>
      <th>Group 02</th>
      <td>
        <input type="text" tabindex="6" class="sample-value" name="edtG02S01" id="edtG02S01" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S01" id="cbxG02S01" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="7" class="sample-value" name="edtG02S02" id="edtG02S02" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S02" id="cbxG02S02" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="8" class="sample-value" name="edtG02S03" id="edtG02S03" disabled="disabled" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S03" id="cbxG02S03" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="9" class="sample-value" name="edtG02S04" id="edtG01S04" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S04" id="cbxG02S04" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="10" class="sample-value" name="edtG02S05" id="edtG02S05" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S05" id="cbxG02S05" /> &lt;1
      </td>
    </tr>
  </table>
</body>

</html>

Thank you very much for your help.

Upvotes: 0

I've changed your code a bit, so it now skips disabled input and jumps to first/last if it gets to the end or start.

var next = $(this).parent('td').next('td');

if (next.length == 0) {
  next = $(this).closest("tr").find("td:first")
} else if (next.find("input:disabled").length > 0) {
  next = next.next("td")
}

next.find('input.sample-value').focus();
event.preventDefault();

demo

$('input.sample-value').keyup(function(event) {
  if (event.which == 39) { /* right arrow key */
    var next = $(this).parent('td').next('td');

    if (next.length == 0) {
      next = $(this).closest("tr").find("td:first")
    } else if (next.find("input:disabled").length > 0) {
      next = next.next("td")
    }

    next.find('input.sample-value').focus();
    event.preventDefault();
  }
  if (event.which == 37) { /* left arrow key */
    var prev = $(this).parent('td').prev('td');

    if (prev.length == 0) {
      prev = $(this).closest("tr").find("td:last")
    } else if (prev.find("input:disabled").length > 0) {
      prev = prev.prev("td")
    }

    prev.find('input.sample-value').focus();
    event.preventDefault();
  }
});
* {
  font-family: Arial;
}

input.sample-value {
  width: 50px;
}

th,
td {
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>

<body>
  <table>
    <tr>
      <th></th>
      <th>Sample<br/>01</th>
      <th>Sample<br/>02</th>
      <th>Sample<br/>03</th>
      <th>Sample<br/>04</th>
      <th>Sample<br/>05</th>
    </tr>
    <tr>
      <th>Group 01</th>
      <td>
        <input type="text" tabindex="1" class="sample-value" name="edtG01S01" id="edtG01S01" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S01" id="cbxG01S01" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="2" class="sample-value" name="edtG01S02" id="edtG01S02" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S02" id="cbxG01S02" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="3" class="sample-value" name="edtG01S03" id="edtG01S03" disabled="disabled" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S03" id="cbxG01S03" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="4" class="sample-value" name="edtG01S04" id="edtG01S04" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S04" id="cbxG01S04" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="5" class="sample-value" name="edtG01S05" id="edtG01S05" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG01S05" id="cbxG01S05" /> &lt;1
      </td>
    </tr>
    <tr>
      <th>Group 02</th>
      <td>
        <input type="text" tabindex="6" class="sample-value" name="edtG02S01" id="edtG02S01" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S01" id="cbxG02S01" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="7" class="sample-value" name="edtG02S02" id="edtG02S02" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S02" id="cbxG02S02" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="8" class="sample-value" name="edtG02S03" id="edtG02S03" disabled="disabled" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S03" id="cbxG02S03" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="9" class="sample-value" name="edtG02S04" id="edtG01S04" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S04" id="cbxG02S04" /> &lt;1
      </td>
      <td>
        <input type="text" tabindex="10" class="sample-value" name="edtG02S05" id="edtG02S05" />
        <br/>
        <input type="checkbox" tabindex="-1" name="cbxG02S05" id="cbxG02S05" /> &lt;1
      </td>
    </tr>
  </table>
</body>

</html>

Upvotes: 1

Just ED
Just ED

Reputation: 27

var input_ = $('#table1').find('input[type="text"]');
input_.blur(function(){
  var thisIndex = input_.index(this);
  var nextInput = input_.eq(thisIndex+1);
  if(nextInput.length){
    nextInput.focus();
  }

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>it will be triggered when the input blur. you can change as you want. hope this helped</p>

<table id="table1">
  <tr>
    <td>
      <input type="text" value="input 1" />
    </td>
  </tr>
  <tr>
    <td>
     <input type="text" value="input 2" /> <br />
     <input type="text" value="input 3" />
    </td>
  </tr>
  <tr>
    <td>
      <input type="text" value="input 4" />
    </td>
  </tr>
</table>

Upvotes: 0

Related Questions