User-defined accesskeys

The drawbacks of accesskeys are well documented (external link), but one way of mitigating those drawbacks is to allow the user to define their own accesskeys for a site. In the absence of an established standard this is the best compromise - those who do make use of the functionality can do so, those who have problems with application or OS conflicts can disable them.

The implementation I'll be describing here is a server-side solution, using PHP. Before I started work on this I was aware of the work done by Rich Pedley and Gez Lemon (external link), but hadn't looked at their scripts. I wasn't aware of the work done by Thierry Koblentz (external link), and only found his implementation when searching for Rich and Gez's. Rich and Gez are clearly much classier coders than I, having provided an OOP solution - my version is totally procedural.

So why bother producing yet another script when there are already at least two out there? Two primary reasons - firstly I wanted to provide user-defined accesskeys at Accessites.org (external link) and at my day job  (external link), and that meant I needed to be totally comfortable and intimate with the code and the way it worked; secondly it's a great learning exercise to try to reproduce something someone else has already produced, and then to compare and contrast.

Key features

The script

The script is very easy to install and use:

  1. Download the code (1k), or copy and paste it from below and save it as accesskeys.inc.php.
  2. Edit the $accesskeypages array. This array contains the URLs of the pages you wish to provide accesskeys for. Each URL has an associative array defining the token (internal name), default accesskey (can be left empty), and label (displayed on the user form).
  3. Include the script on every page where you want accesskeys to be available, ideally via a global or header include file. As it sets a cookie it must be included before output is sent to the user's browser.
  4. Pass a URL to the output_accesskey() function and it will return a string containing the accesskey for that URL if one has been set. For example:
    <?
    $navigation_links = array("Home" => "/index.php", "Contact" => "/contact/", "Accessibility" => "/accessibility/");
    echo '<ul>';
    foreach ($navigation_links as $label => $url) {
      echo '<li><a href="' . $url . '"' . output_accesskey($url) . '>' . $label . '</a></li>';
    }
    echo '</ul>';
    ?>
    

To display the form for a user to set their accesskeys call the output_acesskeys_form() function. For example:

<?
include "accesskeys.inc.php";
include "header.inc.php";
echo output_accesskeys_form();
include "footer.inc.php";
?>

The script contains no styling, so you'll probably want to add some classes or ids to the output_acesskeys_form() function and apply CSS accordingly.

Possible enhancements

There are a few easy enhancements which could be made - providing suggested keys and a button to implement these, and sanity-checking a user's choice of key (for example detecting and warning of duplicates) to name two.

In the wild

The script can be seen in action in two places at the time of writing:

If you do use the script please let me know and I'll add the site to the list.

The script

<?
// accesskeys.inc.php
$accesskeypages = array("/index.php" => array("token" => "home", "default" => "", "label" => "Home", "suggested" => "1"),
    "/accessibility/" => array("token" => "accessibility", "default" => "", "label" => "Accessibility", "suggested" => "0"),
    "/contact/" => array("token" => "contact", "default" => "", "label" => "Contact", "suggested" => "9")
);
if ($_POST["accesskeys"]) {
    $setaccesskeys = array();
	if ($_POST["submit"]) {
		for ($x=0; $x < count($_POST["accesskeys"]); $x++) {
			$setaccesskeys[$_POST["token"][$x]] = $_POST["accesskeys"][$x];
		}
	} else if ($_POST["suggested"]) {
		foreach ($accesskeypages as $accesskeypage) {
			$setaccesskeys[$accesskeypage["token"]] = $accesskeypage["suggested"];
		}
	}
    setcookie("accesskeys", base64_encode(serialize($setaccesskeys)), 2147483647, "/");
    header("Location: " . $_SERVER['PHP_SELF'] . "?ak=1");
}
if ($_COOKIE["accesskeys"]) {
    $useraccesskeys = unserialize(base64_decode($_COOKIE["accesskeys"]));
} else {
    foreach ($accesskeypages as $akarray) {
        $useraccesskeys[$akarray["token"]] = $akarray["default"];
    }
}
function output_accesskey($url) {
    global $accesskeypages;
    global $useraccesskeys;
    if ($useraccesskeys[$accesskeypages[$url]["token"]] != "") {
        return ' accesskey="' . $useraccesskeys[$accesskeypages[$url]["token"]] . '"';
    }
}
function output_accesskeys_form() {
    global $accesskeypages;
    global $useraccesskeys;
    $akform = '';
    if ($_GET["ak"]) {
        $akform .= '<p>Your accesskeys settings have been saved.</p>';
    }
    $akform .= '<form action="' . $_SERVER['PHP_SELF'] . '" method="post"><fieldset><legend>Current settings</legend>';
    foreach ($accesskeypages as $akarray) {
		$akform .= '<div><label for="' . $akarray["token"] . '">' . $akarray["label"];
		if (isset($akarray["suggested"])) $akform .= ' <em>Suggested key: ' . $akarray["suggested"] . '</em>';
		$akform .= '</label> ';
        $akform .= '<input type="text" maxlength="1" size="3" name="accesskeys[]" id="' . $akarray["token"] . '"';
        if (isset($useraccesskeys[$akarray["token"]])) $akform .= ' value="' . $useraccesskeys[$akarray["token"]] . '"';
        $akform .= ' /><input type="hidden" name="token[]" value="' . $akarray["token"] . '" /></div>';
    }
    $akform .= '</fieldset><div><input type="submit" value="Set Accesskeys" name="submit" /> <input type="submit" value="Clear Accesskeys" name="reset" /></div></form>';
    return $akform;
}
?>

Comments

That's an excellent script, Dan. I would suggest adding the "results" message heading and "clear message" link, and have the script return to SELF+resultsID, and then have the "clear message" link return to SELF+formID, like we did at Accessites.org.

As an aside, a pre box with a defined width with overflow:auto (with height:auto) would be awesome for this post as the code runneth over :)

Posted by: Mike Cherim at February 7, 2006 10:57 PM

Very nice work ;-)

Posted by: Blair at February 8, 2006 7:49 AM

Perfect, Nice work.

Posted by: Grant Broome at February 8, 2006 3:30 PM

Nice stuff Dan - good to see Loc gov moving forward in terms of accessibility (and not just ticking the boxes!). Thought your readers might like to know that there is also asp versions of Gez's script (for those that have to play nicely with MS )and that I have implemented Thierry's script on Eden District Council's site (www.eden.gov.uk/accesskeys/).

Posted by: Julian Scarlett at March 7, 2006 3:33 PM

Thanks Julian, and good work at Eden. We're at 2 of 468 local gov sites and counting!

Posted by: Dan at March 7, 2006 5:45 PM

Why not just an extension/ widget for common browsers, which reads headers and turns then into access keys?

Posted by: Andy Mabbett at August 18, 2006 7:36 AM

Damn - I wrote "link headers", using angle brackets, and the HTML monkeys ate it...

Posted by: Andy Mabbett at August 18, 2006 7:59 AM

That would certainly be a more user-friendly solution Andy, allowing the same keys to be set once and used across sites. That consistency would limit site owners in the range of pages they could set keys for, although that would probably be a good thing.

It also limits the implementation to browsers which support extensions, and depends on the user installing them. The main benefits of the server-side solution are that it's cross-browser and requires no additional installation.

Of course ultimately what's needed is an agreed standard for keyboard shortcuts to common pages, which the browser makers can embed into their interfaces. I'm not holding my breath. :-)

Posted by: Dan at August 18, 2006 8:35 AM

>That would certainly be a more
>user-friendly solution Andy, allowing
>the same keys to be set once and used
>across sites.

That's the general idea ;-)
[...]
>It also limits the
>implementation to browsers which
>support extensions

To a point, though that's not to say that, later, browser makers couldn't include it as a native function.

>The main benefits of the server-side >solution are that it's cross-browser >and requires no additional
>installation.

Conversely, the disadvantage is that a user's settings only apply to one site - not to any they may subsequently visit, until they've gone through the set up again, and especially not to sites they're visiting for the fisrt and perhaps only time,

>Of course ultimately
>what's needed is an agreed standard
>for keyboard shortcuts to common
>pages

From what I've read elsewhere (especially Accessify Forum, yesterday and today, see comment about user from Finland: http://www.accessifyforum.com/viewtopic.php?t=5889), I doubt that's even possible.

Posted by: Andy Mabbett at August 18, 2006 6:42 PM

Post a comment

Personal information