Navigate an HTML table by utilizing the arrow keys for seamless browsing

My spreadsheet made with an HTML table works well, but currently requires the user to click on each <td> cell to edit it. I am using jQuery to capture the click event and show a dialog for editing. I want to enhance the user experience by allowing them to navigate through cells using arrow keys, changing the background color to indicate focus, and triggering the jQuery dialog event with the Enter key. I am working with jQuery 1.9.

You can view a demo of what I have so far in this jsfiddle.

I would like to know how to save the currently selected cell so that when a cell is clicked with the mouse and then the arrow keys are used, it will move from the 'current' cell. Any insights about this would be greatly appreciated.

Thank you.

Answer №1

Here is a pure vanilla JavaScript solution that utilizes the onkeydown event along with the previousElementSibling and nextElementSibling properties.

Visit this JSFiddle link for a live demo.

An issue encountered when using tabindex is the lack of navigation flexibility compared to Excel, where users can inadvertently navigate away from the spreadsheet itself.

The HTML structure:

<table>
  <tbody>
    <tr>
      <td id='start'>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
    </tr>
    <tr>
      <td>5</td>
      <td>6</td>
      <td>7</td>
      <td>8</td>
    </tr>
    <tr>
      <td>9</td>
      <td>10</td>
      <td>11</td>
      <td>12</td>
    </tr>
    <tr>
      <td>13</td>
      <td>14</td>
      <td>15</td>
      <td>16</td>
    </tr>
  </tbody>
</table>

The CSS style rules:

table {
  border-collapse: collapse;
  border: 1px solid black;
}
table td {
  border: 1px solid black;
  padding: 10px;
  text-align: center;
}

The JavaScript logic:

var start = document.getElementById('start');
start.focus();
start.style.backgroundColor = 'green';
start.style.color = 'white';

function performNavigation(sibling) {
  if (sibling != null) {
    start.focus();
    start.style.backgroundColor = '';
    start.style.color = '';
    sibling.focus();
    sibling.style.backgroundColor = 'green';
    sibling.style.color = 'white';
    start = sibling;
  }
}

document.onkeydown = checkKey;

function checkKey(e) {
  e = e || window.event;
  if (e.keyCode == '38') {
    // up arrow
    var idx = start.cellIndex;
    var prevRow = start.parentElement.previousElementSibling;
    if (prevRow != null) {
      var sibling = prevRow.cells[idx];
      performNavigation(sibling);
    }
  } else if (e.keyCode == '40') {
    // down arrow
    var idx = start.cellIndex;
    var nextRow = start.parentElement.nextElementSibling;
    if (nextRow != null) {
      var sibling = nextRow.cells[idx];
      performNavigation(sibling);
    }
  } else if (e.keyCode == '37') {
    // left arrow
    var sibling = start.previousElementSibling;
    performNavigation(sibling);
  } else if (e.keyCode == '39') {
    // right arrow
    var sibling = start.nextElementSibling;
    performNavigation(sibling);
  }
}

Answer №2

After synthesizing information from various sources, I managed to solve the issue by combining different solutions. The outcome turned out to be flawless.

Note: To enable navigation, remember to add a tabindex attribute to each <td>.

Check out the jsfiddle link. You can also view the code breakdown below.

The HTML:

<table>
    <thead>
        <tr>
            <th>Col 1</th>
            <th>Col 2</th>
            <th>Col 3</th>
            <th>Col 4</th>
            <th>Col 5</th>
            <th>Col 6</th>
            <th>Col 7</th>
            <th>Col 8</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td tabindex="1">1</td>
            <td tabindex="2">2</td>
            <td tabindex="3">3</td>
            <td tabindex="4">4</td>
            <td tabindex="5">5</td>
            <td tabindex="6">6</td>
            <td tabindex="7">7</td>
            <td tabindex="8">8</td>
        </tr>
        <tr>
            <td tabindex="10">10</td>
            <td tabindex="11">11</td>
            <td tabindex="12">12</td>
            <td tabindex="13">13</td>
            <td tabindex="14">14</td>
            <td tabindex="15">15</td>
            <td tabindex="16">16</td>
            <td tabindex="17">17</td>
        </tr>
    </tbody>
</table>

<div id="edit">
    <form>
        <input type="text" id="text" value="To edit..." />
        <input type="submit" value="Save" />
    </form>
</div>

The CSS:

* {
    font-size: 12px;
    font-family: 'Helvetica', Arial, Sans-Serif;
    box-sizing: border-box;
}

table, th, td {
    border-collapse:collapse;
    border: solid 1px #ccc;
    padding: 10px 20px;
    text-align: center;
}

th {
    background: #0f4871;
    color: #fff;
}

tr:nth-child(2n) {
    background: #f1f1f1;
}
td:hover {
    color: #fff;
    background: #CA293E;
}
td:focus {
    background: #f44;
}

.editing {
    border: 2px dotted #c9c9c9;
}

#edit { 
    display: none;
}

The jQuery:

var currCell = $('td').first();
var editing = false;

// User clicks on a cell
$('td').click(function() {
    currCell = $(this);
    edit();
});

// Show edit box
function edit() {
    editing = true;
    currCell.toggleClass("editing");
    $('#edit').show();
    $('#edit #text').val(currCell.html());
    $('#edit #text').select();
}

// User saves edits
$('#edit form').submit(function(e) {
    editing = false;
    e.preventDefault();
    // Ajax to update value in database
    $.get('#', '', function() {
        $('#edit').hide();
        currCell.toggleClass("editing");
        currCell.html($('#edit #text').val());
        currCell.focus();
    });
});

// User navigates table using keyboard
$('table').keydown(function (e) {
    var c = "";
    if (e.which == 39) {
        // Right Arrow
        c = currCell.next();
    } else if (e.which == 37) { 
        // Left Arrow
        c = currCell.prev();
    } else if (e.which == 38) { 
        // Up Arrow
        c = currCell.closest('tr').prev().find('td:eq(' + 
          currCell.index() + ')');
    } else if (e.which == 40) { 
        // Down Arrow
        c = currCell.closest('tr').next().find('td:eq(' + 
          currCell.index() + ')');
    } else if (!editing && (e.which == 13 || e.which == 32)) { 
        // Enter or Spacebar - edit cell
        e.preventDefault();
        edit();
    } else if (!editing && (e.which == 9 && !e.shiftKey)) { 
        // Tab
        e.preventDefault();
        c = currCell.next();
    } else if (!editing && (e.which == 9 && e.shiftKey)) { 
        // Shift + Tab
        e.preventDefault();
        c = currCell.prev();
    } 

    // If we didn't hit a boundary, update the current cell
    if (c.length > 0) {
        currCell = c;
        currCell.focus();
    }
});

// User can cancel edit by pressing escape
$('#edit').keydown(function (e) {
    if (editing && e.which == 27) { 
        editing = false;
        $('#edit').hide();
        currCell.toggleClass("editing");
        currCell.focus();
    }
});

Answer №3

This code provides a solution for navigating a table using arrow keys effectively. Each cell contains textboxes that can be edited by pressing F2.

