Navigate through table rows using the keyboard

Version 0.6.1. Licensed under the MIT-License.

About

This page describes a technique which uses the keyboard keys to navigate through table rows.
Implemented by Stephan Soller <stephan.soller@helionweb.de> and
extended by Roberto Rambaldi. Idea by Florian Seefried <florian.seefried@helionweb.de>.

The main idea of Florian Seefried was to make tables more userfriendly by giving them the possibilty to use the arrow keys to navigate thought table rows and using return to “enter” an record. Together with the backspace key this makes fast navigation possible. This behaviour is known from some desktop applications but is not seen very often on web applications. This little script should make it easier for programmers to integrate this behaviour into thier applications.

If you have any questions, want to report a bug or just want to talk about the script you can send me a mail.

Features

Example

Just take a look at the example page. You can also download the example as a zipped archive.

How to integrate it into your project

  1. You'll find all necessary source files in the zipped example. Alternativly you can use the linked files of the next step.
  2. Include jquery.js (jQuery v1.1.1) and jquery.table_navigation.js into your page.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
    <head>
    	<script src="jquery.js" type="text/javascript"></script>
    	<script src="jquery.table_navigation.js" type="text/javascript"></script>
  3. Call the jQuery.tableNavigation() function to initialize the table navigation.

    <script type="text/javascript">
    	jQuery.tableNavigation();
    </script>

    You can specify some options to change the behaviour or source code interaction of the script. See the options section for a detailed description. Calling jQuery.tableNavigation() multiple times will update the options used by the table navigation.

  4. Add the CSS class navigateable to the table element which you want to be navigateable using the keyboard. Also add the CSS class activation to the a element of each row whichs href attribute should be used if the user presses the return key or double clicks on the row. This marks the link as the activation link for the corresponding row.

    <table class="navigateable">
    	<thead>
    		<tr>
    			<th>Cell 01</th>
    			<th>Cell 02</th>
    			<th>Cell 03</th>
    		</tr>
    	</thead>
    	<tbody>
    		<tr>
    			<td>Data 01-01</td>
    			<td><a href="target01.html" class="activation">Link 01</a></td>
    		</tr>
    		<tr>
    			<td>Data 02-01</td>
    			<td><a href="target02.html" class="activation">Link 02</a></td>
    		</tr>
    	</tbody>
    </table>

    It's important to use the thead and tbody tags to seperate the table header from the body. By default only the rows (tr elements) in the tbody element are selectable and rows in the table header are not. You can change this by modifying the row_selector option (detailed information below).

  5. Create a CSS style for tr.selected which formats the currently selected table row.

    <style type="text/css">
    	tr.selected {background-color: red; color: white;}
    </style>
  6. That's all. Much fun. If you find this little script useful, discovered bugs or have ideas to improve it, just leave a comment or drop me a mail.

Options

You can specify some options when calling the jQuery.tableNavigation function to initialize the table navigation. To update the used options just call jQuery.tableNavigation again with the new options. The function takes these options as a hash:

jQuery.tableNavigation({
	table_selector: 'table.navigateable',
	row_selector: 'table.navigateable tbody tr',
	selected_class: 'selected',
	activation_selector: 'a.activation',
	bind_key_events_to_links: true,
	focus_links_on_select: true,
	select_event: 'click',
	activate_event: 'dblclick',
	activation_element_activate_event: 'click',
	scroll_overlap: 20,
	cookie_name: 'last_selected_row_index',
	focus_tables: true,
	focused_table_class: 'focused',
	jump_between_tables: true,
	disabled: false,
	on_activate: null,
	on_select: null
});
table_selector
The CSS selector used to get all elements (read tables) which contains selectable rows. This is used to jump between multible tables and to highlight the current table with the class specified in the focused_table_class option. Defaults to table.navigateable.
row_selector
The CSS selector used to get all navigateable table rows. The default value is table.navigateable tbody tr which will effect all table rows (tr elements) inside a tables tbody element. This way header tr elements inside of a thead element will not be affected. You can use your own selector here but it have to effect tr elements. Something like div.overview tbody tr.viewable_records will limit the effected table rows a little bit more.
selected_class
The name of the CSS class identifying the selected table row. Defaults to selected.
activation_selector
The CSS selector which is used to find the rows activation element. This element will be focused when the row is selected. When a row is activated and this element is a link the user will be redirected to it's target URI. The default value is a.activation.
bind_key_events_to_links
This option let you controll if the key press event should be bound to the activation links of the rows (true) or to the document object (false). If the events are bound to the document object forms on the same page will not work properly but the script won't need the focus to work. Default value is true, to get pre v0.5.4 behaviour set it to false.
focus_links_on_select
This specifies if the activation link should be focused when selecting a row. This is necessary for the key press events to work if the bind_key_events_to_links option is set to false. Defaults to true but if you want pre v0.5.4 behaviour set it to false.
select_event
The event which will select the corresponding row. Usually it's a mouse click (default is click) but you can use all event names supported by jQuery, see jQueries event docs.
activate_event
The event activating the currently selected row. You can use all event names supported by jQuery (see jQueries event docs). Defaults to dblclick.
activation_element_activate_event
The event of the activation element which activates the the currently selected row. You can use all event names supported by jQuery (see jQueries event docs) or set it to false or null to not bind any event to the activation element. Defaults to click.
scroll_overlap
The amount of pixels that gets added to a scroll command if the viewport must be scrolled to see a selected row. If the viewport gets scrolled up to make an element visible the user sees the next X pixels over the element, too. Defaults to 20 pixels. This also compensates some browser incompatibilities and a value blow 20 isn't recommended.
cookie_name
The name of the cookie used to store the index of the last activated row. This is used to select the same row if the user returns to the table using the back button or key. If set to null the script will not use any cookies at all. Then however if you return to a table you may find the wrong row selected. This is caused by different browser caching behavior and usually the script can work around the problem using cookies. The default cookie name is last_selected_row_index.
disabled
A switch to temporarily disable all event handlers used by the script. For example useful if you want to use popups. Default is false (event handlers are enabled). See the demonstration page for an example.
focus_tables
A switch (true or false) to toggle the highlighting of the current table. If set to true the table of the currently selected row will be marked with the class specified in the focused_table_class option. Set to false if you just have one table on a page and/or want to increase the performance of the script. Defaults to true.
focused_table_class
The CSS class name used to highlight the table of the currently selected table row. Defaults to focused.
focused_table_class
The CSS class name used to highlight the table of the currently selected table row. Defaults to focused.
jump_between_tables
A switch (true or false) which allows you to controll if the script can jump between multiple tables on one page. If set to true and the last row of a table is reached the next row selected will be the first row of the next table. Note that this can confuse users because the keyborad focus and the view port also jumps to the first row of the next table. Defaults to true.
on_select
A user specified function which will be called if a table row gets selected. This can be used to inject your own actions on this event. The default value is null and no user specified code is run. See user specified event handlers for detailed information and examples.
on_activate
A user specified function which will be called if a table row gets activated (the return key is pressed or a row is double clicked). This can be used to inject your own actions on this event. The default value is null and no user specified code is run. See user specified event handlers for detailed information and examples.

User specified event handlers

They can be used to inject your own code on certain events like when a row gets selected or activated. A quick example will tell more than 100 words so here's one:

jQuery.tableNavigation({
	on_activate: function(row){
		var link_destination = jQuery("a", row).attr("href");
		var user_wants_to_go = confirm('This link will bring you to "' + link_destination + '".\nDo you really want to go?');
		return user_wants_to_go;
	}
});

As you can see in the example the event handler is a function assigned to the on_activate option. This function takes the DOM object of the row affected by the event as a parameter and is called every time a row is activated. What you want to do with the row is up to you. In the example we grab the value of the first links href attribute and ask the user if he wants to go to this location. We then return the users decision. But what is this return user_wants_to_go; about?

