Hack 96 Add Key Shortcuts to Your Site 
To make your application easier and faster to
use (and more like a desktop application), associate a keystroke or
key combination with each button in your SWF.
Adding keyboard shortcuts to each of
your buttons makes your site more accessible. This hack shows how to
implement shortcuts with a minimum of code. Detecting keypresses is a
little more complex than just looking for a key being pressed. We
have to be able to detect keyboard combinations, such as Ctrl-Q, and
detect both when the key is pressed and when it is released.
Detecting Keypresses
To detect the state of the keyboard, you first have to set up a
listener object that listens to the Key class.
The following code traces a message every time it detects a keypress:
function down( ) {
trace("detected!");
}
var keyListener:Object = new Object( );
keyListener.onKeyDown = down;
Key.addListener(keyListener);
Note that the Key class does not differentiate
between uppercase and lowercase letters when looking for keyboard
inputs. For example, the Key class returns the
same keycode regardless of whether you have the Shift key down or the
Caps Lock key enabled when you press the A key.
The trouble with our code is that it runs our function for as long as
a key is held down, which makes it unsuitable for detecting keyboard
shortcuts, because pressing and holding a key down will generate
multiple events. To fix this, we need to switch our event handling
around so that we see only one
"detected!" message for each key
down-up cycle. The following code does this:
function down( ) {
trace("detected!");
delete this.onKeyDown;
this.onKeyUp = up;
}
function up( ) {
this.onKeyUp = undefined;
this.onKeyDown = down;
}
var keyListener:Object = new Object( );
keyListener.onKeyDown = down;
Key.addListener(keyListener);
As soon as a keyDown event is detected, the code
switches to looking for the corresponding keyUp
event. The keyUp event causes the cycle to
restart, creating a set of events that respond to the full keystroke
toggle.
Note that this code works even if you attempt a keystroke
combination, such as Ctrl-A. In that case, you will see a
"detected!" message for each key.
Turning Keystrokes into Inputs
So far, we have only the ability to detect keystrokes. We really need
to be able to detect which keys were pressed. We can do this with the
Key.isDown( ) and Key.getCode(
) methods. The first tells you whether a particular key is
pressed, and the second tells you the keycode of the last key that
caused a key event (i.e., the last key that changed its state, which
will not necessarily be the key that is currently held down). For
example, if you hold down the A and S keys at the same time, then
release the A key, the getCode( ) method returns
the code for the A key, even though the S key is still down.
Note that the Key class returns keycode values
for the physical keys themselves and does not take modifiers into
account. For example, pressing the X key will give you the same code
regardless of the state of the Shift or Ctrl keys.
The online help has a table listing the alphanumeric keycodes. Search
on "Keyboard keys and keycode
values" to find them. For nonalphanumeric keys, such
as the arrow keys and spacebar, you can use the constant properties
of the Key class. For example,
Key.SPACE contains the keycode for the spacebar
and Key.CONTROL contains the keycode for the Ctrl
key.
For example:
x = Key.isDown(65); // x is true if the "A" key is pressed
y = Key.getCode( ); // y is 65 if the last key to cause an event was "A"
Knowing a key's keycode, we can quickly write
ActionScript that detects single keystrokes. Assuming you have three
buttons on the Stage with instance names buttonA,
buttonB, and buttonC, the
following code causes each button's
onRelease( ) handler to execute if you click
button A, B, or C. It also gives selection focus to the button
associated with a keystroke, which (among other things) causes a
yellow rectangle to be drawn around the button:
function aHandler( ) {
trace("you clicked A");
}
function bHandler( ) {
trace("you clicked B");
}
function cHandler( ) {
trace("you clicked C");
}
function down( ) {
if (keys[Key.getCode( )] != undefined) {
keys[Key.getCode( )].onRelease( );
Selection.setFocus(keys[Key.getCode( )]);
}
this.onKeyDown = undefined;
this.onKeyUp = up;
}
function up( ) {
this.onKeyUp = undefined;
this.onKeyDown = down;
}
//
var keys:Array = new Array( );
var keyListener:Object = new Object( );
keys[65] = buttonA;
keys[66] = buttonB;
keys[67] = buttonC;
buttonA.onRelease = aHandler;
buttonB.onRelease = bHandler;
buttonC.onRelease = cHandler;
keyListener.onKeyDown = down;
Key.addListener(keyListener);
The preceding code creates an array called keys
whose indexes are used to identify associated Flash buttons. For
example, keys[65] contains a reference to
buttonA, so when the A key (keycode 65) is
pressed, the event handler buttonA.onRelease( )
is run. In Figure 11-22, the left and right images
show the buttons buttonA,
buttonB, and buttonC before
(left) and after (right) the keyboard A key is pressed. When the A
key is pressed, the message "You clicked
A" appears in the Output panel.

