Teacher's Guide: Event Objects Notes
Overview
This lesson presents a straightforward yet powerful extension of the Keyboard Events! lesson. Event objects provide a range of additional, contextual information about their corresponding events. In the case of keyboard-related events, they also permit us to effectively bind events for all keys with a single event listener, thereby substantially reducing the amount of coding required for many applications. Additionally, this lesson introduces two additional keybinding functions, keyup
and keypress
, which facilitate a more finessed response to computer keyboard input.
More about the lesson
The lesson's coding snippet involves some subtleties that students might overlook without instructor intervention: All lines of code in this snippet work together as a single block. The first two lines create an HTML label element (initially with no text) and a variable that points to that element (via a jQuery wrapper object). The subsequent lines of code define a callback, and bind this callback to the label element using a keypress event listener. Whenever a keypress event fires, the label is updated to show which key was most recently pressed.
The two-argument variants of the keydown
, keypress
, and keyup
event binding functions introduced in the previous lesson are Pencil Code extensions of the underlying jQuery functions by the same name. These two-argument variants enable students to work with events without explicitly evaluating the event object, which is convenient for establishing event handlers for a few keys, but cumbersome when associating callbacks with a wide range of keys. This lesson introduces single-argument variants of these functions that have usage closer to the underlying jQuery.
Like all HTML events, keydown, keypress and keyup produce an event object each time the event fires. This object is passed from the event to the associated callback. Explicitly naming the argument in the callback function's definition (the lesson suggests the commonly used names e
, evt
, and event
) facilitates accessing the event object in the body of the callback.
Keyboard event objects have a number of properties, though at this stage students will likely focus primarily on key
, timeStamp
, and a few others that were mentioned in the lesson. For a complete listing, refer to the jQuery API for Event Objects. Alternatively, we can explore the event object by iterating over it, as illustrated for keydown events in this script.
The syntax for the keydown
, keypress
, and keyup
keybinding functions is identical. The differences between the functions owe to the differences between the underlying keyboard events.
The keyup event is in most ways analogous to the keydown event. As the name suggests, the keyup event generally fires upon release of a key. One exception is for the caps lock key: the keyup event does not fire when caps lock is initially pressed and released, i.e., engaging the lock. Rather, it fires upon the subsequent press (not release) of caps lock, which releases the lock. Another nuance is that keyup is the only event that fires when the delete key is pressed and released.
keydown and keypress events differ in a variety of often nuanced, context-dependent ways. For the purpose of the activities in this lesson, the following general rules will typically suffice:
- With a few exceptions (such as for the delete and caps lock keys), keydown events fire whenever a key is pressed. keypress events only fire when the key press produces a character value. Thus both will fire for the keystroke a but only keydown will fire when esc or → is pressed.
- keypress and keydown event objects both have boolean properties that report the status of modifier keys, e.g.,
shiftKey
,metaKey
, andaltKey
. They differ, however, in that for keypress (but not keydown) the value of the event object'skey
andkeyCode
properties reflect the status of modifier keys such as shift or alt (a.k.a., option on Macs). In Pencil Code, this means that if shift is depressed while either event occurs, thekey
value for a keypress event will return an upper-case letter for keys A through Z and characters such as ! and @ rather than the number values 1 and 2. In general, keypress should be the preferred event when processing text.
Notes to activities
The KeyEventNuances activity is designed to encourage students to explore the differences between the events associated with different keybinding functions along several dimensions. The primary issues to explore relate to "if" and "when" events fire. For example, keypress and keydown fire prior to keyup, and keypress will not fire at all when the arrow keys are pressed. The event object's timeStamp
property can be helpful for documenting the sequencing of the different events with respect to time, and to exploring the timing of automatically re-firing events (as happens when keypress and keydown are pressed and held, rather than immediately released). Finally, studnets might also explore how modifier keys, such as shift or control, affect the values of event object properties.
The lesson provides this example solution to the KeyEventNuances activity. It's approach focuses on a narrow subset of event object properties (key
, keyCode
, and timeStamp
). This alternative script provides another look, highlighting some other important differences between keypress and keydown events.
The String class's split
method is a useful shortcut for TypingTest. split
takes a single argument, which specifies the delimeter: abc.split(" ")
breaks up a single string into an array of words; abc.split("")
splits the string into its component letters.
The instructions for the Piano program recommend the use of tone
, an alternative to the play
function. Pencil Code provides this documentation:
A helpful alternative usage not mentioned in this documentation is the fact that tone
's first argument, which specifies the note, can also be expressed a string (e.g., "A", specifying the note name) rather than a numeric value (e.g., 440, specifying the frequency).
Students have several options to map and bind keyboard keystrokes to individual musical notes. A common approach is to use nested if
statements to test whether e.key
matches an appropriate string. However, such nested if
statements can get rather cumbersome. switch
statements (documented below) provide an alternative means to code the same conditional logic more succinctly. The use of switch
is illustrated in the lesson's example solution. Yet another approach, illustrated in this script, makes use of a collection of objects that maps each keyboard key to the respective musical note, e.g., [{key:"a",note:"C"},{key:"w", note:"^C"}]
. Each time a keyboard event fires, iterate over this array of key-and-note objects to determine which of one matches the event object's key
property.
An additional solution to the Piano activity is provided in the Pencil Code Gym. This Interactive Keyboard script also uses keydown
and keyup
event binding functions, but it uses some advanced features not yet explored in this curriculum: anonymous functions, the delete
function, and subscript notation.
Additional activities
- Run this FourIsCOSMIC program a few times, until you figure out the riddle. Once you do, code your own version of the script. You might save your time by limiting the input options to the numbers 1 to 20.
The Pencil Code
read
function facilitates text entry in the Pencil Code environment.read
accepts two arguments: (1) a string specifying a prompt and (2) a callback that defines what to do when data entry is complete.name = undefined updateName = (txt)-> name = txt read "Please enter your name: ", updateName
This snippet produces the following output on screen:
After the user enters data and then either presses enter (on a PC) or return (on a Mac), or clicks the Submit button, this output changes to the following:
read
is useful for students who are new to coding, but for more advanced coders, it is rather restrictive. For example, the way that the text entry box disappears after you enter data, and then the text you entered appears as a label on the screen, is nice for some purposes, but more generally that behavior could be problematic. For example, we might want to keep the text entry box on the screen for continued use. Alternatively, you might want to interact with the user as they enter text into the box, such as for text validation purposes.Using the techniques described in this lesson, students should have little need for the
read
function. However, it does suggest an alternative means by which we may prompt users to enter data, i.e., using the same kind of text entry box. This is accomplished using an HTML <input> element. <input> is a familiar page element for end users and useful because it facilitates letting the user enter and or modify text prior to submitting it.For this activity, code your own InputBox program that makes use of an <input> element to gather information from the end user. In Pencil Code, you can easily add a specific type of HTML element using the
Sprite
constructor, e.g.,box = new Sprite("<input>")
To attach the event listener to the specific page element, rather than the entire document (which is the default), simply use dot notation:
box.keypress(callback)
. Note that we bind the callback to keypress events so that the value of the event object'skey
property reflects modifier keys such as shift. You should use conditional logic in your callback to test for when the enter / return button is pressed (i.e., whene.keyCode==13
), at which point your program should make use of the input in some fashion.Pro tip: you can manually keep track of each character the user types using the event object's
key
value. However, a much easier approach is to use the jQueryval
method. When a jQuery object wraps an <input> element,val
lets you access or modify the content of the text: e.g.,box.val()
returns the current value andbox.val("x")
replaces the content with the string "x".RPN Calculator: Most calculators are based on infix notation, in which operators are placed between operands. Two alternatives to this are prefix notation, in which operators precede their operands, and postfix notation, in which operators follower their operands.
Prefix and postfix notation have the advantage that they do not required parentheses. These latter two notations are known as Polish notation and Reverse Polish notation (RPN). The description "Polish" refers to the nationality of Jan Łukasiewicz, who invented Polish notation in 1924. RPN is used in scientific and financial calculators made by Hewlett Packard and the logic also undelies some programming languages, most notably LISP.
For this task, code your own RPN Calculator. Rather than bind each key directly, make a single listener for all keys and evaluate the key event for the relevant inputs. Naturally, you will need to program the calculator-computation logic as well!
To carry out a computation using RPN, type the first operand (i.e., value), press
, type the second operand, and then the operator (e.g., "+" or "*"). A more complicated expression such asenter 7*(3+5)
would be computed as7
,
,enter 3
,
,enter 5
,+
,*
.
Beyond the lesson
switch
statements
As noted briefly in the Notes to the Conditional Logic! lesson, a switch
statement provides a convenient alternative to nested if
/else
statements. switch
statement syntax is particularly straightforward in CoffeeScript. The basic syntax is:
switch expression when condition1 then statement when condition2 statement(s) else statement(s)
As in if
statements, using then
in switch
statements allows you to combine single-line conditional expressions on the same line.
switch
statements are particularly useful in the context of keyboard events because they facilitate evaluating event object properties, such as key
or keyCode
, that can take on a wide range of potential values. The following snippet illustrates one way we might evaluate the event object for a keyboard event handler with parameter e
:
switch e.key when "up" fd 100 dot blue, 10 when "down" dot erase, 10 bk 100 when "right" then rt 90 when "left" then lt 90 else see "Invalid input: #{e.key}"
CoffeeScript switch
statements are flexible as well as syntactically simple. For example, the following example takes advantage of the coffeescript feature that "everything that can be an expression is structured to be one"—hence the switch
statement returns a value; additionally, note how the conditions are completely stated in the when
clauses:
result = switch when e.keyCode>=97 and e.keyCode<=122 "an uppercase letter" when e.keyCode>=65 and e.keyCode<=90 "a lowercase letter" when e.keyCode>=48 and e.keyCode<=57 "a number" else "neither a letter or a number"
This latter syntax is harnessed in coding this variant of the KeyboardPiano program .
For more details about switch
, please refer to other CoffeeScript resources, such as this one by TutorialsPoint.
Long presses
When a key is pressed and held, it starts to "auto-repeat"; keydown
and/or keypress
events will fire intermittently—every time the operating system repeats the key—until the key is released. This holds for keys that produce output (such as letters) and arrow keys, but not for modifier keys such as shift or alt.
The JavaScript event object's repeat
property records a boolean value indicating whether the event was a repeat fire from a single key press. This property is not directly accesible in the corresponding jQuery event object, but it is accessible via the jQuery event object's originalEvent
property. That is, for an event e
, access this boolean-valued property with e.originalEvent.repeat
. The following script provides an example:
The timing of the repeat events depends on the system. On a MacBook Pro running Chrome, the lag before the first keydown event and the first repeat is about 400ms. Subseqent refirings occur about every 80ms.
Note that these lags between subsequent events can produce suboptimal behavior in interactive games when key events are used to control movement. A superior alternative is to use event polling, which is enabled by using timers (facilitate with the foreover
function in Pencil Code), as discussed in the Event Polling! lesson.
What can go wrong
key events are not firing!
The preceding sections of these notes have described in detail many of the nuances of working with keyboard-related events. Perhaps the issue most likely to cause confusion is the fact that the different types of events—keydown, keyup, and keypress—do not all fire in response to all keys.
The first, rather straightforward example involves keypress events. These only fire for character keys, i.e., letters, numbers, and punctuation. Notably, this leaves out the arrow keys.
A second, and much more nuanced example involves the behavior of deletion-related keys. The delete key on the Mac behaves the same as backspace← on the PC. PCs typically also have a delete key, with its own behavior. For the keys delete (on the Mac) or backspace← (on the PC), only keyup fires, though this occurs upon releasing the key rather than when pressing it. The associated event object's key
property (also discussed in the next lesson) has value "backspace"
. On the PC, pressing delete does result in keydown and keyup events. The associated event object's key
property has value "delete"
. (See this script for an illustration.)
Variable scope complications
It is common to reference global variables in event handlers. When doing so, note that the variable must be declared prior to calling the event binding function. If not, the variable referenced in the event handler function will be a new variable with function scope. This short script provides a simple illustration.
Event Polling
When learning about keyboard events, students may attempt to create programs that allow them to control sprites using keys. However, such efforts may end in frustration. As described in the instructions to the Additional Activity TurtleWarperGame in the Notes to the Keyboard Events lesson, the use of keyevent-binding functions to control sprite motion is fine for programs that involve simple motions, but it is a suboptimal means by which to control sprite movement more generally. The underlying issue relates to lags associate with key events, a topic explored in these notes in a section on long presses. A superior solution is to rely on frame-based animation using timers and to harness a concept known as event polling, rather than to make use of the event binding fuctions directly. Timers and event polling are the focus of subsequent lessons in this curriculum, Timers (forever)! and Event Polling!.
Pedagogy
Explicitly defined callback functions
As mentioned in the notes to the previous lesson, the coding snippet provided in the Pencil Code block coding palette makes use of anonymous functions, and students and teachers alike may be tempted to adopt this convenience at this point. However, doing so risks diminishing student awareness of the role that callbacks play here. It also blurs the role of arguments used in the keybinding function context.
For example, the following code is the first line of a call to the keydown
function with two arguments:
keydown 'x', (e) ->
An inexperience coder can easily confuse e
as an argument to keydown
, though it is not. Rather, (e) ->
, together with the subsequent lines of indented code which define an anonymous function, are the second argument to keydown
The upshot is that there continues to be good reason to hold off on anonymous functions. The topic will be addressed directly in the Anonymous Functions! lesson.
Technicalities
While the concepts and activities in the lesson are straightforward enough, keyboard inputs can be notoriously difficult to process in practice. Many of the nuances of working with events trace to historical cross-browser (and cross-platform) differences between browsers. Admittedly, these differences have become less pronounced over time, but it is still an issue. A primary reason for adopting jQuery is that it frees coders from having to worry about the many cross-browser differences which would otherwise significanly complicated code. However, when it comes to keyboard events, jQuery is not a panacea.
Over the years, the HTML specifications and the JavaScript langauge have continued to evolve, with some significant changes to keyboard events. A not-to-be-overlooked consequence of this evolution is that different sources for documentation on events will provide divergent and even conflicting approaches to working with events. Quite a few of the properties in event objects have been deprecated over time.
A more far-reaching change is that the JavaScript keypress event itself is now deprecated. The upshot is that, while event bindings with the jQuery keydown
and keyup
functions can be counted on to provide consistent results across systems, the same may not continue to be true for keypress
: per the the JQuery API, "as the keypress event isn't covered by any official specification, the actual behavior encountered when using it may differ across browsers, browser versions, and platforms."
(Details on the deprecation of keypress are provided on the mdn docs page for keypress. The preferred modern alternatives to using keypress involve use of the beforeinput, keydown, or input (this last one new in 2023). However, given that access to the keypress event continues to be facilitated by jQuery, and also that the newer alternatives exceed most student needs while increasing the complexity of use, this curriculum recommends continued reliance on keypress events.)
Yet another complicating factor to working with events in the Pencil Code environment is that the Pencil Code key event binding functions are modifications of the underlying jQuery. Some of these modifications were added to simplify the use of event-binding functions, such as adding the two-argument variants (e.g., keydown "x", eventHandler
). Also, as noted above, the default codes from keydown
are lowercase letters, whereas the standard mappings are to uppercase letters. Additional modifications, unseen to the user, include keybinding changes written with the intent to eliminate some persistent cross-browser differences in jQuery.
The upshot of the foregoing conversation is that, for the level of work that students will use at this point in their coding careers, most of the nuances listed in this section are unlikely to surface, but be aware that problems can arise, and also that documentation listed on other sites (MDN web docs for JavaScript, W3schools, jQuery API documentation) will at times give explanations that don't sync with functionality enountered on the Pencil Code platform.
JavaScript key event bindings
The "best-practice" approach to adding event bindings using pure JavaScript involves using a page element's addEventListener
method. The following simple script illustrates this approach by adding a keydown event listener. The addEventListener
method adds the function writeText
as an event handler to the <document>
element:
document.addEventListener('keydown', writeText); function writeText(e) { write(`${e.key} ${e.keyCode}`); }
Listeners can be removed using the removeEventListener
method.
Compared to JavaScript, the jQuery event binding alternatives have the obvious advantage of simpler syntax. Another advantage to jQuery event binding functions is that they can be used to add event listeners to multiple objects using one function call. Students will explore this functionality in the Mouse Events! lesson.
Legacy approaches to event binding
An alternative approach to adding listeners is to directly assign an event handler function to an event property of an HTML page element reference, such as onkeydown
for key events. This can be done within a script or using inline JavaScript.
A simple example, in a jQuery context, is provided using the following script. It is provided for illustration only—this approach is strongly discouraged.
$("body").attr('onkeypress', " $(\"#lbl\").html(event.key); " )
The foregoing snippet is formatted to highlight a few key features: the argument that defines the callback is passed as a string; the function definition must be coded in JavaScript (not CoffeeScript); and the event object is automatically passed to the callback as the variable event
. Additionally, note that for the event handler to function correctly, the page element must be focusable (described below).
As inline code (embedded in HTML), this binding looks like this:
<body onkeypress="$(\"#lbl\").html(event.key);">...</body>
Key event sources: focusable HTML elements
The reference to document
in the the JavaScript code above illustrates the previously noted fact that all events in JavaScript originate from the browser. Event handler functions need to be registered with HTML page elements (such as <document>), via event listeners, in order for the events to be passed to the script. CoffeeScript compiles to JavaScript, so naturally, event handlers created in CoffeeScript must specify a specific page element.
By default, Pencil Code event binding functions register with the <document> element. That is, keydown
is a Pencil Code alias for $(document).keydown
.
Listeners can be added to any type of page element. However, when working with keyboard input, associated events will only fire for page elements that are . In addition to <document>, text-related elements such as <input>, <textinput>, <form>, and <textarea> are by default focusable. Most other HTML elements are not inherently focusable, but they can be made so by adding a tabindex
attribute (which takes a numeric value). A simple illustration is provided in this script.
The HTML element to which the listener is attached is referenced in the event object's target
property.
Students are unlikely to have much use for varying event sources in this lesson. They will be introduced to this material in the next lesson, Mouse Events!, where it is indispensible.
jQuery on
and off
methods
The jQuery keydown
method is a shortcut for a call to the more general and versatile jQuery method on
. The statement keydown eHandler
is synonymous with $(document).on("keydown", eHandler)
. The on
method works analogously with other browser events, including keypress and keyup.
As with the keydown
method, on
can be called on any jQuery object that references an HTML page element (or a collection of page elements). Note that we have to explicitly reference $(document)
because on
is a Pencil Code alias for true
, rather than this method.
The primary benefit to using the on
method rather than keydown
is that you can subsequently use the corresponding off
method to detach the event handler. off
uses a similar syntax. $(document).off("keydown", eHandler)
removes the eHandler
callback from the keydown event listener that was established using on
. $(document).off("keydown")
will remove all keydown event handlers set up using on
. However, off
has no effect on handlers set up with keydown
, as illustrated in this script
For additional details, refer to the relevant page on the jQuery.com website.
jQuery event object's originalEvent
property
The jQuery library's popularity owes in no small part to how it simplifies writing code that will work in all the different major browsers on all common operating systems. This is no mean feat, as the differences are not only myriad but often highly nuanced, and hence often tedious and time consuming to address.
In the case of events, jQuery creates its own event object that adjusts any system specific features so that they all adhere to these W3C standards. As with objects more generally, jQuery does not simply replace the orginal event, but creates a wrapper object. This jQuery event object normalizes and provides values for "common event properties", which include altKey
, bubbles
, button
, buttons
, cancelable
, char
, charCode
, clientX
, clientY
, ctrlKey
, currentTarget
, data
, detail
, eventPhase
, key
, keyCode
, metaKey
, offsetX
, offsetY
, originalTarget
, pageX
, pageY
, relatedTarget
, screenX
, screenY
, shiftKey
, target
, toElement
, view
, which
.
The foregoing list may seem extensive, but it is not complete. It does not include properties for which the W3C does not provide a standard. The jQuery originalEvent
property provides access to the event object that the browser itself created, and thus to the properties not included in the jQuery object directly.
An example of a property not included in the jQuery event object relates to "long presses", a topic that was addressed briefly above. Though the jQuery event object does not have a repeat
property, the event objects of each of the major browsers do. originalEvent.repeat
records a boolean value indicating whether the event was a repeat fire from a single key press.