If an event handler returns true (or anything other than false) the plugin will continue as usual after calling our event handler. In case of the on_activate event it will redirect the browser to the location of the activation link. However this default action is not always wanted eg. if the user clicks "abort" in our example. In this case an event handler can return false to suppress the default action and the user will not be redirected to the new location.

Alternatively to specifying a function you can assign a simple string to the on_activate option:

jQuery.tableNavigation({
	on_activate: "onclick"
});

or

jQuery.tableNavigation({
	on_activate: "alert('we love the evil eval()');"
});

If the string you assigned to the on_activate option starts with "on" it is interpreted as an attribute name. When a row gets activated the plugin will look for an attribute of the activation link named like this and eval its content. Any other string will be evaled directly.

These approaches are usually hard to debug and not really good coding practice. In almost any cases you will be better of by using a function like in the first example.

Everything show here for the on_activate option also applies to the on_select option. The default action of the on_select event is to mark the next row with the class specified in the selected_class option and to scroll the page to make the new row visible if necessary. Again if your handler returns anything but true these default actions will be skipped.

Known issues

Want to help? Know a solution to a bug? Leave a comment or send me a mail.

How it works

This script intercepts several keyboard and mouse events and uses them to manipulate the currently selected row of a table.

First of by default the script will only affect tables of the CSS class navigateable. Within such a table the script considers only rows (tr elements) with a tbody element as selectable. This is necessary to avoide that the table header row can be selected (tr elements within the thead element). The selected row will be marked with the CSS class selected. So if you want to define special styles for the selected row you should simply use the CSS selector tr.selected.

Several events can be used to manipulate the selected table row:

A click on a row
This will select the clicked table row.
A double click on a row
This will activate the double clicked table row.
Pressing the or key
The next table row will be selected. This is the tr element that follows the currently selected tr element.
Pressing the or key
The previous table row will be selected. This is the tr element just before the currently selected tr element.
Pressing the return key
This will activate the currently selected row.

When selecting a row the CSS class "selected" is added to this rows tr tag. This class gets removed from the previously selected row. Additionaly the activation link of the new row will be focused and the screen will scroll to the position of the row leaving a certain amount of pixels to the border of the viewport (default 20 pixels). This ensures that the selected row is visible. If there is no next row to select the arrow keys will cause the page to scroll just as usual.

Activating a row will also select this row. More important this will read the URL of the first link (a tag) in the row of the class activation. Before following this URL the index of the currently activated row and the URL of the current page will be stored to a cookie.

So, the user got to the next page... what now? Usually if you entered a record you aren't intrested in you'll use the backspace key to get back to the table and select the next interesting one. It's intuitive to think that the record you just activated is also still selected if you come back to select the next one... Well, this heavily depends on the kind of caching the browser performs. Because every browser has it's own way of caching pages this doesn't work very well and you'll get everything but not the table with the row selected you activated before.

To bypass this limitation the script saved the index of the last activated row and the URL of this page in a cookie. If the user now returns to the table the script reads the cookie and selects the row stored there which is the row the user activated just before leaving. This will only be done if the URL stored in the cookie matches the current URL. In case the user goes on to another page using this script the URLs don't match and the script will select the first row, not some strange row index from another table. You can disable this workaround by setting the cookie_name option to null.

Credits

This little script is build upon jQuery by John Resig and contributors.
It also includes the getPos and getScroll functions of the utility functions of Stefan Petres Interface Elements for jQuery.
Cookies are handled by the cookie handling functions form Quirks Mode written by Scott Andrew and edited by Peter-Paul Koch.
Roberto Rambaldi also added some nice things to it (namely the 0.4.0 release).

Much tanks to these guys, without them such a quickly (prototype done in about 4 to 6 hours) and clearly developed script wouldn't have been possible.
Special thanks to Roberto Rambaldi for contributing the 0.4.0 release.
Also special thanks to Antonio Biondo whos few, simple but genius changes led to the 0.5.4 release.

Changelog

The MIT License

Copyright (c) 2006 Stephan Soller <stephan.soller@helionweb.de>, Florian Seefried <florian.seefried@helionweb.de>.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.