reptile7's JavaScript blog
Tuesday, April 28, 2009
 
Press My Keys
Blog Entry #143

The keyboard and the mouse, the standard input components for a GUI computer, both have associated event handlers in JavaScript. At this point, we're well-versed in using mouse events (click, mouseover, etc.) to trigger the execution of JavaScript code, but we have almost no experience with key events in this regard; today's post takes a small step towards rectifying the situation via a look at HTML Goodies' "Capturing a Key" tutorial.

JavaScript has three key-related event handlers: onKeyDown, onKeyPress, and onKeyUp. We discussed and demonstrated onKeyUp in the "Other 'After-the-Fact' Event Handlers" section that concludes Blog Entry #24. The "Capturing a Key" tutorial showcases onKeyPress, which [e]xecutes JavaScript code when a KeyPress event occurs; that is, when the user presses or holds down a key, quoting Netscape. The tutorial presents a script that pops up a "That's the x key" alert( ) message when the x key for a given character on the keyboard is pressed and thus functions as a simple (and harmless) 'keystroke logger'; the script is reproduced in the div below:

<HEAD>

<SCRIPT LANGUAGE="JavaScript1.2">

<!--

function NNKeyCap(thisOne) 
{
    if (thisOne.modifiers & Event.SHIFT_MASK) 
{
    if (thisOne.which == 37) 
        {alert('That\'s the % key')};
    if (thisOne.which == 90) 
        {alert('That\'s the Z key')};
    if (thisOne.which == 41) 
        {alert('That\'s the ) key')};
}

    if (thisOne.which == 61) 
        {alert('That\'s the = key')};
    if (thisOne.which == 106) 
        {alert('That\'s the j key')};
    if (thisOne.which == 51) 
        {alert('That\'s the 3 key')};
}

function IEKeyCap() 
{
if (window.event.shiftKey) 
{
    if (window.event.keyCode == 37) 
        {alert('That\'s the % key')};
    if (window.event.keyCode == 90) 
        {alert('That\'s the Z key')};
    if (window.event.keyCode == 41) 
        {alert('That\'s the ) key')};
}

    if (window.event.keyCode == 61) 
        {alert('That\'s the = key')};
    if (window.event.keyCode == 106) 
        {alert('That\'s the j key')};
    if (window.event.keyCode == 51) 
        {alert('That\'s the 3 key')};
}

if (navigator.appName == 'Netscape') {
window.captureEvents(Event.KEYPRESS);
window.onKeyPress = NNKeyCap;
}

//-->

</SCRIPT>

</HEAD>

<BODY onKeyPress="IEKeyCap()">

You can try out the script at the tutorial's demo page.

The script is superficially a cross-browser script that works with both Netscape and Internet Explorer, but is perhaps more accurately described as two scripts because it comprises a Netscape part and an Internet Explorer part that are separate in every way - this is a consequence of these browsers' differing event object interfaces. We'll begin by going through both parts of the script; subsequently, we'll modernize the code a bit and tighten it up so that it more closely resembles a real cross-browser script, with a demo of our own to follow.

Netscape Navigator key capture