$(document).ready(function()
{
var tr,td,cell;
td=$("td").length;
tr=$("tr").length;
cell=td/(tr-1);//one tr have that much of td
//alert(cell);
$("td").keydown(function(e)
{
switch(e.keyCode)
{

case 37 : var first_cell = $(this).index();
  if(first_cell==0)
  {
$(this).parent().prev().children("td:last-child").focus();
  }
  else
$(this).prev("td").focus();break;//left arrow
case 39 : var last_cell=$(this).index();
  if(last_cell==cell-1)
  {
$(this).parent().next().children("td").eq(0).focus();
  }
  $(this).next("td").activeElement; break; //right arrow
case 40 : var child_cell = $(this).index();
  $(this).parent().next().children("td").eq(child_cell).focus(); break; //down arrow
case 38 : var parent_cell = $(this).index();
  $(this).parent().prev().children("td").eq(parent_cell).focus(); break; //up arrow
}
if(e.keyCode==113)
{
$(this).children().focus();
}
});
$("td").focusin(function()
{
$(this).css("outline","solid steelblue 3px");//animate({'borderWidth': '3px','borderColor': '#f37736'},100);
});
$("td").focusout(function()
{
$(this).css("outline","none");//.animate({'borderWidth': '1px','borderColor': 'none'},500);
});

});
input
{
width:100%;
border:none;
}
<html>
<head>
<title>Web Grid Using Arrow Key</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  </head>
<body>
<h1>Web Grid Table</h1>
<div id="abc" class="table_here" role="grid">
<table class="table" border="1" style="width:50%; padding:15px;">
<tr>
<th>Name</th>
<th>Email</th>
<th>Mobile</th>
<th>Address</th>
</tr>
<tr role="row">
<td role="gridcell" tabindex="0" aria-label="name" aria-describedby="f2_key">
<input type="text" class="link" tabindex="-1" name="name" aria-label="name">
</td>
<td role="gridcell" tabindex="-1" aria-label="Email Id" aria-describedby="f2_key">
<input type="text" class="link" tabindex="-1" name="email" aria-label="email">
</td>
<td role="gridcell" tabindex="-1" aria-label="Mobile Number" aria-describedby="f2_key">
<input type="text" class="link" tabindex="-1" name="mob" aria-label="mobile">
</td>
<td role="gridcell" tabindex="-1" aria-label="Address" aria-describedby="f2_key">
<input type="text" class="link" tabindex="-1" name="add" aria-label="address">
</td>
<p id="f2_key" style="display:none;" aria-hidden="true">Press F2 Key To Edit cell</p>
</tr>
... (continued HTML code)
      </table>
    </div>
   </body>
</html>

Answer №4

After researching arrow key focusing techniques, I combined several solutions from various sources to create the following code snippet. It appears that using .next() or .prev() did not work as expected for rows, requiring the use of .prevAll and .nextAll instead:

   $("input").keydown(function (e) {
    var textInput = this;
    var val = textInput.value;
    var isAtStart = false, isAtEnd = false;
    var cellindex = $(this).parents('td').index();
    if (typeof textInput.selectionStart == "number") {
        // Non-IE browsers

        isAtStart = (textInput.selectionStart == 0);
        isAtEnd = (textInput.selectionEnd == val.length);
    } else if (document.selection && document.selection.createRange) {
        // IE <= 8 branch
        textInput.focus();
        var selRange = document.selection.createRange();
        var inputRange = textInput.createTextRange();
        var inputSelRange = inputRange.duplicate();
        inputSelRange.moveToBookmark(selRange.getBookmark());
        isAtStart = inputSelRange.compareEndPoints("StartToStart", inputRange) == 0;
        isAtEnd = inputSelRange.compareEndPoints("EndToEnd", inputRange) == 0;
    }

      // workaround for text inputs of 'number' not working in Chrome... selectionStart/End is null.  Can no longer move cursor left or right inside this field.
    if (textInput.selectionStart == null) {
        if (e.which == 37 || e.which == 39) {

            isAtStart = true;
            isAtEnd = true;
        }
    }

    if (e.which == 37) {
        if (isAtStart) {
            $(this).closest('td').prevAll('td').find("input").focus();
        }
    }
    if (e.which == 39) {

        if (isAtEnd) {
            $(this).closest('td').nextAll('td').find("input").not(":hidden").first().focus();
        }
    }
    if (e.which == 40) {
              $(e.target).closest('tr').nextAll('tr').find('td').eq(cellindex).find(':text').focus();
    }
    if (e.which == 38) {
    $(e.target).closest('tr').prevAll('tr').first().find('td').eq(cellindex).find(':text').focus();
    }

});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Python button Selenium

I am new to using selenium and need assistance with clicking a button located between button tags. I attempted to use find_element_by_tag_name, but it did not work because there are other button tags scattered around before. If I try by_class_name or by_xp ...

Ways to implement a transition effect when the hover state is deactivated or no longer present

After applying a transition to the header using the hover pseudo-class, I noticed that it abruptly snaps back to its original position when the hover effect is removed. Is there a way to smoothly return the header to its original position using the same ...

What is the best approach to assigning a default value to @Html.EditorFor(model => model.location)?

After attempting to add , new { @Value = "test" } and failing, I then made modifications to the model as follows: private string _location = string.Empty; public string location { get { return _location; } set { _location = value; } } Even when ...

