jQuery: Grid like table with keyboard navigation

Guess what time is it kids? It’s time for yet another boring, technical post. It’s that sort of week. Or rather it was – I usually queue my posts approximately 8-10 days in advance. This means that if I get hit by a bus one day, you won’t know anything is wrong until a week or so later. But I digress…

One of the web apps I maintain has a big table that looks like this:

<table>
	<tr>
		<td><input class='flat' name='foo'></td>
		<td><input class='flat' name='bar'></td>
		<td><input class='flat' name='baz'></td>
		<td><input class='flat' name='bin'></td>
		<td><input class='flat' name='bom'></td>
		<td><input class='flat' name='bam'></td>
	</tr>
 
	. . .
	. . .
	. . .
 
	<tr>
		<td><input class='flat' name='foo'></td>
		<td><input class='flat' name='bar'></td>
		<td><input class='flat' name='baz'></td>
		<td><input class='flat' name='bin'></td>
		<td><input class='flat' name='bom'></td>
		<td><input class='flat' name='bam'></td>
	</tr>
</table>

Basically, every cell contains an input box. Each of them has an onChange trigger which will submit it’s contents to the database via AJAX call. It basically allows the user to pull up a full page of records and edit them en masse without doing a lot of clicking.

Unfortunately this setup is a pain in the ass to navigate. Tabbing over works fine but only in one direction. The most intuitive way to traverse such a structure would be with the arrow keys – you know, just like a big spreadsheet. Sadly it just doesn’t work that way. But we can make it work like that with just a pinch of jQuery magic:

$(document).ready(function() {
    $("input.flat").keypress( function (e) {
        switch(e.keyCode)
        {
            // left arrow
            case 37:
                $(this).parent()
                        .prev()
                        .children("input.flat")
                        .focus();
                break;
 
            // right arrow
            case 39:
                $(this).parent()
                        .next()
                        .children("input.flat")
                        .focus();
                break;
 
            // up arrow
            case 40:
                $(this).parent()
                        .parent()
                        .next()
                        .children("td")
                        .children("input.flat[name="
                            +$(this).attr("name")+"]")
                        .focus();
                break;
 
            // down arrow
            case 38:
                $(this).parent()
                        .parent()
                        .prev()
                        .children("td")
                        .children("input.flat[name="
                            +$(this).attr("name")+"]")
                        .focus();
                break;
        }
    });
});

How does this work? Each time you press a key while one of the input boxes is in focus, I check the key code. If the code corresponds to one of the arrow key values I switch focus. The statements up there look a bit convoluted so let me explain one in more detail. Let’s do the left arrow:

  1. First we use the parent() function to get the parent node of the input box. This gives us the <td> tag
  2. Second, we use the prev() to get the previous sibling of our <td> node
  3. Third, we get the list of children of that node – and we narrow it down to just input boxes with the specific class. Because of the way our table is structured, we know there will always be exactly one child there
  4. Finally we call the focus() function on all the children (remember, there is only one there) which moves the cursor over

Moving up and down, requires extra steps. We call parent() twice to back out to the <tr> tag and grab previous or next sibling of that. Then we grab all the <td> children, and their children but narrowing it down to input boxes with the same name as the one that triggered the focus change. Once again there will always be exactly one node there.

End result is intuitive keyboard navigation that allows you to move in the table very much the way you move around in an Excel spreadsheet. I thought this was a pretty neat effect so I decided to share it here.

I created a live demo to demonstrate this effect. Mess around with it and let me know what you think. It’s a simple little tweak, but makes a huge usability difference – at least in my opinion.

Does anyone has an idea how to make this prettier, faster, better, stronger and etc? This code is probably suboptimal, but it works. I actually tested it on large-ish data sets, and it didn’t seem to cause visible slowdowns. But of course I’m always open for constructive criticism.

This entry was posted in programming and tagged , , . Bookmark the permalink.



13 Responses to jQuery: Grid like table with keyboard navigation

  1. freelancer SWEDEN Mozilla Firefox Windows Terminalist says:

    Hey, I love these boring, technical posts. They almost always tell me something useful that I don’t know. Like this, I probably never would’ve even thought about it. Might even use it on a bigger web app I’m maintaining, if I ever get around to learning jQuery (Yes, I’m still using plain old javascript for everything. And yes, I know I should move on :P)

    Reply  |  Quote
  2. Naum UNITED STATES Google Chrome Mac OS says:

    Why, these are my favorite posts. And they’re ones I wish I would take the time to write…

    Have you experimented with the jqGrid jQuery plugin? – http://trirand.com/jqgrid/jqgrid.html

    What are the drawbacks of using it?

    Reply  |  Quote
  3. Dax UNITED STATES Opera Windows says:

    The only downside to this is when someone wants to use the keyboard to move the cursor to a specific part of a value to edit it.

    Otherwise, this is a pretty neat idea. I haven’t used JQuery much, but I think I need to find a reason to include it in my next project.

    Reply  |  Quote
  4. Reflecting what Dax said, you lose the ability to manipulate the cursor position in the textbox. Maybe you want it to auto-select the content of the textbox when you arrive, so any typing replaces the content of the cell like in a spreadsheet.

    Reply  |  Quote
  5. Mart SINGAPORE Mozilla Firefox Windows Terminalist says:

    I’m with Dax. I would prefer not to lose the ability to move the cursor to a specific place within the value box.

    I’m quite comfy with the normal arrow key movements and tabbing. I’d imagine I’d be pretty frustrated if a dev implemented such a feature while I have to edit specific parts of the value in the whole spreadsheet.

    How about instead of using just arrow keys to move, use Ctrl+arrow keys instead? Then using the arrows will move the cursor within the value, while holding Ctrl (or Command for macs) with the arrows navigates the input boxes. Possible to implement?

    Reply  |  Quote
  6. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    @ Naum:

    I haven’t used the grid plugin. It’s probably the better, more robust solution. Mine is a quick hack that popped into my head one day. :)

    @ Dax:

    I could probably get around that by checking the position of the cursor within the input box. It would probably add a considerable complexity to the script. Maybe in the next version.

    @ Chris Wellons:

    Auto selecting is actually a good idea. Thanks for the tip.

    e@ Mart:

    Nah, that would probably be too complex and counter intuitive. I’d rather not have the users have to learn how to navigate the thing, but rather do what comes naturally. It should be possible to implement the left-right jump only if the cursor is at the beginning/end of the word. It’s just more work. :)

    Reply  |  Quote
  7. Steve UNITED STATES Google Chrome Windows says:

    What’s the point of having so many input boxes? Seems excessive especially when it comes to loading a large number of rows and columns. Since a user can only update one cell at a time, wouldn’t it make more sense to change a cell into an input box upon onclick() of that table cell? That way you can load just a plain html table, and you only have one input box at any one time on the table. And through the magic of css you can style it to look however you want.

    This could probably make the navigation part you’re trying to do a bit easier as well.

    Reply  |  Quote
  8. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    @ Steve:

    Interesting idea. I didn’t really think about this, but it might work. I will actually look into hacking that up, and see how it compares to this.

    Thanks.

    Reply  |  Quote
  9. Goran THE FORMER YUGOSLAV REPUBLIC OF MACEDONIA Mozilla Firefox Windows says:

    It only works in Firefox. It doesn’t work in IE and it doesn’t work in Safari.

    Reply  |  Quote
  10. bwngo SINGAPORE Google Chrome Windows says:

    I was doing a form that required lots of input fields in a table (think excel) and had to develop something similar as well.

    I had a solution for the up and down arrow to make it not rely on the input name. Thought I should share it here.

    // For up arrow:
    $(this).parent()
    .parent()
    .prev()
    .children("td")
    .eq($(this).parent().index())
    .children("input")
    .eq($(this).index())
    .focus();

    Basically the first eq() gets the TD index of the previous input, while the second eq() basically gets the INPUT index of the previous input. Hope this is useful.

    Reply  |  Quote
  11. Anup UNITED KINGDOM Mozilla Firefox Windows says:

    Hi,

    Thanks for the post.

    I think using keyup as the event will work for IE.

    Also, might be good idea to try and use delegate() method of jQuery so you only have to attach one event handler…

    Anup

    Reply  |  Quote
  12. xGs_Manco COLOMBIA Google Chrome Windows says:

    It not works!! to me…. what must i do? :S:S Do you have any new version?

    Reply  |  Quote
  13. Pingback: jQuery: Grid like table with keyboard navigation | arzrasel UNITED STATES PHP

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>