Open main menu

CDOT Wiki β

BigBlueButton Accessibility Instructions

Revision as of 11:28, 3 March 2013 by JTRobinson (talk | contribs) (Shortcut Keys)
Bigbluebutton.png


Overview: How to develop accessible extensions, modules, and plugins for the BigBlueButton Flash client

Accessibility is an important part of the BigBlueButton application. Not only does it allow users with disabilities to run and participate in meetings, it's also mandated by law that if we CAN make it accessible, we HAVE to. For our purposes, disabilities fall into three main categories: Visual, Auditory, and Motor. This guide focuses mostly on Visual and Motor, simply because of the nature of BigBlueButton and what the CDOT team has run into so far.

With Visual and Motor disabilities there is a common concern of the user being able to navigate through the application, since use of a mouse may not be possible. You'll accommodate this by including your code in the tab order, and also by providing shortcut keys for all user functionality in your code. The rule is, if you can do it with a mouse, you need to be able to do it with a keyboard.

Some Visual disabilities require the user to have a screen reader, which is what it sounds like: an application that reads out the content of the screen to the user. In Flash, this is done mainly through the accessibilityName property. The rule here is, all relevant data on the screen must have an accessibilityName.

It may look like a lot of work to develop accessibly, but the majority of the work involved is strictly one-time setup. Once that groundwork is in your code, it becomes a very simple matter to "fill in the blanks" as you add more code.

Tab Order

This guide will start with the assumption that you are building an entirely new module.

Linking in your module

BigBlueButton already has an established core tab order, with each module given a baseTabIndex property. The convention is to establish your module's baseTabIndex in an Options class. For an example, examine the ChatOptions class at dev\bigbluebutton\bigbluebutton-client\src\org\bigbluebutton\modules\chat\model\ChatOptions.as.

Within the Options class, declare the baseTabIndex instance variable like so:

[Bindable] public var baseTabIndex:int;

In the constructor, make a link to your module's entry in BigBlueButton's config.xml. You can add a baseTabIndex property to the config entry if you want, to allow customization later. This code includes a default value in the event that baseTabIndex is not defined in your module config:

public function ChatOptions() {
   var cxml:XML =     BBB.getConfigForModule("ChatModule");
   if (cxml != null) {
      if (cxml.@baseTabIndex != undefined) {
         baseTabIndex = cxml.@baseTabIndex;
      }
      else{
         baseTabIndex = 701;
      }
   }
}
       
public function getBaseIndex():int{
   return baseTabIndex;
}

The core BigBlueButton modules have baseTabIndex values 100 elements apart, to accommodate future growth. Replace 701 in the else clause with another, more suitable number that reflects a logical place for your module.

For the existing default baseTabIndex values of the core BigBlueButton client, check \dev\bigbluebutton\bigbluebutton-client\README.

Lastly, determine which MXML file in your module is the "main" file. Continuing with the example of the Chat module, this is dev\bigbluebutton\bigbluebutton-client\src\org\bigbluebutton\modules\chat\views\ChatWindow.mxml. Import your Options class into that MXML with a standard import statement, and declare a baseIndex instance variable just as you did in your Options class, as well as an instance of your Options class, like so:

[Bindable] private var baseIndex:int;
[Bindable] public var chatOptions:ChatOptions;

If your MXML does not already call a method on initialization, add an initialize property to the MDIWindow tag like so:

<MDIWindow xmlns="flexlib.mdi.containers.*"
           xmlns:mx="http://www.adobe.com/2006/mxml"
           initialize="init()"
           >

Your MDIWindow will likely have far more properties than shown, these have been removed for clarity's sake.

In your initialize method (in this example, init()) initialize baseIndex with the getBaseTabIndex() method from your Options class:

private function init():void{
   baseIndex = chatOptions.baseTabIndex;
}

Establishing internal tab order

Your module now "knows" where it should sit in the general tab order; you now have to give each Flash component within your module a tabIndex property. This is based on the baseIndex variable in the MXML, so if the file you're working in does not have one, either re-read "Linking in your module" above or find a way to pass the baseIndex from the "main" MXML into the file you're working with.

Adding the main controls

If the MDIWindow you are working in doesn't have a creationComplete method, add one:

<MDIWindow xmlns="flexlib.mdi.containers.*"
           xmlns:mx="http://www.adobe.com/2006/mxml"
           creationComplete="onCreationComplete()"
           >

Your MDIWindow will likely have far more properties than shown, these have been removed for clarity's sake.

Each MDIWindow has a titlebar overlay, also known as the "main bar" of the window, showing the window's title. Generally, there is also a minimize button, maximize button, and close button. These items will come first in the internal tab order of the module, so in the creationComplete method, give each of them a tabIndex property:

private function onCreationComplete():void {
   titleBarOverlay.tabIndex = baseIndex;
   minimizeBtn.tabIndex = baseIndex+1;
   maximizeRestoreBtn.tabIndex = baseIndex+2;
   closeBtn.tabIndex = baseIndex+3;
}

You'll also want to add the accessibilityDescriptions to each item here, but ignore that for now.

All other Flash elements

Now that you have put the titlebar and control buttons into the tab order, continue through each element in the module and continue assigning tabIndex properties based on the baseIndex. The tab order within the module is up to your discretion, as long as the order is as sensible to a user who cannot see the screen as it is to a fully-sighted user.

You have already seen how to assign a tabIndex dynamically above. To assign a static tabIndex within the Flash component itself, write the property like so:

tabIndex="{baseIndex+4}"

Testing

Testing the tab order is very easy, simply focus into the application and continue pressing the Tab key until you see the focus indicator in the general vicinity of your contribution. Continue tabbing, and observe how the movement of the indicator lines up with how you expected it to move.

Screen Reader Compatibility

According to CDOT's research, there are two main screen-reader applications to be aware of: JAWS and NVDA. JAWS is proprietary software available for a fee; use your own judgement as far as acquiring a license for the software or ignoring it in your testing. NVDA, on the other hand, is the leading open-source screen-reader and completely free to download, use, and test with.

Before you can get any meaningful results from the screen-reader, you'll need to have your tab order sorted out. If you have not done so, please refer to the previous part of this guide.

Adding screen-reader compatibility is relatively simple, and revolves around the accessibilityName property, or in some cases the toolTip property. However, not all Flash components are compatible; for example, Labels are not accessible. Adobe has released a comprehensive guide on the subject; we will focus on BigBlueButton here.

One last thing to consider, is localization. All of your accessibilityNames must be in the locale file, so that the community can translate them to other languages and your module can be used worldwide.

The titlebar component of your MDIWindow needs an accessibilityName, as do the control buttons. See the Tab Order section above for the section were titleBarOverlay is given a tabIndex. In the same place, add this code:

titleBarOverlay.accessibilityName = ResourceUtil.getInstance().getString('bbb.exampleModule.titleBar.accessName');
minimizeBtn.accessibilityName = ResourceUtil.getInstance().getString("bbb.exampleModule.minBtn.accessName');
maximizeRestoreBtn.accessibilityName = ResourceUtil.getInstance().getString("bbb.exampleModule.maxBtn.accessName');
closeBtn.accessibilityName = ResourceUtil.getInstance().getString("bbb.exampleModule.closeBtn.accessName');

Dynamic accessibilityNames can be assigned the same way. For static components, the accessibilityName can be set within the component itself:

accessibilityName="{ResourceUtil.getInstance().getString('bbb.exampleModule.exampleComponent.accessName')}"

If you change an accessibilityName dynamically, be sure to call Accessibility.updateProperties() to force the screen-reader to update it's cached version of the application. The operation is fairly memory-intensive, so use it efficiently.

Testing

Testing can be done in a very similar manner to testing the tab order. Open your screen-reader of choice, start up the BigBlueButton application, and use the Tab key to navigate to your module. Listen to the reader's description of what is on the screen, and try to put yourself in the position of a blind or partially-sighted user. Turn your monitor off, if it helps. Also listen to the descriptions of established BigBlueButton core modules, and mimic the conventions there in your own accessiblityNames.

Shortcut Keys

The last main component of adding accessibility to your code is to add shortcut keys. Like hotkeys in any other application, these are key combinations that allow quick access to any feature. In BigBlueButton, there are two distinct sets of hotkeys: global and local. Each of these has it's own "modifier," depending on the browser, and an ASCII keycode. For example, the hotkey to focus the Presentation window has the keyCode 52, which translates to the number 4 key. In Firefox, since it is a global hotkey, the modifier is the Control key. So, Ctrl-4 focuses the Presentation window. The modifiers are in this table:

Browser Global Local
Firefox Ctrl Ctrl-Shift
Chrome Ctrl Ctrl-Shift
Explorer Ctrl-Alt Ctrl-Shift

Deciding what hotkeys to add

Essentially, you want to have a hotkey for each button in your module, as well as a hotkey to focus to each place where the user can provide input or make decisions, such as text input boxes, dropdown lists, or checkboxes. It may be useful for you to map these functions out on a spreadsheet, along with the key you want associated with them and the matching ASCII code.

Because the global and local hotkeys have separate modifiers, each module and the global scope have nearly the entire keyboard to work with. The modifiers were chosen because the major browsers have little or no functions already bound to them, however the W T and N keys are off-limits.

Shortcut Help Window

The Shortcut Help Window, found at dev/bigbluebutton-client/src/org/bigbluebutton/main/views/ShortcutHelpWindow.mxml, is an in-client guide to all hotkeys in the application. The first thing you have to do is add your keycodes into the locale files. For each keycode, you want both the ASCII value of the key and a plain-language description of what the hotkey does. For example, these two lines are the English locale entry for the "focus Presentation window" hotkey already mentioned:

bbb.shortcutkey.focus.presentation = 52
bbb.shortcutkey.focus.presentation.function = Move focus to the Presentation window.

NOTE: It is VERY important that it follow the same pattern of bbb.X, bbb.X.function. Otherwise, the Shortcut Window will not process it correctly. REMINDER: After any change to the locale file, you will need to recompile with ant locales.

Now, we edit the ShortcutWindow.mxml itself. First, in the instance variables you will see a set of ArrayLists (genKeys, presKeys, chatKeys, etc) and a set of corresponding Arrays. You want to create one of each, following the pattern you'll be able to see in the file. For example, let's say that your module lets a user fill in a text box and then click a button to submit their text, and it is called the Opinion module. The instance variables in your ShortcutWindow.mxml should look like this: /**/ symbol used to denote areas to really pay attention to.

private var genKeys:ArrayList;
private var presKeys:ArrayList;
private var chatKeys:ArrayList;
private var audKeys:ArrayList;
private var viewerKeys:ArrayList;
/**/private var opinionKeys:ArrayList;
/**/          
private var genResource:Array = ['bbb.shortcutkey.flash.exit', 'bbb.shortcutkey.focus.viewers', 'bbb.shortcutkey.focus.listeners',
                            'bbb.shortcutkey.focus.video', 'bbb.shortcutkey.focus.presentation', 'bbb.shortcutkey.focus.chat',
                            'bbb.shortcutkey.share.desktop', 'bbb.shortcutkey.share.microphone', 'bbb.shortcutkey.share.webcam',
                            'bbb.shortcutkey.shortcutWindow', 'bbb.shortcutkey.logout', 'bbb.shortcutkey.raiseHand',
                            /**/'bbb.shortcutkey.focus.opinion'/**/];
                            // Notice that the hotkey to focus to the module is in the global scope
                                             
private var presResource:Array = ['bbb.shortcutkey.present.focusslide', 'bbb.shortcutkey.whiteboard.undo',
                            'bbb.shortcutkey.present.upload', 'bbb.shortcutkey.present.previous', 'bbb.shortcutkey.present.select',   
                            'bbb.shortcutkey.present.next', 'bbb.shortcutkey.present.fitWidth', 'bbb.shortcutkey.present.fitPage'];
           
private var chatResource:Array = ['bbb.shortcutkey.chat.chatinput', 'bbb.shortcutkey.chat.focusTabs',
                            'bbb.shortcutkey.chat.focusBox', 'bbb.shortcutkey.chat.changeColour', 'bbb.shortcutkey.chat.sendMessage',
                            'bbb.shortcutkey.chat.explanation', 'bbb.shortcutkey.chat.chatbox.gofirst',
                            'bbb.shortcutkey.chat.chatbox.goback', 'bbb.shortcutkey.chat.chatbox.repeat',
                            'bbb.shortcutkey.chat.chatbox.advance', 'bbb.shortcutkey.chat.chatbox.golatest',  'bbb.shortcutkey.chat.chatbox.goread'];
                                             
private var audResource:Array = ['bbb.shortcutkey.listeners.muteme'];
           
private var viewerResource:Array = ['bbb.shortcutkey.viewers.makePresenter'];

/**/private var opinionResource:Array = ['bbb.shortcutkey.opinion.focusInput', 'bbb.shortcutkey.opinion.submit'];
/**/

Global Shortcuts

Testing

Local Shortcuts

Testing