Wednesday, August 25, 2010

30 Days of Accessibility

For the last month or so, I've been delving into the topic of accessibility in Flex. I've learned a lot, and know there is still plenty more to learn (there always is). But it's already changed how I approach building an app or component.

I've decided to post something (hopefully) useful about accessibility once a day for a month. 30 days seemed like a good challenge, and it's about how long I been a learnin'. So here I go.

Event listeners and keyboard navigation

Why am I starting here? I think there's plenty of info already available, it's how I got here. This is actually something I would never have thought twice about. But maybe you should.

Watch what you listen for!

Pun intended. Here's an example with ComboBox. It dispatches ListEvent.CHANGE when the selection changes, and DropDownEvent.CLOSE when the drop down list closes. When the user is navigating with the keyboard, there is a big difference between these two events!

The CHANGE event will be dispatched each time the user uses the arrow keys to move the selection in the drop down list. This would fire your listener many times, when the user pressed down arrow to choose something from the drop down list. It's not a problem when using the mouse.

Instead, listen for the CLOSE event (DropDownList.CLOSE) and know that your listener will fire when the user finishes interacting w/the ComboBox.

Be careful about where you add your event listeners

Let's say your itemRenderer is a Canvas with some labels and pretty pictures. Now you add a CLICK listener to the canvas (or even the image). However, there's no way to trigger a CLICK event on this itemRenderer with using only the keyboard :(

Note, I've been working on a Flex 3 project lately, but I don't think this is any different in Flex 4. My solution was to extend List and override keyDownHandler(). When the ENTER key is pressed, I dispatch ListEvent.ITEM_CLICK. ITEM_CLICK is already dispatched when you use the mouse, problem solved!


override protected function keyDownHandler(event:KeyboardEvent):void
{  
   super.keyDownHandler(event);
   // the super classes return on these conditions, this should too
   if (!selectable || !iteratorValid || !collection || itemEditorInstance )
      return;
   // our own reason to do nothing
   if (selectedIndex == -1)
      return;
   
   switch (event.keyCode)
   {
      case Keyboard.ENTER:
         dispatchEvent( new ListEvent(
            ListEvent.ITEM_CLICK, false, false, -1,
            selectedIndex, null,
            indexToItemRenderer(selectedIndex) ) );
         break;
   }
}
// now listen for ITEM_CLICK being dispatched by the list
// in your listener, use the event's itemRender property
// to get at the selected item: event.itemRenderer.data

The lessons I learned

Know what events a component dispatches with respect to mouse and keyboard interaction. Then make sure your event listeners can be triggered using keyboard navigation.

Bonus points
  • Look at the keyDownHandler() method of any UIComponent to see how it processes keystrokes. keyUpHandler is available too, but not necessarily as useful (Button uses it for auto-repeating keystroke support)
  • If you don't want to read code, you can read some Adobe docs to see how keyboard navigation works for each component.

1 comment:

  1. I should note that after implementing this, I decided to dispatch my own event rather than re-use ListEvent.ITEM_CLICK. Listening for ITEM_CLICK means that clicking anywhere in your renderer will trigger your listener, which may not be your intent.

    ReplyDelete