A guide on displaying HTML content in a browser with a C# class library

Is there a way to view HTML content without actually creating an HTML file in C# using the .NET class library? I would like to achieve this by loading the content directly and then navigating to another page specified in the form's action tag with a p ...

Unable to locate the specified CSS file

I've been diving into the world of NativeScript and exploring the tutorials provided at . However, I'm facing an issue with getting the platform specific CSS to work properly. Every time I try to run the code, I encounter the following error mess ...

Whenever I send a single submission using the POST method to Python Flask, my webpage ends up being rendered multiple times

@app.route("/process" , methods=['POST']) def process(): return render_template('index.html') I am in need of assistance with my problem. I am currently working on a project using Python Flask and HTML. ...

Is there a way to specify a particular color for all question marks in the text, for instance?

Can punctuation marks such as question marks, exclamation points, or commas be styled uniformly without the need to individually wrap each one in a div or span element? ...

Having trouble with aligning items in the center using Bootstrap 5?

Can anyone help me figure out how to center a div or img element vertically within another div element? I've tried using align-items-center", but nothing seems to be working. I've also experimented with changing the display properties of both ele ...

Retrieve the key value pair from the AJAX response

I am trying to retrieve specific values from a cross domain call to a PHP script. The response I receive is as follows: response=2&count=15&id=84546379&firstname=adsa&emailstatus= My goal is to extract the values of 'response' a ...

Guide to inserting outcome rows into a JSON structure

I'm currently using Ajax to update my data by making calls to function.php. Within function.php, I utilize a switch statement to determine which function to execute and store the results in a JSON object ($resp = new stdClass). However, I am unsure of ...

Steps to display the "col-md-6" div upon hovering over list items in a different div

I have been trying to make a hover action work in my HTML document. I've managed to get it to work only when the description is a child of the li tag, but I want the description to be displayed as a separate div. <div class="row"> ...

Troubleshooting Issues with Implementing JavaScript for HTML5 Canvas

I've encountered an issue with my code. After fixing a previous bug, I now have a new one where the rectangle is not being drawn on the canvas. Surprisingly, the console isn't showing any errors. Here's the snippet of code: 13. var can ...

The response of the JQuery button click event is not what I anticipated when using asp.net mvc

I need assistance with my _Layout.cshtml code. <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>@ViewBag.Title - My ASP.NET MVC Application</title> <link href="~/fav ...

refreshing the div content without reloading the page

I've created a PHP script that tracks button clicks to a text file, and it's working perfectly. On my HTML page, I'm displaying the click counts like this: <?php include ('counter.php'); ?> <div class="count_right" id=" ...

Mastering the art of customizing styles in Material UI v5 versus v4

When I first started using Material UI v4, I relied on makeStyles and withStyles from @mui/styles to customize the default styles of components in my application. It was a simple and effective approach to achieve the desired look and feel. However, with th ...

Issues encountered with certain Tailwind styles functioning improperly when deployed in a production environment with Next.js

It seems that there are some style issues occurring in the production build hosted on Netlify for a specific component. The problematic component is a wrapper located at ./layout/FormLayout.tsx. Below is the code for this wrapper: const FormLayout: React.F ...

Is there a way to conceal/remove the text displayed on the submit button?

I am facing an issue with a submit button in my PHP code. The button displays an integer value that is crucial for running a script, but I want to hide the value as it interferes with the design using CSS. I have attempted various solutions like removing t ...

What is the best way to preserve text input value when going back a page in PHP?

Is there a way to keep the text box value entered by the user even after navigating away from the page? Below is an example of what I am trying to achieve in my code: **main.php** <html> <head></head> <body> <i ...

Utilizing fanybox-2 for creating a variety of background colors

I am currently using fancybox to showcase some of my website links with opaque backgrounds as requested by the designer. I have successfully customized the background color to meet the requirements. However, I am now facing a new question: is it possible ...

How to automatically collapse all opened accordion panes in jQuery when a new one is expanded

I am currently working on implementing an accordion feature that needs to collapse other expanded links when a new one is clicked. I am aware that this functionality is available in the accordion plugin, but I want to avoid adding another library like jQue ...