Chapter 9. Ajax and WAI-ARIA

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.

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.

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:

In Bulletproof Ajax (New Riders), Jeremy Keith writes:

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]

alert

directory

main

radio

tab

alertdialog

document

marquee

radiogroup

tablist

application

grid

menu

region

tabpanel

banner

gridcell

menubar

row

textbox

button

group

menuitem

rowheader

timer

checkbox

heading

menuitemcheckbox

search

toolbar

columnheader

img

menuitemradio

secondary

tooltip

combobox

link

navigation

seealso

tree

contentinfo

list

note

separator

treegrid

definition

listbox

option

slider

treeitem

description

listitem

presentation

spinbutton

 

dialog

log

progressbar

status

 

[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 &amp;
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;
}

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:

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.

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 treeitem. Here, the user often asks for the container parent such as a folder name. In rich Internet applications, only a subset of treeitems may be available at any one time, and they may be added dynamically without modification to the DOM tree hierarchy—a common occurrence in Ajax applications.

Using aria-owns in the following example, the "dropbox" div is explicitly identified as a child of the "mine" div, even though "dropbox" is not an actual child in the DOM. WAI-ARIA calls "dropbox" an adopted child.

 
 --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>