PencilCoder

Teacher's Guide: Keyboard Events

Overview

Events provide students a means to make their programs interactive. They therefore open up a range of new program design opportunities.

Along with the basic concept of events, the lesson introduces a range of events-related concepts and terminology, including event listeners, event binding functions, and event handlers. This introductory lesson focuses on keyboard-related events because they can be accessed in Pencil Code without making direct reference to event objects. Event objects, which provide additional options for reacting to keyboard events and facilitate processing other system events will be the focus of two subsequent lessons, Event Objects! and Mouse Events!.

More about the lesson

Any operating system that supports GUIs constantly monitors system events such as keystrokes or mouse clicks. The operating environment reports these events to the program that is currently active. The program then decides what, if anything, to do in response to these events.

Web browser programs, such as Safari and Chrome, pass a subset of system event info on to JavaScript programs currently running in the browser. The browser processes these as HTML events. The list of accessible events is quite extensive, including things such as:

  • key presses
  • mouse movements, mouse clicks, mouse hovers
  • completing the loading of the web page
  • submission of a form
  • changes to the browser, such as closing a tab or resizing the window

This and subsequent lessons introduce coding tools to harness information about events, thereby making it possible to write programs that are interactive, i.e., which respond to user input. There are scores of events that can be accessed using jQuery methods. We begin with the keystroke-related event keydown. Students access this event using the conveniently-named Pencil Code event-binding function, keydown.

In practice, use of keydown is quite simple. For example, the statement

keydown "a", doSomething

binds any press of the a key to the callback function doSomething. While the program is running, whenever a is pressed, doSomething is executed.

Pencil Code provides two additional keyboard-related event-binding functions, keyup and keypress, which set up event listeners for two related keyboard events, keyup and keypress. The usage of these functions is analogous to that of keydown. While the keyup event is largely intuitive, the difference between keypress and keydown events is nuanced. keyup and keypress will be formally introduced to students in the Event Objects! lesson, and all three of these event types will be explored in much greater depth in the Notes to the Event Objects! lesson.

Event binding functions, listeners, and handlers

keydown is an example of an event binding function. An event binding function binds a specific event type to a specific HTML page element. It does this by registering the event and the page element with an event listener. While the JavaScript program is running, whenever that event fires on that particular HTML element, the event listener invokes the callback function that was passed to it when the listener was first set up.

When used in this type of event-related context, a callback that gets executed each time the event is triggered is formally referred to as an event handler—because it provides the instructions necessary for the program to appropriately respond to (i.e., deal with or "handle") that particular system event.

In common parlance, the terms event listener and event handler are often used interchangeably, though they are technically distinct. The listener listens out for the event happening on the page element (which from the perspective of JavaScript is considered the event source), and the handler is the code that it runs in response to the event happening.

Key events and animation queues

Key events are not synced with animation queues. As a consequence, even very simple programs that incorporate sprite movements or other animations can result in significant lags between button presses and resulting sprite behavior on screen. This can be illustrated with a simple "random mover" script, such as the following, which binds the space key to animations:

doOnSpace = ()->
  rt -30 + random(61)
  fd 10 + random(20)
keydown "space", doOnSpace

The most straightforward means to eliminate such lags is to switch from queue-based to frame-based animation, by setting speed to Infinity. An alternate approach is to embed a boolean flag in the event handler callback to prevent repeated keystrokes from resulting in a backlog of animations. In the following script, the boolean variable busy effectively causes subsequent presses of the space key to be ignored until the animations are complete. Note the use of the plan function, which ensures that the statement busy=false is indeed called at the right time in the queue:

busy = false

resetBusyFalse = ()->
  busy = false

doOnSpace = ()->
  if not busy
    busy = true
    rt -30 + random(61)
    fd 10 + random(20)
    plan resetBusyFalse
      
keydown "space", doOnSpace

A more sophisticated approach to prevent keyboard-related events and animations from getting out of sync involves the use event polling, a technique that will be explored in a later lesson.

