Quick accessibility fix with jQuery

IE 7 has inherited the IE 6 problem with keyboard navigation and inline anchors. The otherwise time consuming fix was made simple with little a JavaScript – made even easier with jQuery. JavaScript to improve accessibility! Who would have thought…

The accessibility problem

By using a keyboard I mean tapping the tab key to move from link to link through a web page. This method works well to get until you want to skip some of the content by following a link to an anchor that is within the content of the page. The page will scroll down appropriately to the location of the anchor, but tapping tab once more to keep moving doesn’t always do what you may expect. Instead of moving the focus to the next link after the anchor it will move to a different link somewhere near the top of the page – often scrolling the page back up without regard for where you thought you were at in the page. This is disorienting to me – imagine what it is like for someone reliant on a screen reader to tell them what is happening on the screen!

Two kinds of anchor

The true nature of this problem seems to be obscured by the complexity of it’s triggers. Rather than attempt to figure it out definitively I’ll just stick to the problem I was experiencing. There are two kinds of destinations for same page anchors in my project:

The workaround

To cut a longer story short you can fix it by wrapping a span around each inline anchor;

<span class='anchor'><a id='contentsection'></a></span>

and add a line to your CSS:

.anchor {display:absolute;}

Markup baggage

Unfortunately the site I am working on has many hundreds of these anchors peppered through it’s content. Each one has been individually inserted by our client with the link tool sported by WYSIWYG toolbar of the site’s CMS. Even if we hacked the code of the toolbar to change the mark-up it produced when inserting anchors. It would be necessary to reinsert every one.

Using jQuery to add the markup

I was considering pestering one of our developers to do a bit of database search and replace monkeying when I realised how easy it would be to add the extra code with some jQuery driven JavaScript.

If you are like me – HTML and CSS savvy, but not quite up with the pack in the JavaScript department – jQuery is the shit when it comes to DOM scripting JavaScript libraries. I hope this little example helps communicate my enthusiasm for it.

The code

$(document).ready(function(){
    $("a[@id]").not("[@href]").wrap("<span class='anchor'></span>");
});

That’s it! With the (pleasantly trim) jQuery library loaded, this script will find all the anchors in the page that have an id but no href and enclose each of them in a span with a class.

Links in the chain

To the uninitiated the code above will look arcane. Broken down it’s simple.

  1. This code ensures that any functions it contains are not fired until the DOM is ready – generally well before all the content (especially images) of the page is loaded.

    $(document).ready(function(){
    
    });
    
  2. Firstly I need to specify which elements in the document I wish to modify. Let’s start with the most basic expression before narrowing it down. This tells jQuery (represented by the ‘$’) to find every <a> in the document. Just as in CSS where a{}; refers to every <a>.

    $(document).ready(function(){
        $("a");
    });
    
  3. With a ‘selector’ defined I can simply add to the chain to manipulate each of those elements. This will add a span around every <a>:

    $(document).ready(function(){
        $("a").wrap("<span class='anchor'></span>");
    });
    
  4. Of course I don’t want to do that! This would do crazy things to the regular links in my page. To discriminate between the links and anchors I need to be more specific.

    My anchors each have an id assigned which I’m pretty sure the regular navigation links don’t have. So I can use that to refine the selector expression for jQuery. Adding the square brackets immediately after the ‘a’ in the expression provides a context to check something about this element in particular. Inside which, the @ refers to an attribute of that element, in this case it’s id attribute. All up a[@id] selects every <a> with any id:

    $(document).ready(function(){
        $("a[@id]").wrap("<span class='anchor'></span>");
    });
    
  5. Now my list of <a> elements in the page only includes those that have an id. I’m pretty sure I don’t have any regular links with ids… To be absolutely sure it would be a good idea to remove all the elements from this list that have a href.

    This isn’t done by further refining the expression like I have done so far. Instead I am adding a not() to the chain – between the $() and the wrap(). This whittles down the list of elements found by the initial expression – removing any that match this further check before the list is passed along down the chain for ‘wrapping’. The ‘not’ expression should look familiar now – except this time we are checking for href instead of id. Also notice there is no need to include the a in this expression. The list of elements is already limited to those that have passed the first test:

    $(document).ready(function(){
        $("a[@id]").not("[@href]").wrap("<span class='anchor'></span>");
    });
    

Perhaps my explanation has puzzled your curiosity sufficiently to look at jQuery for yourself. Check out the official site. It has excellent documentation and astounding plugins. Even AJAX looks easy with jQuery!

Medicine. but not a cure

This coding cleverness doesn’t resolve the keyboard navigation problem for those of us unlucky enough to need to use Internet Explorer without JavaScript. Nor does it solve the same problem for Safari users – with or without JavaScript! Misuse has earned JavaScript a bad reputation when it comes to web accessibility. I hope this does a little to demonstrate it can be a power for good too.

Further thoughts

21 November 2006

Seems this script breaks some anchors altogether in IE 6. So much for enhancing accessibility! I’ll be sure to post something here when I get to the bottom of it.