In the last chapter, we showed you how to add JavaScript to your HTML and CSS to make a web application. In this chapter, we discuss adding Accessible Rich Internet Application (WAI-ARIA, or ARIA for short) properties and other techniques for making JavaScript and Ajax accessible.
We have yet to realize the non-disability-specific benefits of ARIA, although we expect to see the mobile market take advantage of many of the ARIA features. For example, drag-and-drop is something that is not widely supported across mobile devices, even on the iPhone (as of late 2008).
A report from Informa Telecoms & Media (2007) predicts that 50% of the world’s population will have access to the Internet via a mobile device by 2010. Many of these devices will not support scripting or embedded objects. The majority of currently available mobile devices have a screen resolution of 240x320 pixels, and the iPhone isn’t much bigger at 480x320. Therefore, with smaller screen sizes, rich applications designed for desktop environments are not likely to fit entirely on the screen.
These similarities—constraints on viewport size and lack of a mouse—should cause mobile developers to take a look at WAI-ARIA and other accessibility APIs. The techniques you are looking for may already exist.
Designing web applications without Ajax, while more complicated than our earlier exercises with simple HTML, is still pretty straightforward. In addition to what you’ve learned about images, forms, tables, frames, and the like, you will need to take a look at how your site is implemented at the server level, as well as how you are using script to make your users’ lives easier.
Once you’ve taken stock of all the script and server magic you use to glue your site together, separate it into three categories: code that works well universally, code that can be made to work universally, and code that needs to be a workaround.
You may look at a script-based feature in your site and find no problems with it either in mobile devices or assistive technology. In some cases, the site may even function better than it would otherwise; for example, it may take fewer keystrokes to enter data. Not only is this the best outcome, it’s what we hope you’ve learned to do with all your code after reading this book. Part of universal design is building intelligence into software components so that they can be reused again and again. Why redo work you’ve already done?
Let’s say that you evaluate your web application, and it doesn’t work on a class of mobile devices. Is it a limitation of the technology, or could it in fact be modified or rewritten to work elsewhere? This is your chance to build a better toolkit: where the need and the ability meet.
Sometimes, there is no easy way to make a component work everywhere, particularly with assistive technology. It could be that this code is too brittle to be modified, or it’s part of a library that you can’t change, or you simply don’t have time to rewrite it properly.
In these cases, it’s important that the key function of that code is available some other way, be it through a server transaction or through what we call graceful degradation. (This is the flip side of progressive enhancement, which we discuss later.)
On the desktop, Ajax is now old hat. All major browsers have full
support for XmlHttpRequest
, the core
object of Ajax, which fetches data and allows a developer to insert new
information into a document without a page refresh. Until recently (the
dawn of the iPhone era), device-based browsing has lacked support. With
a limited ability to interact with Ajax controls, it is imperative to
practice progressive enhancement.
Moreover, people authoring Ajax-based controls must understand
that even with Ajax support in web-enabled devices, mouse interaction
may be extremely limited. For example, how do you execute a mouseover
with a touchscreen, which registers
only taps? The Nokia N800 series is an excellent example. Although, it
does have a Mozilla-based browser and Flash 9, mouseover
events are totally unreliable
on the device.
How has Ajax been supported in assistive technology? Not well.
Remember that while JavaScript is in some ways supported by assistive
technology, it’s still reading HTML as a format for more or less static
documents. Further complicating matters is how Ajax came to be—through
Microsoft’s introduction of an ActiveX control called XMLHTTP. (The
XmlHttpRequest
object has since been
submitted to the W3C for formal standardization.)
Today’s assistive technology can’t deal well with asynchronous data being inserted, and so nearly all Ajax-based content comes with accessibility problems. As a result, the accessibility techniques traditionally associated with Ajax have been in the vein of progressive enhancement. In other words, the techniques provide users a way to opt out of the rich experience. Jeremy Keith coined the term “Hijax” to describe this approach.
If people had imagined when XmlHttpRequest
was created that a brew of
HTML, script, and asynchronous XML data would become a leading method
for creating web user interfaces, they did a great job keeping the rest
of us in the dark. Assistive technology depends on an understanding of
the semantics of any given object you see on the screen: specifically,
an object’s name and description, but also its role
and state.
Role and state can be most easily described as what a control does and its current configuration. In HTML, the roles and states for links and form controls are well defined, and we rely almost solely on this information for the purposes of accessibility. For example, the checkbox role has states of checked and unchecked. We take this kind of contract between the language and its behavior for granted when it comes to working well with assistive technology.
But what if I want to create a slider control? HTML doesn’t define a control and (before ARIA) we were not able to assign roles and states to controls we created using HTML alone.
ARIA is the fastest-moving advance in web accessibility. It’s supported in all major browsers: IE 8, Mozilla 3, Opera 9.5, and Safari 4. Frameworks including Dojo, Google Web Toolkit, and the Yahoo! User Interface Library have built-in support, and the makers of the JAWS and Window-Eyes screen readers have implemented ARIA as well. It doesn’t stop there. Communities are building support in open source assistive technologies, such as Orca and NVDA. Google implemented AxsJAX, scripts that inject web pages with ARIA properties. Charles Chen, who authored AxsJAX, also created Fire Vox, an add-on to Firefox that makes it a self-voicing browser. This vocabulary is the key to unlocking Ajax accessibility.
That said, ARIA has not reached W3C Recommendations status, which means the current specification could (and likely will) change; and while many browsers and assistive technologies have begun implementing ARIA, these implementations are not complete and not all users have these new versions.
Since this is a new area, we have not addressed all of the important and revolutionary concepts introduced with ARIA. For the latest information, refer to the WAI-ARIA Overview (http://www.w3.org/WAI/intro/aria) and the Code Talks wiki (http://wiki.codetalks.org).
Before delving into examples of how to implement ARIA, it’s important to know exactly what makes it necessary. ARIA functions as a bridge between web and software accessibility. All of this information—role and state, live regions, focus management—corresponds to the typical information required in a software application.
Most of this is well covered in HTML. Every form field in HTML
4.01, for example, has a role in Microsoft Active Accessibility (MSAA)
that clearly corresponds. It’s the new controls introduced by the scripting
frameworks that make ARIA useful. Let’s say someone has developed a
combo box by grafting a text field onto a select box. Or maybe he
created the entire control out of an empty div
and
some DOM scripting. The way to turn that blob of HTML into something
semantically useful again is to define its roles and states in ARIA. To
put it another way, ARIA exists so that you can express semantics that
can’t be expressed in HTML alone.
Plan for Ajax from the start. Implement Ajax at the end.[21] | ||
--Jeremy Keith |
The progression for designing Ajax applications is to make the page work at each of these stages with each of these technologies:
HTML—the basic structure of the application
CSS—layout and style
Server-side scripts (PHP, Python, ASP, Ruby on Rails, etc.)—adding or changing content on HTTP requests and page refresh
Ajax—getting rid of page refresh to add and change content (using Hijax as necessary, ensuring keyboard access to all functionality)
ARIA—set roles on unidentifiable objects, create programmatically determinable relationships, and keep states and properties up-to-date
In Bulletproof Ajax (New Riders), Jeremy Keith writes:
Before Ajax, a Web site worked like a self-service restaurant. Every time you wanted some information, the browser had to fetch a new page. In a self-service restaurant, whenever you want some food, you have to go up to the counter to order it.
Adding Ajax to a Web site is like hiring a waiter for a restaurant. The customer no longer needs to go to the counter to order food. The waiter will take the order to the counter instead. This results in a much more pleasant dining experience.
Just because you’ve hired a waiter doesn’t mean you can fire the cook. Yet this is exactly what some Ajax applications attempt to do. Not content with having a waiter take orders and bring food, they get the waiter to do all the cooking too.
Cooking should happen in the kitchen. Application logic belongs on the server. It’s better for everyone that way. Your application will work more consistently when it is server-based. The browser environment is simply too unpredictable.
This unpredictability will only increase as your audience accesses your site from more devices, in more situations, and with a growing range of personal preferences. Whether it be due to security reasons or device capabilities, you cannot assume everyone in your worldwide audience will experience Ajax. Layering the experience in a single application so that the environment can transform to meet everyone’s needs. Creating a single flexible application decreases your maintenance costs because all of the various end-user experiences run off of the same code on the server. Keep in mind that a “single flexible application” does not mean making your site work with a single style sheet or set of scripts. Once your application has a solid HTML foundation and the bulk of the functionality is on the server, you can tack on as many CSS and client-side scriptable building blocks as you like. See Chapter 4 for more on creating CSS for different devices.
In HTML, most of the elements have a defined role and behavior:
an a
is a link, h1
is a first-level heading, and so on.
However, because HTML was primarily designed as a document markup
language, there are many objects that HTML does not define. Examples
include div
and span
, which can be used to do any number of
things from grouping elements into a section to creating a button. As
such, assistive technologies don’t know what to make of them. ARIA
provides over 50 roles to choose from, as shown in Table 9-1.
Table 9-1. WAI-ARIA roles[22]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
| |
[22] http://www.w3.org/TR/wai-aria/#roles—February 4, 2008 Working Draft |
In the previous chapter, we showed a menu with
submenus. Marking the parent ul
as a menubar and each list item as a menu item will let the user agent
map the role to one of the objects in the OS-level accessibility API.
Roles can be used like classes in that a single object can have
multiple roles, and as with classes you use a space-separated list. In
the case of our menu, it is a structural item and needs only one
role:
<ul role="menubar"> <li role="menuitem"><a href="http://bellevuecollege.edu/about/" class="first" id="first">About BCC</a> <ul role="menu"> <li role="menuitem"><a href="http://bellevuecollege.edu/about/goals/">Mission & Goals</a></li> <li role="menuitem"><a href="http://bellevuecollege.edu/about/around/">Getting Around</a></li> ... </ul>
As with HTML elements, after you have chosen one, you can tweak it with a variety of attributes. With ARIA roles, we have several properties to choose from. In the case of the menubar, the following are the available properties:
activedescendant
atomic
busy
channel
controls
expanded
live
relevant
templateid
The global states are also available:
datatype
describedby
dropeffect
flowto
grab
haspopup
hidden
labelledby
owns
ARIA properties must be prefixed with aria-
as in aria-haspopup
—as you
will see in our code examples. There is one exception: role does
not need the aria
prefix.
When discussing ARIA properties in text (as in the previous
paragraph), we will refer to properties without the prefix.
In the following example, we initially want the menu to be
collapsed (aria-expanded="false"
) and want it to
appear in the tab order (tabindex="0"
, although because we’re
using links, they will appear in the tab order as active HTML
elements). The aria-haspopup
property causes a screen reader to notify a person that submenu items
exist:
<ul role="menubar"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" tabindex="0"><a href="http://bellevuecollege.edu/about/" class="first" id="first">About BCC</a> <ul role="menu"> <li role="menuitem"><a href="http://bellevuecollege.edu/about/goals/">Mission & Goals</a></li> <li role="menuitem"><a href="http://bellevuecollege.edu /about/around/">Getting Around</a></li> ... </ul>
In the last chapter, we started handling keyboard support, but one thing was missing. It is very difficult to track and change focus in today’s browsers using only HTML and scripting. Here’s another venue where ARIA can shine.
Since a mouse has only a few events that it can fire, it’s not surprising that mouse events are well handled and standardized: when you hover over an object more information about it might pop up, or when you click on an object it does something. Keyboard conventions aren’t so well developed. A standard keyboard typically has 101 keys (give or take a few) that can be pressed 1 or 2 or 3 at a time. Mac keyboards differ from Windows keyboards differ from Linux keyboards; English keyboards differ from French keyboards differ from Japanese keyboards. I’ve lost track of how many possible keyboard combinations that is….
There are several commonly used practices on Windows—Alt+F typically opens an application’s File menubar. Pressing the Enter key activates selected items, like submitting a form. The Escape key usually allows you to back out of an operation. Arrow keys are used to navigate menus.
However, there are OS-specific keyboard commands, browser-specific keyboard commands, as well as assistive technology-specific. Add on top of those application-specific keyboard commands, and managing keyboard collisions has become an art form in the assistive technology world—and is one of the requirements of Section 508.
Unfortunately for Ajax, it is a relatively new concept. If you
define a new component, you will have to define its keyboard support.
WAI-ARIA Best Practices describes optionKeyEvent
—a
function to handle keystrokes for a widget—at http://www.w3.org/TR/2008/WD-wai-aria-practices-20080204/#accessiblewidget.
Luckily, there are lots of folks writing scripts to handle this behavior, which can be borrowed, or they are building it into widgets in the toolkits. Example 9-1 shows some of the keyboard support from the Mozilla bare-bones “spreadsheet” example (http://www.mozilla.org/access/dhtml/spreadsheet).
Example 9-1. JavaScript for connecting keyboard commands to widgets
Menubar.prototype.doNavigation = function(event) { var bEventContinue = true; // browser can still use event var key = event.keyCode; if ((key >=KEY_LEFT && key <= KEY_DOWN) || key == KEY_ESCAPE) { var idx = this.getActiveMenuIndex(); if (idx < 0) { idx = 0; } // if subIndex > −1 a submenu is open - up and down movement will occur in it var subIndex = this.getActiveSubIndex(); var subMenuId = this.getActiveSubOpen(); var menus = this.getMenus(); var menuLen = menus.length; var activeMenuId = menus[idx]; var curDrop = document.getElementById(activeMenuId + DROP_SUFFIX); if (key == KEY_LEFT || key == KEY_RIGHT) { var nextIdx = idx; if (key == KEY_LEFT) { // left nextIdx = (idx + (menuLen-1)) % menuLen; } else { // right nextIdx = ((idx + 1) % menuLen); } // if drop menu is displayed, close it and open the next one or move into submenu var bShowNext = this.isActiveOpen(); if (bShowNext) { // current drop is open, see if it has submenu if (curDrop) { if (subMenuId != null) { // submenu is open - close it this.hideSubMenu(); if (key == KEY_LEFT) { // find parentItem and set focus to it gFocusItem = document.getElementById(subMenuId); setTimeout("doFocus();",0); return false; } // if right just close and move to next top level menu (which happens below) } // end of submenu open if (key == KEY_RIGHT && subMenuId == null) { // if right arrow and submenu are not open, get current item in the drop-down and see if it has a submenu var itemIdx = this.getActiveItemIndex(); var menuItems = this.getMenuItems(activeMenuId); var curItemInDrop= document.getElementById(menuItems[itemIdx]); var bHasSubMenu = this.hasSubMenu(curItemInDrop); if (bHasSubMenu == true) { return this.showSubMenu(event,curItemInDrop.getAttribute ("id")); } } // if haven't returned yet - then close the current drop curDrop.style.display = "none"; // only one menudrop child per menu this.setActiveItemIndex(0); } } this.setActiveMenuIndex(nextIdx); var nextMenuId = menus[nextIdx]; gFocusItem=document.getElementById(nextMenuId + TOP_SUFFIX); if (bShowNext == true) { var drop = document.getElementById(nextMenuId + DROP_SUFFIX); if (drop) { drop.style.display="block"; var menuItems = this.getMenuItems(nextMenuId); gFocusItem=document.getElementById(menuItems[0]); } } setTimeout("doFocus();",0); } if (key == KEY_UP || key == KEY_DOWN ) { var itemIdx = this.getActiveItemIndex(); var bOpen = this.isActiveOpen(); if (curDrop) { // first see if submenu is open if (subMenuId != null) { // submenu is open - move within it var subMenus = this.getSubMenuItems(subMenuId); var subLen = subMenus.length; var nextSubIndex =subIndex; if (key == KEY_DOWN) { nextSubIndex = (subIndex +1) % subLen; } else if (key == KEY_UP) { nextSubIndex = (subIndex + (subLen - 1)) % subLen; } gFocusItem = document.getElementById(subMenus[nextSubIndex]); this.setActiveSubIndex(nextSubIndex); setTimeout("doFocus();",0); return false; } // end of if submenus - back to regular processing var menuItems = this.getMenuItems(activeMenuId); var itemLen = menuItems.length; var nextItemIdx; if (!bOpen) { // open and set to the first item this.showMenu(event, activeMenuId, true); itemIdx = −1; } if (itemIdx == −1) { nextItemIdx = 0; } else if (key == KEY_DOWN ){ nextItemIdx = (itemIdx +1) % itemLen; } else { //UP nextItemIdx = (itemIdx + (itemLen-1)) % itemLen; } this.setActiveItemIndex(nextItemIdx); gFocusItem=document.getElementById(menuItems[nextItemIdx]); setTimeout("doFocus();",0); } // end of id curDrop } // end of id up/down // need to prevent propagation of arrow keys /* try { ev.preventDefault(); } catch (err) { try { ev.returnValue=false; } catch (errNext) { ; } }*/ bEventContinue = false; if (key == KEY_ESCAPE ) { this.closeMenu(event); bEventContinue = false; if (gFocusBeforeMenus) { gFocusItem = gFocusBeforeMenus; setTimeout("doFocus();",0); } else { document.focus(); } } // end of if KEY_ESCAPE } // end of if arrow or escape key return bEventContinue; }
Part of making your keyboard handlers work properly is managing
focus changes as a user interacts with your application. Sometimes you
will need to move focus to a particular element, whereas other times
you will be watching for focus changes. For complex widgets, such as a
tree structure, you’ll want the parent to be in the tab order, but all
of the children don’t have to be. In this case, put a tabindex
on the container element and use
activedescendant
to change the
focus between children. See the following resources to learn
more:
WAI-ARIA Best Practices, Section 3.2 Providing Keyboard Focus (http://www.w3.org/WAI/PF/aria-practices/#kbd_focus)
Keyboard-navigable JS widgets (http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets)
In their article “Making Ajax Work with Screen Readers” (http://juicystudio.com/article/making-ajax-work-with-screen-readers.php), Gez Lemon and Steve Faulkner describe how screen readers use a virtual buffer to interact with elements displayed onscreen. The virtual buffer is usually only updated on page refresh or when the user changes modes. As screen readers get better at handling updates, one of the features in ARIA is the ability to tell a component when and how to present the user with updates as they happen.
ARIA defines four priority settings:
off
Do not update the user.
polite
Update the user when he is not busy.
assertive
Update the user as soon as possible, but do not interrupt the user.
rude
Update the user immediately, interrupting everything, including the last rude update.
For example, as you edit a tweet in Twitter, the number of
characters left is updated as you type. If you are sighted, how often
do you glance at that number? Probably only when you pause to edit
your tweet. Therefore, in this example you would use aria-live="polite"
so that when you pause,
your assistive technology can update you.
On the other hand, if you were about to send an email to everyone in your address book, you would want an alert to interrupt you as soon as it detected the possibility. Be careful with the assertive and rude roles, though. It may seem like a good idea to have your clock widget reading out every second, but after about half a minute of listening to the screen reader chattering, you will quickly realize the error of your ways. Most of the time in ARIA, as in life, polite is the way to go.
Building on the bare-bones spreadsheet, we can add a function to
sort the columns when selected. Using Stuart Langridge’s sorttable
script (as described at http://woork.blogspot.com/2008/02/sort-table-rows-using-ajax.html),
we need to make the spreadsheet a live region. We make it “assertive,”
as we don’t need to interrupt the user, but we do want to notify her
that the sort is finished. (We also add class="sortable"
to use Stuart’s
script.)
<table tabindex="0" id="table2" class="sortable" role="grid"
aria-live="assertive" aria-activedescendant="row1col1" >
Using Unobtrusive JavaScript, attach onkeydown
and onfocus
event handlers to the table.
If a component controls another part of the web page or
application, explicitly identify this relationship using aria-controls
. For someone with sight, you
will likely have created visual cues to alert them to changes in
content. Using the aria-controls
attribute will allow a person using an assistive technology to follow
the same path and find the updated content.
The flowto
attribute is a
more elegant approach to setting reading order in an application.
Instead of setting a numeric tabindex
value (which can become a serious
pain when the numbers are too close to one another), this attribute
allows you to point directly to the next control in the reading order.
And unlike tabindex
, it’s possible
to have multiple controls pointing to the same item (say, the subtotal
in a shopping cart), which is perfect for marking up a flowchart, as
shown in Figure 9-1.
Assume we use CSS to position the images, using this HTML:
<img src="start.png" id="start" alt="start" flowto="question-90" /> <img src="90-question.png" id="question-90" alt="The 90's?" flowto="no yes" /> <img src="no.png" id="no" alt="no" flowto="stop1" /> <img src="yes.png" id="yes" alt="yes" flowto="stop2" /> <img src="stop.png" id="stop1" alt="stop"/> <img src="stop.png" id="stop2" alt="stop" flowto="hammertime collaborate" /> <img src="hammertime.png" id="hammertime" alt="hammertime"/> <img src="collaborate.png" id="collaborate" alt="collaborate" flowto="listen" /> <img src="listen.png" id="listen" alt="listen"/>
While this is a fun example, it would be more relevant to look at a spreadsheet and specify a reading order through the cells to guide someone through an interpretation of the data. Also note that as of this writing, no screen reader supports this attribute.
Another feature in ARIA is the ability to point from a control
to its label and description. We know about the label
element discussed in Chapter 5: it defines a label, and binds it to a control.
The aria-labelledby
attribute is the inverse of that: it assigns the control to the ID of
a label elsewhere in the document. The aria-describedby
attribute lets you assign a
description to a given control as well.
Figure 9-2 shows
the form created by the following code. When someone using a screen
reader enters the form, the first description
(groupdesc1
) should be read. When the focus moves
to the Check Now button, its description (buttondesc1
) should be read, giving the
person additional information about the button.
<fieldset aria-describedby="groupdesc1"> <legend>Order tracking</legend> <div role="description" id="groupdesc1"> Order tracking has been enabled for all shipments outside of Texas. For information on orders to Texas, please call 1-555-HELP-NOW for an automated recording. </div> <div> <label for="tracknum">Tracking number</label> <input id="tracknum" type="text"/> </div> <div> <span role="button" tabindex="0" aria-describedby="buttondesc1" > Check Now </span> <p role="description" id="buttondesc1"> Check to see if your order has been shipped. </p> </div> </fieldset>
Using Unobtrusive JavaScript, attach onkeydown
and onclick
event handlers to the button.
Chapter 5’s discussion of error handling outlines a few issues to watch out for when making forms accessible, including identifying required fields, alerting the user to input errors, and indicating which fields contain errors. WAI-ARIA contains two properties and HTML has one role to address these issues:
aria-required="true"
Indicates that a field is required.
aria-invalid="true"
Indicates that the field is not valid.
role="alert"
Indicates that a section is playing the role of an alert.
There is some JavaScript trickery required to make this appear
as an alert (adding it to the DOM with the role of alert
should cause a browser to fire
an alert event to an assistive technology).
Marco Zehe’s blog “Easy ARIA tip #3: aria-invalid and role ‘alert’” ties all of these concepts together in a tutorial with working examples (http://www.marcozehe.de/2008/07/16/easy-aria-tip-3-aria-invalid-and-role-alert/).
Before ARIA, if a control, such as a “previous page” button, was
created with an image link, the only way to disable it was to get rid
of the href
attribute:
<a><img src="disabled-first-page.gif" alt="First Page"/></a> <a><img src="disabled-prev-page.gif" alt="Previous Page"/></a>
It is an ugly hack.
Now, we can use the ARIA disabled
attribute:
<span role="button" aria-disabled="true" id="FirstButton">
<img src="disabled-first-page.gif" alt="First Page"/>
</span>
Here’s an example of an input element from the Dojo Toolkit. The
code is for the text area after the text onChange:
.
Note the different syntax when dealing with Dojo—disabled
doesn’t require a name/value
pair:
<input id="oc1" disabled value="not fired yet!" autocomplete="off">
Figure 9-3 shows a screenshot of this form.
Figure 9-3. Screenshot of disabled input area from http://archive.dojotoolkit.org/nightly/dojotoolkit/dijit/tests/form/test_Spinner.html
As you create new nodes and add them to the DOM, make sure you
add them at an appropriate place or use aria-owns
to create a meaningful reading
order.[23]
[T]he DOM enables assistive technologies to request the
“parent” of the object on which the user is working. However, the
DOM hierarchy is acyclic—each node may have one and only one parent,
making it impossible to represent all parent/child relationships
within a single hierarchy. An example is a Using | ||
--WAI-ARIA Best Practices, Section 5.2.1 |
<div role="treeitem" aria-owns="mine">My Documents<div> ... <div id="dropbox" role="group"> <div role="treeitem">Chapter 1</div> <div role="treeitem">Chapter 2</div> </div>