Thursday, August 26, 2010

Create a focusable component

In a Flex application a focusable component implements the IFocusManagerComponent interface. UIComponent implements the methods of this interface, so your work is mostly done! However, since not all UIComponent's are intended to be focusable (containers, labels, etc.) UIComponent's class declaration does not state that it implements the interface.

Step 1: implement this interface by adding "implements IFocusManagerComponent" to your class definition

Note: in an MXML component you could do this w/the 'implements' attribute.

Step 2: set the appropriate values on the tab & focus related properties

This all depends on what your component does. The interesting properties are tabEnabled and tabChildren. Here some examples of how you might use them:

  • Your component gets focus, but not it's child objects: use tabEnabled=true, tabChildren=false
  • Your component does not get focused, some of it's child objects do: use tabEnabled=false, tabChildren=true
  • Your component gets focus, and so do some of it's children: tabEnabled=true, tabChildren=true
PropertyClassNotes
tabEnabledInteractiveObject Whether the user can tab navigate to this component. UIComponent sets this to 'true' if the component implements IFocusManagerComponent
tabIndexInterativeObject Optional, specifies the index of this component in the tab order. After all I've learned, I say avoid using this. More on this later!
tabChildrenDisplayObjectContainer Whether or not child objects of your component are able to be tabbed to. The default is true.
focusEnabledUIComponent Whether the component can receive focus when tabbed to. The default is 'true'.
hasFocusableChildrenUIComponent Flex 4 only! Similar to the tabChildren property, docs say that Flex apps should not use tabChildren.
mouseFocusEnabledUIComponent Whether or not the component can be focused with the mouse. This is a strange property b/c somewhere else some other class seems to decide whether or not to honor it. Default is 'true'.
tabFocusEnabledUIComponent Flex 4 only! Similar to tabEnabled property. Default is 'true'.

Step 3: Implement keyboard support

Making something focusable means that keystrokes will be sent to your component when it has focus. So go the distance and add sensible keyboard support to your component.

When your component has focus, it's keyDownHandler() and keyUpHandler() methods will be called as the user is typing. Override the keyDownHandler() method and you're off to the races.


override protected function keyDownHandler(event:KeyboardEvent):void
{
    switch(event.keyCode)
    {
        case Keyboard.ENTER:
            dispatchEvent( new MouseEvent(MouseEvent.CLICK) );
            break;
  // and so on
    }
}

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.