Why events?

The use of events and listeners is a clever solution to a longstanding computing challenge in systems in which asynchronous inputs are involved, such as those coming from hardware devices (e.g., keyboard, mouse) and network connections. This section provides an explanation of events in terms of an analogy: think of the underlying OS as being a news outlet, and imagine your program to be a news recipient who needs to constantly stay up-to-date on the news to get his job done.

One approach our news recipient might take to stay up-to-date is to continuously and endlessly send out a stream of inquiries to the news service about whether there is any new information. Sure, this would keep him up to date. But all these queries would consume a lot of his attention, which he otherwise could be devoted to other tasks.

An alternative approach is for the news receipient to subscribe to a news feed from the news service. To subscribe, he registers with the news service, and instructs it what to do when it has news to share. His instructions might direct the news service to send him an email or a text message. His instructions could also instruct the news service to differentiate between different types of news. For example, maybe it should text summaries of all financial news to his cell phone and email complete articles on technology news to his email address. He does not subscribe to other types of news, so these never become a distraction to him.

This second setup is much more efficient. Instead of the news recipient initiating each transfer of information, he simply signs up and leaves the it to the information source to keep him up to date. This saves him lots of time. Morever, other consumers of news can similarly sign up, saving them time and energy as well.

This news feed setup is akin to using events. The news service is the computer's operating system. It keeps current on the news through inputs from its reporters (hardware device drivers), which the news service packages and shares with subscribers based on their expressed interests. The news recipient is an application, such as a web browser (and, by extension, a JavaScript program running within the browser). The recipient registers for types of news (specific event types) by registering with the news service (setting up listeners) by using a sign-up form (an event-binding function) which allows him to provide specific instruction to be carried out for each type of news event which he signs up for (the event-handler callback function).

Notes to activities

The lesson activities are intentionally limited to simple, straightforward uses of keyboard events. The goal is for students to gain familiarity with events and comfort using keybinding funtions and callbacks to incorporate events into programs. Even thus limited, this lesson opens up broad new frontiers and should be much fun.

Students may be tempted to extend the TurtleCannon program to include one or more targets. For example, one could add a series of fixed targets, or, alternatively, a single moving target. As noted in the lesson, that extension will be easier to accomplish after the introduction of the forever function and the use of frame-based animation.

In the meantime, the TurtleWarperGame activity, below, presents a similar coding challenge, and provides suggestions for solving it using the queue-based animation students have worked with thus far in the course. The TankBattle activity provides a similar set of challenges and some additional related tips for working through the animation-queue-related aspects of the script.

Additional activities

  • Create a TurtleWarper program that has the turtle move around the screen in response to arrow presses, leaving dots as it goes. When you press the space bar, it should fade out, then reappear in a location somewhere ahead of where it was. Animate the "warp", perhaps making it look like a black hole appears under the turtle.
  • Simon
  • TurtleWarperGame: Turn the code from the previous activity into a game, in which the turtle tries to earn points as it moves around the screen, and perhaps ends when it is is encounters a predator or some other demise. Be aware, however, that this is a pretty tricky extension: including a predator in the program is almost certain to lead to animation-queue-related complications.

    In a few lessons, students will be introduced to event polling, which arguably provides the optimal solution to this challenge. In the meantime, students have little option but to use a combination of iteration, i.e., a for loop with a very high number of iterations, and await done defer(). The embedded calls to await done defer() will prevent the large number of iterations from causing the program to hang, and will also make calls to touches function appropriately.

    However, it is not necessary to rely on await done defer() to code this activity. An alternative is to embed the code in each iteration of the loop in a callback function passed to plan. As described in the notes to the Callbacks lesson, plan is a sprite-specific function used to insert code into the animation queue, such as calls to touches.

  • Simon: At long last it's time to transform our Simon games (introduced in additional activities section of the Notes to the Arrays! lesson and continued in Musical Objects Interlude! and Push Pop!) into a working game. Copy your previous work to a new program in this folder. Then pick four keys to correspond to the "buttons" on the screen, and bind them to an appropriate action for each keydown event.

    Simon

    This code can quickly become very complicated, so think through the logic first, perhaps writing it down on a piece of paper. Because "game play" gets passed back and forth between the game and the player, consider using boolean flags (e.g., playersTurn, gameOver) along with logic statements to effectively "turn off" the keybindings at the appropriate time. You should not need to use await done defer(), but calls to sync will likely help a lot. Finally, be creative in your use of functions to simplify your code, most importantly by reducing redundant code (which will certainly become a maintenance headache). For example, you might encapsulate the logic for "the game's turn" in a function showNextChallenge, which you can first call to kick off the game, and subsequently call again each time the end user successfully completes a round.

  • MazeEscape: Code a script that generates a random map, such as by filling in each square of a grid one of two colors. This can be accomplished efficiently using calls to box as you iterate over a two-dimensional array (i.e., for rows and columns). Choose one color to designate a safe path and the other to denote a game-over-inducing hazard (perhaps lava? quicksand?). Position the sprite on one side of the map, and challenge it to reach the other side. Use keybindings for the arrow keys to control turtle movements. Use conditional logic after each move to determine whether gameplay continues or ends—either in victory or else an early demise for the turtle!
  • MazeEscape Blank space MazeEscape2
  • TankBattle: use custom sprites (or images) to create tanks that two different players can control simultaneously in a two-player game. Use keybindings as suggested in the image below.

    By now, defining keybindings should seem relatively easy. The tougher challenge here is the code that controls what happens when a player fires the cannon. An effective approach is to model the projectile using a sprite. When the projectile is fired, that sprite should move from the firing tank in the direction that tank is pointing. Now comes the conditional logic: the projectile should continue moving until it either strikes the other tank (test this using touches) or exits the screen (test this using inside). Of course, these boolean functions won't give you the desired output unless you set speed to Infinity or use calls to await done defer(). If you code your program using the latter option, note that sprite-specific calls to done can be helpful, e.g., await projectile.done defer(). The benefit of using sprite-specific calls is that it will not block the code associated with the other sprites.

    MazeEscape

    This program will take some time to work through. Even then, you will likely run into disappointment: players may get frustrated that their keypresses won't work whenever another player is pressing and holding a key. A subsequent lesson, Event Objects! , provides workarounds for this. We'll continue developing this program there.

  • PianoKeyup: Turn your keyboard into a piano! Use keybindings to make the keys A through K function as the "ivory" keys from middle C to the C one octave above. (Later on, after you have completed the basic exercise, you might also bind the corresponding keys in the row above to function as as the corresponding "ebony" keys.) Make this program function more like a real piano by making the note play longer if you continue to hold the key down. To do this, you will need to make use the keyup keybinding function. keyup is analagous to keydown, except that it binds to the keyup event, which fires when a key is released.
  • ControlledDot: Write a script that draws a randomly colored dot when you press the space key. The dot should continue to grow so long as you continue to hold down the key. A subsequent press of the key should start a new dot at a new location. You will need to define variables outside of your callback, i.e., global variables, to track the color and the dot size. You will also need to make use of keyup events.
  • MazeEscape
  • The Tangram is a puzzle that challenges you to arrange seven geometrical pieces—a square, a parallelogram, and five triangles—into a range of different shapes. Code a tangram board so that a player can manipulate blocks on screen. Bind arrow keys so that they control movement. You will also need to come up with key bindings to toggle between the pieces, to rotate the currently selected piece, and to flip it (by calling the mirror method). Once you have coded the blocks, see if you can meet the classic challenge: rearrange them, without overlapping, as a square!

Beyond the lesson

Events and the Document Object Model

The Pencil Code keydown event binding function simplifies the code required to set up keydown-event listeners. However, in so doing, it obscures a fundamental feature of events that is essential to their complete understanding: the connection between events and the HTML Document Object Model.

When a web page is loaded, the browser creates a Document Object Model (DOM) of the page. The DOM is a representation of the HTML document that acts as an interface between JavaScript and the document itself. The gives JavaScript the ability to do things like add, change, and remove HTML elements, change CSS styles, and react to GUI events such as key strokes and mouse moves and clicks—i.e., the DOM is what allows for the creation of dynamic web pages.

GUI events originate with the underlying operating system. The OS passes relevant information about each event to the currently-active program. When this program is a web browser, the information the OS communicates includes data that enables the browser, using the DOM, to identify which page element the event affects. If the system event matches an event registered for that element, JavaScript event listeners will invoke the event handler.

The Pencil Code keydown function by default binds keydown events to to the global window object. window is actually a reference to the window containing the DOM document, but for the purposes of this discussion, referencing window has the same effect as binding the event handler with the whole web page, i.e, the <html> element.

One can override keydown's default page element setting setting using dot notation, but doing so is premature at this stage. The default setup works well for students new to keyboard-related events, as it sidesteps a number of technicalities with which they would otherwise need to contend. These includes such things as focusability, event propagation, and bubbling. Subsequent lessons will explore these concepts as they introduce tools which facilitate binding events to specific page elements other than the whole document.

Auto-refiring of key events

keydown events automatically refire if a key is pressed and held rather than immediately released. There are two aspects of these refirings that are likely to impact student work, potentially in an undesired or frustrating way.

First, there is a significant lag following the initial event (about 400ms) before the first event refiring. Subsequent refirings occur with a much shorter lag (about 80ms). This can lead to rather clunky movements.

A second, and often more frustrating limitation is that subsequent key presses of a different key cancel the refiring of a previous keydown event for a pressed and held key. An illustration of this behavior is provided in this script.

For now, the primary practical solution to both of these issues is to simply have the end user repeatedly press keys rather than press and hold. The ideal solution is to use event polling, a technique addressed in the Event Polling! lesson.

Anonymous functions

In this lesson, students will need to define the callback prior to binding the keys. An alternative is to define the callback in the same statement as the call to the keybinding function, as an anonymous function. The coding snippet in the Pencil Code coding palette provides an example which students will invariably encounter:

Typing Test

This functionality will be introduced to students in a subsequent lesson. In the meantime, it is recommended to avoid the use of anonymous functions as it can cause students to lose sight of the fact that they are working with callbacks.

What can go wrong

Muddling up concepts

The Pencil Code keydown function is a useful tool for making a program respond to keydown events. The use of a function name matching the underlying event name is convenient and intuitive. The downside is that the dual use of the term can obscure the actual mechanics at work.

Be sure to make a clear distinction between an event (e.g., keydown event, which fires when a key is pressed, causing registered listeners to invoke their assigned event-handler functions) and the corresponding event binding function (e.g., keydown, which is used to set up the listener and thereby bind the event-handler functions to the specific event or class of events).

delete (on Mac) or backspace← (on PC) "doesn't work"

keydown events fire for most, but not all buttons on a standard keyboard. The most notable exception are deletion-related keys, i.e., delete (on Mac) and backspace← (on PC). The Event Objects! lesson will introduce two additional keyboard-related events, keypress and keyup, the latter of which can be used to track the use of the delete and backspace← keys.

focus

In the context of operating systems, focus refers to the currently active window or application that is receiving user input, essentially indicating which program is currently selected and will respond to keyboard or mouse actions. Focus is a way to manage which application is in the forefront and ready to receive commands.