(We previously discussed the Netscape event model in Blog Entry #107.)

Joe begins his script deconstruction with the Netscape part, as will we. The script's Netscape action is carried out by the NNKeyCap( ) function at the beginning of the script's script element. At the end of the script element, an if block captures keypress events at the level of the window object - the concept of event capture originated with Netscape and has since been picked up by the W3C - and then coassociates keypress events, the window object, and the NNKeyCap( ) function via a property assignment statement:

if (navigator.appName == 'Netscape') {
window.captureEvents(Event.KEYPRESS);
window.onKeyPress = NNKeyCap; }


There is no explicit NNKeyCap( ) function call in the script; rather, NNKeyCap( ) is implicitly called by a keypress event, which, besides calling NNKeyCap( ), also passes to NNKeyCap( ) a corresponding event object that is given the identifier thisOne. (In the tutorial's "The Netscape Portion" section, Joe states, Notice that the function will be passed a number upon the keystroke - this is not directly true although it is indirectly true. The passed event object, like any object, is a package of sorts holding a set of attendant properties and their values; one of these values is indeed the 'keystroke number', as discussed below.)

The NNKeyCap( ) function contains six if statements that test if the user presses the key(s) for one of the following characters: %, Z, ), =, j, or 3. Towards this end, the if conditions make use of the which property of the event object, which when read returns the [n]umber specifying either the mouse button that was pressed or the [decimal] ASCII value of a pressed key. For example, the decimal ASCII code position for the lowercase j character is 106, and thus

function NNKeyCap(thisOne) { ...
if (thisOne.which == 106) {alert('That\'s the j key')}; ...


pops up a That's the j key message when the user presses the j key on the keyboard.

The which property was once Netscape-specific and is, to the best of my knowledge, still not supported by Internet Explorer; however, I can confirm that Firefox, Opera, and Safari all support it on my Intel Mac.

The corresponding if statements for the = and 3 characters are equally straightforward, but what about NNKeyCap( )'s first three if statements, the ones that are conditionalized with a

if (thisOne.modifiers & Event.SHIFT_MASK) { ... }

statement? Joe doesn't have much to say about the thisOne.modifiers & Event.SHIFT_MASK condition - [It] asks if the shift key is held down - but in fairness to Joe, Netscape's definition of the modifiers property of the event object isn't very enlightening either: String specifying the modifier keys associated with a mouse or key event. Modifier key values are: ALT_MASK, CONTROL_MASK, SHIFT_MASK, and META_MASK. This definition suggests that the if condition should actually be

if (thisOne.modifiers == "SHIFT_MASK") { ... }

if we're trying to verify that the user is modifying a character keypress with the shift key. In an attempt to sort out this situation, I went into the SheepShaver environment and probed the thisOne.modifiers and Event.SHIFT_MASK expressions in the NNKeyCap( ) function with Netscape Communicator 4.61. Here's what I found:

(1) thisOne.modifiers and Event.SHIFT_MASK aren't strings at all. typeof Event.SHIFT_MASK returns number, as does typeof thisOne.modifiers when any key for an ASCII character x is pressed with or without shift/control/alt-modification.

(2) Event.SHIFT_MASK returns 4; in response to pressing shift-x, thisOne.modifiers also returns 4. In JavaScript, & is a bitwise logical AND operator, so it would seem that the above if condition compares 0100 and 0100 operands, and therefore returns true, when shift-x is pressed.

Do we really need to test if the shift key has been pressed for the %, Z, and ) characters? After all, a specific which value maps onto a specific character - why would how that character is generated have anything to do with it? The script implies that the thisOne.which return for pressing Z, for example, would be the same as that for pressing z, and thus wouldn't pop up an alert( ) box, if we were to get rid of the modifiers conditional: true or false? False, at least on my computer - I find that I can comment out the if line and the block's brace delimiters without any problems.

I suspect that Joe's modifiers code ultimately derives from this JavaScript 1.2 example:
<SCRIPT>
function fun1(evnt) {
   alert ("Document got an event: " + evnt.type);
   alert ("x position is " + evnt.layerX);
   alert ("y position is " + evnt.layerY);
   if (evnt.modifiers & Event.ALT_MASK)
      alert ("Alt key was down for event.");
   return true;
   }
document.onmousedown = fun1;
</SCRIPT>
The preceding script's if statement tests if the alt key is held down in connection with a mousedown event, but other than popping up an Alt key was down for event. message for a true condition return, it does nothing further with the test result.

BTW, the modifiers property does not appear on Mozilla's DOM event Reference page and is in all likelihood now deprecated or even obsolete.

Internet Explorer key capture

(We previously discussed the MSIE event model in Blog Entry #108.)

The script's MSIE action is carried out by the IEKeyCap( ) function that follows the NNKeyCap( ) function. The IEKeyCap( ) function is called conventionally by an onkeypress attribute in the body element start-tag:

<BODY onKeyPress="IEKeyCap( )">

The IEKeyCap( ) function also holds six if statements that test if the user presses the key(s) for the %, Z, ), =, j, or 3 character. Instead of the which property, these statements use Microsoft's analogous keyCode property to identify the test characters, e.g.:

function IEKeyCap( ) { ...
if (window.event.keyCode == 51) {alert('That\'s the 3 key')}; ...


The keyCode property [s]ets or retrieves the [decimal] Unicode key code associated with the key that caused the event. The first 128 Unicode code positions and ASCII's code positions map onto the same characters; consequently, IEKeyCap( ) uses the same keystroke identification numbers that NNKeyCap( ) does.

The keyCode return, like the which return, corresponds to a particular character and not to the key or combination of keys that is used to generate that character, and thus Joe's conditionalization of the first three IEKeyCap( ) if statements with

if (window.event.shiftKey) { ... }

is also unnecessary.

Bringing it all up to date

On my computer, I find that Opera and Safari support both the Netscape event model and Microsoft's event model; with these browsers, we can use either the which property or the keyCode property to identify keypress characters. Unsurprisingly, Firefox only supports the Netscape event model and MSIE only supports Microsoft's event model. Mozilla has added a keyCode property to its event object interface but uses it to specifically flag non-character keys, e.g., thisOne.keyCode returns 9 for the tab key and 27 for the esc(ape) key, but it uniformly returns 0 for a character key.

The different Netscape and Microsoft event models notwithstanding, we can profitably use the ?: conditional operator to condense NNKeyCap( ) and IEKeyCap( ) into a single function as follows:

function KeyCap(e) {

var thisKey = e ? e.which : (event ? event.keyCode : "");
if (thisKey) {

if (thisKey == 37) window.alert("That's the % key.");
if (thisKey == 90) window.alert("That's the Z key.");
if (thisKey == 41) window.alert("That's the ) key.");
if (thisKey == 61) window.alert("That's the = key.");
if (thisKey == 106) window.alert("That's the j key.");
if (thisKey == 51) window.alert("That's the 3 key."); }

else window.alert("Sorry, your browser does not support the event object."); }


The above script's ?: statement is based on the conditional code provided by the "Accommodating Both Event Object References" section of the Apple Developer Connection's "Supporting Three Event Models at Once" tutorial, whose ADC URL, http://developer.apple.com/internet/webcontent/eventmodels.html, is strangely non-functional as of this writing; fortunately, the tutorial can be accessed here via the all-important Internet Archive Web site.

Internet Explorer does not support onkeypress for the window object, but it does support it for the document object, as do all of the other browsers on my computer, so we'll use a

document.onkeypress = KeyCap;
// onkeypress must be all-lowercase for cross-browser support.


statement to coassociate keypress events and the KeyCap( ) function.

Finally, if you want to hold on to the original script's captureEvents( ) command - Netscape 4.x does in fact need it, even as the captureEvents( ) method is now deemed "obsolete" by Mozilla - then you should formulate/conditionalize it as

if (document.layers) document.captureEvents(Event.KEYPRESS);

given (a) the preceding coassociation statement and (b) that Firefox and Safari both return Netscape for the navigator.appName property.

FYI: If your computer features a command-line interpreter, then you can run man ascii on the command line to get ASCII values (as opposed to running to Wikipedia to get them) for using the script with other characters.

Demo

As promised, I have a demo for you.
(1) Click on the pale blue area below to impart focus to the document if necessary and then
(2) press the key for the a, b, c, d, e, or f character.

Press the key for one of the following lowercase characters: a, b, c, d, e, or f.

August 2016 Update:
I have belatedly discovered that keypressEvent.which returns 0 for document navigation keys (, , , etc.) when using Firefox on my computer; to prevent the "your browser does not support the event object" else clause from firing in this case, I have changed the if (thisKey) gate to an if (typeof thisKey == "number") gate.

The W3C and key events

So where is the W3C on key events, huh? At the Recommendation stage, the DOM Level 2 Events Specification does not provide a key event module. At the Working Draft stage, the DOM Level 3 Events Specification contains a "Keyboard event types" section that is likely to be moved to a separate specification on keyboard events. Looking "Keyboard event types" over, it appears that the W3C wants to phase out the keypress event, whose interface would evidently be subsumed by that for the keydown event; I also see that, rather than picking up the which or keyCode property, the W3C is introducing a keyIdentifier property for identifying all of a keyboard's keys, including those keys that do not generate Unicode characters.

The script in the next Beyond HTML : JavaScript tutorial, "So, You Want To Set A Cookie, Huh?", is largely a rehash of the Script Tips #60-63 Script, which we analyzed in Blog Entry #82, but we still might have a bit to say about it in the following entry - at the least we can sort out its problematic demo, which works for Internet Explorer but not for other browsers.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)