For the Flash Player to be able to capture keystrokes, the SWF must
have browser focus [Hack #95] . The
SWF maintains focus if the SWF within the browser has been clicked
on, until other content outside the SWF is clicked on. Simply moving
the mouse causes a button to lose browser focus, so you may never see
the yellow bounding rectangle if you are holding the mouse when
pressing A.
To test a SWF that needs to capture keystrokes, you should select
Control Disable Shortcuts once in Test Movie mode. This
prevents the Flash application from capturing keyboard inputs for its
own keyboard shortcuts and masking them from the SWF under test.
Turning Combos into Inputs
As well as having single keystroke shortcuts, many applications have
multiple keystroke combinations (also known as combos), such as
Ctrl-F1. Flash is sometimes used as a standalone desktop application,
so it would be reasonable to use combos as Flash inputs.
However, you need to be careful about using combos as inputs into
Flash, given that Flash is rarely at the top of the pecking order for
receiving combos. Most of the common combos are already taken by the
operating system or the browser, so Flash will not receive them all.
We can make our code look for combo keystrokes by specifying an array
that defines a number of keys that need to be held down to form an
input.
Suppose we wanted to make:
A the keystroke for buttonA Ctrl-B the combo for buttonB Ctrl-D the combo for buttonC
We can define this information in an array of objects:
keys[0] = {btn:buttonA, combo:[65]};
keys[1] = {btn:buttonB, combo:[Key.CONTROL, 66]};
keys[2] = {btn:buttonC, combo:[Key.CONTROL, 68]};
Each of our keys entries is now an object
consisting of two properties:
The following code uses this data structure to add the keyboard
combos for our three buttons, buttonA,
buttonB, and buttonC. The hacky
part of this code is the way we check whether the full combo is down.
For each combo, the code:
Sets a Boolean, allKeys, to
true prior to searching through each combo entry,
keys[j].combo. For each key in the combo, tests whether each key is down via
Key.isDown(keys[j].combo[i]). This expression is
true if the key is down and
false if it is not. This value is ANDed
(&&) with allKeys. If
any key is not down, allKeys
becomes false and remains so until the end of the
loop. At the end of the search, if allKeys is still
true, we know that the full combo was depressed.
The cool thing about using the && logic
operator in this way is that allKeys
doesn't care in which order the keys were pressed or
how many keys there are. We test whether they are all down whenever
we carry out the test. If allKeys is true, the
code carries on in much the same way as the preceding single
keystroke code. The following code demonstrates (changes are shown in
bold):
function aHandler( ) {
trace("you clicked A");
}
function bHandler( ) {
trace("you clicked B");
}
function cHandler( ) {
trace("you clicked C");
}
function down( ) {
var allKeys;
//trace("--------------");
for (var j = 0; j < keys.length; j++) {
allKeys = true;
for (var i = 0; i < keys[j].combo.length; i++) {
allKeys = allKeys && Key.isDown(keys[j].combo[i]);
}
if (allKeys) {
Selection.setFocus(keys[j]);
//trace("combo detected");
this.onKeyDown = undefined;
this.onKeyUp = up;
keys[j].btn.onRelease( );
break;
}
}
}
function up( ) {
//trace("up");
this.onKeyUp = undefined;
this.onKeyDown = down;
}
//
var keys:Array = new Array( );
var keyListener:Object = new Object( );
keys[0] = {btn:buttonA, combo:[65]};
keys[1] = {btn:buttonB, combo:[Key.CONTROL, 66]}; // Ctrl-B
keys[2] = {btn:buttonC, combo:[Key.CONTROL, 68]}; // Ctrl-D
buttonA.onRelease = aHandler;
buttonB.onRelease = bHandler;
buttonC.onRelease = cHandler;
keyListener.onKeyDown = down;
Key.addListener(keyListener);
I have left in some useful trace( ) actions,
which I used in developing this code in the listing. If you want to
see how (and in what order) the code detects and sets off the
keyDown( ), keyUp( ), and
onRelease( ) event handlers, you can uncomment
the trace( ) statements and view the order of
events in the Output panel. It should make the code much easier to
understand.
The Ctrl-B shortcut won't work when you test the
movie in Flash unless you disable keyboard shortcuts, because Ctrl-B
opens the Bandwidth Profiler. Ctrl-C doesn't work
because it is captured by other applications, most likely your
operating system's copy feature so we use Ctrl-D as
the keyboard combination for buttonC.
If you test the SWF in the browser, you will also see that Ctrl-B may
cause problems (it is the shortcut for organizing your Favorites
links in Internet Explorer). The moral of this is to check that your
combos are not used by anything else that might be running at the
same time as the Flash SWF; otherwise, Flash will not see them!
Final Thoughts
Accessibility is becoming more of a required feature as time goes on.
Making your site respond to the keyboard as well as the mouse makes
it more useful to the sight impaired but also makes it more
accessible to the command-line twitch-typer. Surprisingly few Flash
sites support keyboard shortcuts, probably because the code to do
this is not totally obvious. Well, it is now, so
there's no excuse!
|