Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Creative tools for business
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / Flash Developer Center /

Creating ActionScript 3.0 components in Flash – Part 8: Keyboard support

by Jeff Kamerer

Jeff Kamerer

Created

9 June 2008

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
ActionScript best practice components Flash Professional keyboard

Requirements

Prerequisite knowledge

To get the most from this article series, you should be familiar with Flash Professional, including how to manipulate the Timeline, the Property inspector, the Components panel, the Components inspector, the Library, and the Actions panel.

User level

Beginning

Required products

  • Flash Professional (Download trial)

Sample files for Part 8

  • component_keyboard_support.zip (666 KB)

Welcome to Part 8 of the article series on creating components using ActionScript 3.0. This part continues from the previous segment, where you discovered how to control the focus of the elements within the MenuBar component. In this article I'll cover how to add keyboard support so that users can negotiate through the menu using key commands, as well as their mouse.

For your reference, here are all the parts in this series:

  • Part 1: Introducing components
  • Part 2: MenuBar component prototype
  • Part 3: From prototype to component
  • Part 4: Events
  • Part 5: Styles and skins
  • Part 6: Invalidation model
  • Part 7: Focus management
  • Part 8: Keyboard support
  • Part 9: Shim compiled clip

Whenever you implement focus management for your component, you should always add keyboard support as well. When a user tabs to a component, they will expect to be able to continue using the keyboard to select items within it. For the purposes of our sample project, I added keyboard support to the MenuBar component. I updated the code to allow the arrow keys to navigate the menus, the Escape key to close the menus, and also added functionality so that the Spacebar and Enter keys can be used to select a menu item.

The details of keyboard support for the MenuBar component are sufficiently complicated that I wrote much of this part to provide the full explanation of how this works.

Handling keyDown and keyUp events

If your component needs to handle either the keyDown or keyUp event, you do not need to add your own listener because UIComponent registers keyDownHandler() and keyUpHandler() and you can override these methods. The UIComponent implementations do nothing, so you do not always need to call the super implementation of these methods. If your component extends a class which implements these methods, for example fl.controls.LabelButton, then you might want to call the super implementation; this is a case by case decision you'll need to make by examining the code and stepping through each line of code with the debugger.

I overrode keyDownHandler() to handle all keyboard events. The general form to take for a KeyboardEvent handler is to switch on the event's keyCode for special keys like Escape, Shift, F1–F12, and the arrow keys and match their values against the constants defined in flash.ui.KeyBoard. To handle alphanumeric and punctuation input, you can convert the keyCode to a String with the String.fromCharCode() method. For alphabetic keys this approach always returns the upper case value, and for other keys it always returns the value you would get without holding the Shift key down.

A simple keyDownHandler() might look like this:

override protected function keyDownHandler(e:KeyboardEvent):void { switch (e.keyCode) { case Keyboard.UP: trace("up"); break; case Keyboard.DOWN: trace("down"); break; case Keyboard.LEFT: trace("left"); break; case Keyboard.RIGHT: trace("right"); break; } switch (String.fromCharCode(e.keyCode)) { case '1': trace("We're number one!"); break; case 'A': trace("A is for apple"); break; case 'B': trace("B is for banana"); break; case 'C': trace("C is for coconut"); break; } }

Adding keyboard support to MenuBar

(The rest of Part 8 is rather advanced. You can skip to Part 9 if you wish.) Up until this point, our MenuBar component has relied completely on mouse events to open and close menus. The component currently has no way of tracking or displaying a menu item selected with the keyboard.

Enabling selectable

To remedy this, I enabled selection by setting the selectable property to true on the MenuBarTileList instance and all of the MenuList instances, undoing one of the very first changes I had made when creating the prototype in Part 1 of this article series.

Rather than making the selection persistent, I wanted it to last only as long as the keyboard was active in the menu bar or in the specific drop-down menu. Because of this, I left the code in place that set selectable = false and only set the value to true when the controls become active, then immediately setting the value back to false when they are closed. This part was easy to implement, since I already had methods to manage hiding and showing the drop-down menus, as well as opening and closing the menu bar completely. You can see the changes I made to the code below:

private function openMenuBar(menuToOpen:List):void { // enable selectable for keyboard support myMenuBar.selectable = true; // open the List drop down menu hideAllMenusExcept(menuToOpen); ... } private function closeMenuBar():void { // close all menus hideAllMenusExcept(null); // reset the state of keepMenuOpen, just to make sure it isn't left funky keepMenuOpen = false; // disable selection when menu bar closed myMenuBar.selectedIndex = -1; myMenuBar.selectable = false; ... } private function hideAllMenusExcept(except:List):void { for (var i:int = 0; i < myMenus.length; i++) { var theMenu:List = myMenus[i] as List; if (theMenu == except) { theMenu.visible = true; theMenu.selectable = true; } else { theMenu.visible = false; theMenu.selectedIndex = -1; theMenu.selectable = false; } } }

Since MenuBar does not have any selectable skins, I also changed the custom cell renderers for the menu bar and the drop-down menus to use the over skins instead. I copied the code from the implementation of BaseButton, which was being used by the cell renderer classes currently, and changed it slightly. To illustrate the changes, I've included the code for both the MenuCellRenderer and the BaseButton below:

/* * MenuCellRenderer version */ override protected function drawBackground():void { var styleName:String = (enabled) ? mouseState : "disabled"; if (selected) { styleName = "over"; } styleName += "Skin"; var bg:DisplayObject = background; background = getDisplayObjectInstance(getStyleValue(styleName)); addChildAt(background, 0); if (bg != null && bg != background) { removeChild(bg); } } /* * BaseButton version */ protected function drawBackground():void { var styleName:String = (enabled) ? mouseState : "disabled"; if (selected) { styleName = "selected"+styleName.substr(0,1).toUpperCase()+styleName.substr(1); } styleName += "Skin"; var bg:DisplayObject = background; background = getDisplayObjectInstance(getStyleValue(styleName)); addChildAt(background, 0); if (bg != null && bg != background) { removeChild(bg); } }

I changed MenuBarCellRenderer to extend MenuCellRenderer because it needed the exact same code to fix drawBackground(). I could have copied and pasted the code into both classes, but that would have created a maintenance problem, and to follow best practices, this is really an ideal situation to leverage inheritance:

public class MenuBarCellRenderer extends MenuCellRenderer {

You may be getting tired of hearing me say this, but I couldn't have achieved this part of the development process without reading and debugging the ActionScript source for the User Interface components. Being able to copy that code and make minor changes to it was a huge time saver as well.

keyDownHandler()

To understand the keyDownHandler() method and its auxiliary method, dispatchItemSelectedEvent(), you can read the commented code below:

/* * keyboard handling */ override protected function keyDownHandler(e:KeyboardEvent):void { // keyboard support does nothing if there are not any drop-down menus if (myMenus.length < 1) return; var theMenu:List; // if the menu bar is not open and up or down key was hit, then open it if (myMenuBar.selectedIndex < 0) { // enable selectable myMenuBar.selectable = true; // first switch on key to determine which drop-down menu should be opened switch (e.keyCode) { case Keyboard.UP: case Keyboard.DOWN: case Keyboard.RIGHT: myMenuBar.selectedIndex = 0; break; case Keyboard.LEFT: myMenuBar.selectedIndex = (myMenuBar.length - 1); break; } // open the drop-down menu openMenuBar(myMenus[myMenuBar.selectedIndex]); theMenu = myMenus[myMenuBar.selectedIndex] as List; // now switch to see whether the first or last item in the // drop-down menu should be selected switch (e.keyCode) { case Keyboard.RIGHT: case Keyboard.LEFT: case Keyboard.DOWN: theMenu.selectedIndex = 0; break; case Keyboard.UP: theMenu.selectedIndex = (theMenu.length - 1); break; } // done! return; } // this code path is hit if the menu bar was already open switch (e.keyCode) { case Keyboard.UP: // the up key moves the drop-down menu selection up, // or down to the bottom if the selection is at the top theMenu = myMenus[myMenuBar.selectedIndex] as List; if (theMenu.selectedIndex <= 0) { theMenu.selectedIndex = (theMenu.length - 1); } else { theMenu.selectedIndex--; } break; case Keyboard.DOWN: // the down key moves the drop-down menu selection down, // or up to the top if the selection is at the bottom theMenu = myMenus[myMenuBar.selectedIndex] as List; if (theMenu.selectedIndex < 0 || (theMenu.selectedIndex + 1) >= theMenu.length) { theMenu.selectedIndex = 0; } else { theMenu.selectedIndex++; } break; case Keyboard.LEFT: // the left key closes the currently opened drop-down menu // and opens the one immediately to its left, or if the // leftmost menu was open then it opens the rightmost menu if (myMenuBar.selectedIndex <= 0) { myMenuBar.selectedIndex = (myMenuBar.length - 1); } else { myMenuBar.selectedIndex--; } theMenu = myMenus[myMenuBar.selectedIndex] as List; hideAllMenusExcept(theMenu); theMenu.selectedIndex = 0; break; case Keyboard.RIGHT: // the right key closes the currently opened drop-down menu // and opens the one immediately to its right, or if the // rightmost menu was open then it opens the leftmost menu if (myMenuBar.selectedIndex < 0 || (myMenuBar.selectedIndex + 1) >= myMenuBar.length) { myMenuBar.selectedIndex = 0; } else { myMenuBar.selectedIndex++; } theMenu = myMenus[myMenuBar.selectedIndex] as List; hideAllMenusExcept(theMenu); theMenu.selectedIndex = 0; break; case Keyboard.SPACE: case Keyboard.ENTER: // space or enter will dispatch a menu event if a // drop-down menu item is selected dispatchItemSelectedEvent(); closeMenuBar(); case Keyboard.ESCAPE: // escape will close the menu without selecting any items closeMenuBar(); break; } } private function dispatchItemSelectedEvent():void { // get the menu bar index and label var menuIndex:int = myMenuBar.selectedIndex; // we will not dispatch an event if no menu bar item is selected if (menuIndex < 0) return; var menuLabel:String = myMenuBar.dataProvider.getItemAt(menuIndex).label; // get the drop-down menu item index and label var theMenu:List = myMenus[myMenuBar.selectedIndex] as List; var itemIndex:int = theMenu.selectedIndex; // we will not dispatch an event if no drop-down item is selected if (itemIndex < 0) return; var itemLabel:String = theMenu.dataProvider.getItemAt(itemIndex).label; // dispatch the event dispatchEvent(new MenuEvent(MenuEvent.ITEM_SELECTED, false, false, menuIndex, menuLabel, itemIndex, itemLabel)); }

Coordinating mouse input and keyboard input

Once I had written the keyDownHandler() method and made other code changes to get selection working in the MenuBar component's subcomponents, keyboard support was working great, but it was not interacting very well with mouse support. After selecting Control > Test Movie and doing some tests, I noticed that if I started changing the menu selection with the keyboard and then switched to using the mouse, I saw multiple menu items in the over state. If I started navigating through the menu with the mouse and then tried to take over the control with the keyboard, the next item in the menu was not selected properly. This issue occurred because the keyboard support was driven by selection, but the mouse support did not currently interact with selection at all.

To resolve this issue, I added some code to menuBarMouseHandler() to make sure that mouse interactions were setting the menu bar selection. The updated version of the code is shown below, with some of the comments removed:

private function menuBarMouseHandler(e:MouseEvent):void { var cellRenderer:ICellRenderer = e.target as ICellRenderer; if (cellRenderer == null) return; switch (e.type) { case MouseEvent.MOUSE_DOWN: var theMenu:List = getChildByName(cellRenderer.data.label) as List; openMenuBar(theMenu); // see MOUSE_UP handling below for discussion of keepMenuOpen keepMenuOpen = true; // set selectedIndex to improve interaction between // mouse support and keyboard support myMenuBar.selectedIndex = cellRenderer.listData.index; break; case MouseEvent.MOUSE_OVER: theMenu = getChildByName(cellRenderer.data.label) as List; hideAllMenusExcept(theMenu); // set selectedIndex to improve interaction between // mouse support and keyboard support myMenuBar.selectedIndex = cellRenderer.listData.index; break; case MouseEvent.MOUSE_UP: // this event will only be hit on the first mouseUp after the // first mouseDown which opened the menus. We need to handle // this to prevent the stage mouseUp listener from closing the // menus. We could stop the stage listener from getting the // event at all by calling e.stopPropagation(), but since this // will eventually become component code that could be used in // an arbitrary application, it seems dangerous to stop event // propagation since we do not know what sort of event handling // the user might be coding on top of ours. So instead we use // the keepMenuOpen:Boolean. myMenuBar.removeEventListener(MouseEvent.MOUSE_UP, menuBarMouseHandler); keepMenuOpen = true; break; } }

I had to start listening for mouseOver events on the drop-down menu MenuList instances, adding the listeners in openMenuBar() and removing them in closeMenuBar() like I was already doing for other event listeners on the MenuList instances. I added code to menuMouseHandler() to handle this event. I was also able to simplify the event dispatching code, by using the same private method to dispatch the itemSelected event for both keyboard and mouse interaction:

private function menuMouseHandler(e:MouseEvent):void { var cellRenderer:ICellRenderer = e.target as ICellRenderer; if (cellRenderer == null) return; switch (e.type) { case MouseEvent.MOUSE_UP: // dispatch event and close menu bar dispatchItemSelectedEvent(); closeMenuBar(); break; case MouseEvent.MOUSE_DOWN: // keep stage listener from closing the menus // more on use of keepMenuOpen in comment // for mouseUp event in menuBarMouseHandler() keepMenuOpen = true; break; case MouseEvent.MOUSE_OVER: // set selectedIndex to improve interaction between // mouse support and keyboard support var theMenu:List = cellRenderer.listData.owner as List; theMenu.selectedIndex = cellRenderer.listData.index; break; } }

After these changes to the code were implemented, everything about the behavior of the MenuBar component was almost working, but I found that I was still having cosmetic problems when I started control with the mouse and then took over control with the keyboard. After making some tests, I identified the problem. The over skin would remain on a menu item as long as the mouse was hovering over it, even if I moved the selection to another menu item. I fixed this issue with another change to MenuCellRenderer, and it was only necessary to apply the fix in one place because I altered MenuBarCellRenderer to subclass this class:

override protected function drawBackground():void { var styleName:String = (enabled) ? mouseState : "disabled"; if (selected) { styleName = "over"; } else if (styleName == "over") { styleName = "up"; } styleName += "Skin"; var bg:DisplayObject = background; background = getDisplayObjectInstance(getStyleValue(styleName)); addChildAt(background, 0); if (bg != null && bg != background) { removeChild(bg); } }

Where to go from here

In the last part of this article series, you'll examine how to work with compiled clips. We'll put the finishing touches on our MenuBar component by ensuring that when developers use your components, their FLA files will publish quickly. I'll provide some tips and resources to help you optimize your components before distributing them and also touch on some compatibility issues that you should consider when developing components.

To learn more about how to handle events and control keyboard input with ActionScript 3.0, see Introduction to event handling in ActionScript 3.0.

More Like This

  • Controlling web video with ActionScript 3 FLVPlayback programming
  • Controlling the appearance of text elements with the Text Layout Framework
  • Optimizing content for Apple iOS devices
  • Creating ActionScript 3.0 components in Flash – Part 6: Invalidation model
  • Creating ActionScript 3.0 components in Flash – Part 7: Focus management
  • Creating ActionScript 3.0 components in Flash – Part 9: Shim compiled clip
  • Modeling User Workflows for Rich Internet Applications
  • Common mistakes working with fonts and text objects in Flash
  • Avoiding common Timeline errors in Flash
  • Using timeline labels to dispatch events with the ActionScript 3.0 TimelineWatcher class

Flash User Forum

More
04/23/2012 Auto-Save and Auto-Recovery
04/23/2012 Open hyperlinks in new window/tab/pop-up ?
04/21/2012 PNG transparencies glitched
04/01/2010 Workaround for JSFL shape selection bug?

Flash Cookbooks

More
02/13/2012 Randomize an array
02/11/2012 How to create a Facebook fan page with Flash
02/08/2012 Digital Clock
01/18/2012 Recording webcam video & audio in a flv file on local drive

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement