I created a singleton to handle accessibility chores for a flash application. The goal of the class is to centralize the checking for assisting devices (like screen readers) and update accessibility properties once the existence of a device has been determined. This demonstration will also touch on a few finer points about making flash applications accessible.
You can download the project here. It is a flash develop project, but you can just create a flash file and set the application to start Main in the stage's "Properties" window inside the fla.
Before digging into the project, here is an example of assigning an object an accessible name and description for a screen reader or other device to use:
if (Accessibility.active){
var object:Sprite = new Sprite();
object.accessibilityProperties = new AccessibilityProperties;
object.accessibilityProperties.name = "name screen reader will read";
object.accessibilityProperties.description = "description screen reader will read"
Accessibility.updateProperties();
}
Sticking points with this process are:
- Accessibility.active can only be determined after a certain amount of time has passed for it to be accurate, as devices like screen readers take a while to communicate with flash once a page is loaded. This call is expensive -- and really need only be performed once.
- The method Accessibility.updateProperties() -- required to send accessible information to the assisting device -- can't be called until Accessibility.active is true.
- It is preferable to create as many objects as you can with their respective accessibilityProperties before triggering the Accessibility.updateProperties() method, as this method requires a bit of overhead as well.
The singleton class I created aims to delay the checking for devices and then updating the properties one time only, after device availability has been confirmed. The singleton then holds a boolean -- available to all classes -- to avoid calling the expensive check with the Accessibility class again. If further objects need to be created with accessible properties, the singleton already knows if an assisting device is present.
Quick Overview
In my demo project, the Main class creates two objects which require accessibility information. The objects are placed on the stage and accessibility properties are applied to each with the AccessibilityManager.
The demo also illustrates how to cancel functionality that may not be acceptable for use with a screen reader -- in this case, the auto-hide functionality, as you might have with a control bar on a full-screen movie. Making objects invisible is not going to serve a non-sighted user well, so this functionality is not set when an assisting device is confirmed. The observer pattern is used to notify the Main class when the accessibility check is completed.
Finally, the demo briefly introduces the tabbing manager which repositions objects for an assisting device's script, despite where they appear on the screen.
Breaking Down the Demo
The two objects constructed in our project are adAccDisplay, a dynamic TextField inside a Sprite, and btnShowAcc, a Sprite that serves as a button to make the display show whether an assisting device is active and communicating with Flash.
Before constructing any objects, however, the Main constructor creates the AccessibilityManager singleton:
Main.as
var accManager:AccessibilityManager = AccessibilityManager.getInstance();
As no instance of AccessibilityManager has been created prior, AccessibilityManager creates an instance of itself and ensures further attempts to instantiate the class will return this instance.
AccessibilityManager.as
public static function getInstance():AccessibilityManager {
if (AccessibilityManager._instance == null){
AccessibilityManager._instance = new AccessibilityManager(new PrivateClass);
trace("AccessibilityManager Instantiated");
}
return AccessibilityManager._instance;
}
The AccessibilityManager constructor calls accCheck() which will call checkAccessibility() only after 2100 milliseconds have passed (per our timeout constant). The optional IAccessibilityListener parameter registers an observer -- we'll go into that later.
AccessibilityManager.as
public function accCheck(ob:IAccessibilityListener = null):String {
if (!_accChecked) {
if (ob){
_aListeners.push(ob);
}
setTimeout(checkAccessibility, timeout);
_currentAccessibility = AccessibilityManager.UNCHECKED;
}
return _currentAccessibility;
}
We need a delay before checking accessibility, because it takes a few seconds for a device like a screen reader to be visible to the Flash application after loading a web page. The goal is to get this boolean only once to save overhead. Our singleton class AccessibilityManager keeps the result (_accessibilityActive), which can effectively be retrieved by any other class in the application. It becomes, more or less, a "global" variable.
While the timeout is counting down, Main is continuing to build components. Our AccessibilityDisplay creates a TextField which will show the accessibility status. It gets the same instance of the the AccessibilityManager and adds accessibility properties for itself with the addAccessibility() method.
AccessibilityDisplay.as
tf = new TextField();
tf.border = true;
tf.width = 200;
tf.height = 20;
addChild(tf);
accManager = AccessibilityManager.getInstance();
accManager.addAccessibility(this, "Accessibility status:");
Looking at the addAccessibility method, we see there are arguments for the InteractiveObject, the name of the object, the description of the object, and whether the object is silent.
AccessibilityManager.as
public function addAccessibility(obj:InteractiveObject, accName:String, accDesc:String = null, silent:Boolean = false):void {
var accProp:AccessibilityProperties = new AccessibilityProperties();
accProp.name = accName;
if (accDesc && accDesc != ""){
accProp.description = accDesc;
}
if (silent){
accProp.silent = true;
} else {
obj.tabEnabled = true;
}
obj.accessibilityProperties = accProp;
}
Each object is given an instantiation of AccessibilityProperties with the applicable information. The silent argument assigns whether an assisting device will tell the user it is on the stage. If the object is not silent, it will be automatically tabEnabled. This can be reset afterword by simply calling:
theObject.tabEnabled = false;
Dynamic text, unless rendered silent, is always read by the screen reader. Putting the TextField in a Sprite with a AccessibleProperties.name gives the screen reader user context of what they are looking at. In this case "Accessibility status:" gives context for what follows in the text box. Instead of just reading "active," the screen reader will now read "Accessibility status:, active." Another example of where you might want to do this is a text field reading the total time for an mp3. Instead of the screen reader just reading "3:16," a text field inside a Sprite could be made to say "Total Time, 3:16."
The assigning of names and descriptions for "display" could have been performed at the Main level instead of inside the "display" object. This is exactly how we are assigning accessibility properties for btnShowAcc :
Main.as
var btnLabel:String = "show accessibility";
btnShowAcc = new RoundButton(btnLabel);
btnShowAcc.x = 50;
btnShowAcc.y = 150;
accMgr.addAccessibility(btnShowAcc, btnLabel);
checkAccessibility();
The button (a Sprite) will be read as "show accessibility" by the screen reader when encountered. As btnShowAcc has a buttonMode of true, most screen readers will say "button" after reading its accessibilityProperties.name. In this case, the screen reader will read: "Get Accessibility Button" when we tab to it. You don't want to give the name property a String having "button" in it -- if you give this parameter the string "play button," the screen reader will read "play button button."
If you look inside RoundButton (our button's base class), you see that we're making a custom label for the button, which is essentially a TextField.
RoundButton.as
private function makeTextField(str:String):TextField {
txt=new TextField();
addChild(txt);
...
(AccessibilityManager.getInstance()).addAccessibility(txt, "", "", true); // set text field as silent
return txt;
}
Notice that the text is set to silent. If the TextField is not set to silent, the screen reader will read: "Get Accessibility Button, Get Acessibility." This is needlessly confusing. With the TextField set to silent, the screen reader will just read "Get Accessibility Button" for the object.
Another purpose of the AccessibilityManager class is to keep track of tab order. Most of this portion of code was written by Charles Brandt. The setTab, overrideTab, getTab, enableTab, and showTabList methods are pretty well documented in the class code, but here's the skinny:
- setTab sets tabEnabled=true with a tabIndex of the next available tab. If a specific index is given, it pushes any item at that index ahead to the next available index.
- overrideTab sets tabEnabled=true to a specified index, but writing over the index rather than reassigning what might be there.
- getTab returns the tabIndex for an object.
- enableTab sets tabEnabled=true for an object.
- showTabList is a diagnostic tool to trace all the objects and their tabIndex .
This application demonstrates using setTab() in Main.as.
Main.as
accMgr.setTab(accDisplay, 1);// accDisplay will have tabIndex of 1
accMgr.setTab(btnShowAcc, 1);// button will have tabIndex of 1 and accDisplay will be pushed back to 2
accMgr.showTabList();
The accDisplay object is given a tabIndex of 1, but then is pushed back to 2 when the btnShowAcc is given the same index of 1. This can be seen in your output window thanks to showTabList(). If the objects were tab-enabled, but not given indexes, Flash would automatically assign indexes based on their position, starting at the top left and working down and across. Now, our button will still be the first object tabbed to, even though btnShowAcc is below accDisplay . The accDisplay object doesn't actually need an index... its contents will still be read by the screen reader even if it is not tab-enabled because it has an AccessibilityProperties.name. Any text in a TextField or TextBox is always going to be read unless it is silent.
Finally, we add a listener to the button to get our Accessibility.active status from the AccessibilityManager singleton. This is written in our display's TextField by the buttonClickHandler():
Main.as
private function buttonClickHandler(e:MouseEvent):void {
accDisplay.updateText((AccessibilityManager.getInstance()).accCheck());
}
While we've been constructing these objects, the accessibility check has been waiting to fire. When and if the AccessibilityManager determines there is an assisting device, it calls Accessibility.updateProperties() to register the information with the device. Any further changes you make to an object's accessibilityProperties after the 2.1-second timeout will not be received by a device like a screen reader until this method is called again. The method is expensive, so we want to limit the times we call it. Be aware that the user will not necessarily know things have changed after an update unless they refresh the view on their device or stumble upon the change while moving the cursor.
Even if you are pretty sure your objects will be built with AccessibilityProperties before the timeout expires, you can always call the AccessibilityManager.update() method to be safe. If the singleton's _accCheck() returns "unchecked" when you call it, Flash's Accessibility.updateProperties() will automatically be performed by the AccessibilityManager if and when its check finds a device. No matter when you try and update(), the singleton will never crash the application by unnecessarily calling Accessibility.updateProperties().
Main.as
var btnLabel:String = "show accessibility";
btnShowAcc = new RoundButton(btnLabel);
btnShowAcc.x = 50;
btnShowAcc.y = 150;
accMgr.addAccessibility(btnShowAcc, btnLabel);
checkAccessibility();
In this scenario, however, we need the AccessibilityManager to notify our Main class of the check's outcome. We do this through the IAccessibilityListener interface, which has a registration method called checkAccessibility():
Main.as
// register with AccessibilityManager singleton
public function checkAccessibility():void {
var accActive:Boolean = false;
if (accMgr.accCheck(this as IAccessibilityListener) == AccessibilityManager.ACTIVE) {
accMgr.update();
}
}
// AccessibilityManager notifies if accessible device is active after its timeout calls check
public function setAccessibilityActive(accActive:Boolean):void {
setAutoHide(accActive);
}
In the checkAccessibility() method, we are doing a few things. We are ensuring that our button's accessibilityProperties update if the singleton's check was already completed. We are also registering Main to be notified when the result comes in via setAccessibilityActive(), if "unchecked." Our application withholds implementing our auto hide functionality until the notification arrives from the AccessibilityManager singleton.
Checking Our Work
You should QA your work with a screen reader, like Jaws, NVDA, or WindowEyes (which has a free, time-limited demo). If you don't have a screen reader, you should download aDesigner -- a free tool and a real timesaver. When you open aDesigner, choose Flash as your mode and put the url of your html page in the Address bar. You must have an HTML page being served to test accessibility. You should notice that the button and text box do not dissappear when using aDesigner, but do hide if you are just viewing with a browser while no assistive software is running.
Look in aDesigner's GUI Event and GUI Summary panes to see what Flash is going to tell an assisting device about your swf via its Accessibility class:
In the GUI Summary window, we can see what Flash is going to tell a device like a screen reader in our application based on MSAA. Note that all devices and software are slightly different and this information is at their mercy, so to speak. Upon first viewing, it appears the screen reader is going to say something along the lines of:
AccesibilityManagerSingleton Window
Flash movie start
show accessibility Button
Graphic Accessibility status:
Flash movie end
Notice that the "show accessibility" button is read BEFORE our "Accessibility status:" field -- this is due to the tab order we set for each object. If we took out the lines of code assigning tab indexes, the script would be flipped and "Accessibility status:, graphic" would be heard before "show accessibility, Button." As a note, it is good practice to assign a low tabIndex to controls a user will want to get to quickly, no matter where on the screen they are.
If we click our "show accessibility" button before our 2.1 seconds, the "display" text is going to change to "unchecked." If it is clicked after the timeout with aDesigner running, "active" will appear in the field. Notice that our GUI summary does not change. When using a screen reader, users can refresh their view and the device will re-read the accessible items. In aDesigner, hit the opposing gold arrows and your GUI summary will refresh.
You now see the script for the device has been updated to "Flash movie start, show accessibility Button, Accessibility status: Graphic, active, Flash movie end."
Now that the "display" text field has text in it, a screen reader is going to speak it. Because we gave an accessible name to its parent container, the text has context for the non-sighted user with an assisting device.
Note that if other objects are going to appear on the stage later (e.g., if the button were to cause a MovieClip to appear), the addAccessibility() and the update() methods would need to be called again via the AccessibilityManager. I kept these methods unlinked because you may want to add properties for several objects and then update(). The main point of the AccessibilityManager is that it keeps you from calling Accessibility.active again, no matter where you are in the application.
Download project