In the context of web browsers, focus refers to which page element on the screen is currently active or selected, essentially indicating where the user is currently located on the page and which element is receiving keyboard input. On a typical web page, you can use the tab key or mouse clicks to navigate between focusable elements (including out of the web page itself to the browser's address bar or bookmarks bar).

For keyboard input to register in your running Pencil Code program, you have to have the focus on the right part of the screen. In Pencil Code "output" mode (i.e., when the URL to your Pencil Code program contains /home/), this is rarely an issue; the exception is if you have the focus on the web browser's address bar, in which case keyboard events will not fire in the program. However, in "development" mode (when the URL to your Pencil Code program contains /edit/), you have to click on the graphical output part of the screen to ensure that focus is there and not in the coding pane.

Blocking keybinding functions via await done defer()

In programs that make use of events, event bindings must be assigned prior to calls to await done defer(). Otherwise, calls to await done defer() will block that the execution of the keybinding functions and thus prevent those bindings from being assigned.

Technicalities

jQuery's on method

The Pencil Code keydown event binding function introduced in this lesson is a convenience function to assist novice coders. It greatly simplifies setting up event listeners for keydown events for specific keys. However, it is important to recognize that use of keydown obscures some important aspects of working with events. This section seeks to highlight some of these. Notes to later lessons will provide much more detail.

Behind the scenes, Pencil Code's keydown makes use of the jQuery on method for setting up listeners, using statements similar to this:

$("html").on "keydown", cb

As this example illustrates, the jQuery on method is called on a specific page element (here, <html>). The on method accepts two arguments. The first argument is a string that indicates the type of event (here, keydown). The second argument is a callback that is assigned to serve as the event handler (here, cb).

The need to register an event with a specific page element was described in the Beyond The Lesson section of these notes. The page element is selected using the jQuery selector function. The fact that Pencil Code's keydown function invokes the on method on the <html> element means that so long as the visible web page has focus, a keydown event will fire whenever a key is pressed.

Assigning key event listeners to the <html> element is a reasonable choice for beginners working with keyboard events in the Pencil Code environment. More advanced coders, however, may seek to gather keyboard input via other HTML elements, such as <input>, which provides the familiar text entry box common to so many web forms, e.g.,

Gathering text input from different page elements is complicated because it necessitates dealing with issues such as event bubbling and propagation. These topics are will be explored in notes to subsequent lessons. In the meantime, this script provides a sneak peak.

Another observation to make about the "behind-the-scenes" coding example above is that jQuery's on does not accept an argument to identify a specific key. Indeed, on sets up a listener that fires in response to any key press. To react to a specific key, we need to code the callback to evaluate the event's event object.

JavaScript creates an event object corresponding to every event for which there is an active listener. jQuery wraps this native JavaScript event object within its own object (retaining the JavaScript object with an originalObject property). Compared to the raw JavaScript, the jQuery object provides additional functionality and cross-browser consistency.

Event objects provide a wealth of information about each event. In the case of keydown events, this information includes not only the key that was pressed, but also a timestamp, a record of concurrently-pressed meta-keys (such as shift or control), and more.

jQuery automatically passes the event object to the callback specified as the event handler. To make the program react to a specific key, the callback passed to on should be written to evaluate one or more of the event object's properties, such as key or which. The following script provides a simple example of using an event object to control turtle movements using the arrow keys:

cb = (e) ->
  if e.key=="up"
    fd 25
  else if e.key=="down"
    bk 25
  else if e.key=="right"
    slide 25
  else if e.key=="left"
    slide -25
  else
    #do nothing!

keydown cb

Event objects are essential for harnessing the power of events. The use of event objects is the focus of two subsequent lessons. The Event Objects! lesson explores event objects in the context of keyboard-related events, using the one-argument syntax for Pencil Code's keydown, keypress, and keyup, similar to that illustrated above. The Mouse Events! lesson provides many more examples of working with event objects in the context of mouse-related events.

jQuery keydown method (deprecated)

A look at the jQuery API for Events will reveal that jQuery historically has defined its own keydown method. That method should not be confused with Pencil Code's keydown. Morever, note that jQuery's keydown method is deprecated, meaning that it should no longer be used. It is no longer maintained and is only retained for legacy purposes. Rather, as noted in the jQuery API, the preferred jQuery approach for setting up keydown event listeners is to use the jQuery on method, as described above.