EDITORIAL
TECHNIQUES Adventures With SVG In ePub
JAVASCRIPT Keyboard Class
ARTIST’S CORNER The Art Of Programming

JAVASCRIPT

Keyboard Class

Author: DS
Published: October 2014

A Thorny Problem

Ever been in the fields when you were a kid, trying to get that fat and ripe blackberry, over there, further up into that big bramble? Ouch! Keyboard input in an SVG document is quite straightforward, but then when trying to get backspace to work without causing this or that browser to go to the previous page, or this or that browser to do anything at all, it’s like above: ouch! But while with the bramble bush you can settle for two smaller blackberries and be just as happy, in the browsers’ bush you only have one blackberry to get, it’s that or nothing. We want a Key class that works right out of the box for any modern implementation. It’s just a matter of finding the right mechanism, proving by the same token that the browsers are actually well tuned.

The right mechanism is concealed and somewhat hard to find, but in the end really simple. It yields consistent results across all the major implementations (1), and because it allows discriminatory use of special keys it constitutes the basis for complete editing capabilities. The hard facts are:

  1. Keydown and keypress are used in conjunction. Keyup is somehow less important in terms of output, but there are uses and it’s included for completeness.
  2. The keydown handler is used to process the key codes for editing purposes (e.g. BACKSPACE, DELETE, ENTER), and possibly for output from keys that don’t have a printable representation. Misuse in the keydown handler affects the behavior of keypress.
  3. Preventing defaults affects the behavior of the entire keystroke sequence, therefore it must be used selectively in the keydown handler, which must then provide the character output for the keys that were prevented.
These points are discussed, applied, and demonstrated by the examples.

The Key class must meet these requirements:

The first point implies a model where a process is initiated–and its handling defined–at instantiation time, and the callback functions are valid for the life of that process only. A process can be terminated or even suspended, but cannot run simultaneously with other processes that may cause obnoxious interferences. Therefore the practice of adding resident event listeners to the window object and statically assign handlers is not suitable for wider environments. For example, suppose a game library, a mapping library, and a generic library all running in the same environment, and all registering listeners and handlers for the arrow keys; fatally those handlers will execute the different actions for which they were intended, and not necessarily in the order that you may wish, leaving you no other alternative than hacking. These situations make it obvious that keyboard input should only be handled by a system keyboard manager.

Osmotic Keypress

This authoring tool is conformant with the SVGDOM [1] specification (itself referencing the DOM Level 2 [2] specification). The limitations traditionally associated with different keyboard layouts are being addressed by DOM 3 and DOM 4 drafts. That process implies quite a few changes–it will probably shake your comfortable habits–and will finally provide a decent OOD model based on constructors (DOM 4). However, those issues seem to relate more to the key codes returned by keydown than the character codes returned by keypress.

My experience is that all major and modern implementations are in osmosis in respect to keypress (candidate for deprecation), which should have always been used for character output, and not for scanning the keyboard, the issue being then the restitution of the same key codes independently of platform and keyboard layout, rather than the validity of the methods for retrieving them. Obviously, the keyCode and charCode legacy implies that those attributes cannot be reassigned, but perhaps a redesign of charCode, with its new name of char, was not necessary since it works very well for that which it was designed for: output. And it actually does output any Unicode character independently of keyboard layout. For other situations, like reaching the old WASD nirvana, you need consistent codes from the keydown event. You can get an idea of what the new plans are by surfing through the drafts [3], [4], [5], and [6], for starters. Finally, at times one can be suspicious when “better” methods get more complicated.

The Constructor

The Key class defines these prototype methods: register(), for initializing a typing process; release(), for ending the current typing process; mousedown(), handler for mousedown event on document; handleEvent().

The register() method expects an initialization object specifying the required handle property–typically the object requiring the service–and the optional callback, cursor, stop and destroy properties (we will see these properties in detail in the example further down). The method registers “keydown”, “keypress” and “keyup” event listeners on the document with this handler. Yes, the practice of using only keydown or keypress events to do things which they were not intended for is a very bad practice, and a source of confusion in terms of “supposed” browsers’ behavior. The method also registers the “mousedown” event on the document to terminate the current process–the Enter key can also be configured to do this.

The commented code at lines 41-43 and line 62 refers to the input cursor, which we don’t have at our disposal here and which we will see in a future article together with an Input class.

The class property active is a reference to the current typing process–the Key instance that activated the keyboard scan.

The release() method ends the input following a “mousedown” event on the document. This behavior can be overridden by the stop property, which you can set dynamically to false for particular situations, and which we will see further down.

The release() method may also be explicitly invoked like this:

If the handle object has a method named endInput (to process the result, like sending information to the server, modifying the document, applying a color value to a target object, etc.), it is invoked with the optional parameters passed to release(). In this case, the presence of parameters signals to the endInput() function that input was ended by means other than those predefined for the Key instance, letting you take appropriate actions. The optional parameters are anonymous, and the method passes them as arguments[0], therefore if you do use this feature params would probably best be an object.

endInput is an arbitrary method name that you can eventually change to suit your existing code, as long as it remains consistent throughout all your objects using keyboard input. This function is also a type of callback, but we already have callback functions, and although it could be added to the callback object, I was too lazy to do it. More seriously, we can clearly see that in a more than just basic scenario like this one, listeners, handlers, and callbacks are not the same thing.

Callback vs Handler

The addEventListener method is a low level function expecting a function parameter that could technically be considered a callback, heritage of Functional Programming and traditionally associated with asynchronous operations, or… the this object, in which case the event is handled by the prototype’s handleEvent method. According to the DOM 4 Events working draft [7], what once was the handler, this time seems to be bound to be called callback:

Events are objects too and implement the Event interface (or a derived interface). In the example above ev is the event. It is passed as argument to event listener’s callback (typically a JavaScript Function as shown above)...

We notice how in this tentative terminology redefinition the word callback in bold is not hyperlinked to some more or less official definition. Hey, this is how you get a new tuxedo for free, and that’s not quite orthodox. By the way, callback is not the only candidate.

Event handling–or should we say “calling back” or “callbacking”?–is not necessarily event processing. When dealing with event handlers requiring the use of several conditions it is often preferable to do the processing in callback functions, manually passing the event if needed. A listener instead automatically passes the event to the handler.

One other relevant argument against this renaming is the case where a callback is a reference that may change dynamically. Instead, you would normally play with the add/remove event methods rather than replace the handler, thing that by the way is not even possible. Finally, in an Object Oriented environment the callback functions are often very simply instance or prototype methods that are just invoked by the event handler, leaving the callback idea with the old spaghetti code syndrome diehards.

As a general rule, if you are a specification maker and you’re running short of ideas, you can always rename stuff.

Callback Functions

Talking of which, the Key class defines class methods to serve as default callbacks for “keydown”, “keypress” and “keyup” handlers. It also defines a set of character filter methods that you can use as alternative callback functions for “keypress”.

The default callbacks are suitable for most situations, but for specific needs you can pass your own callback functions to the register() method.

These functions expect the handle object to have a value property and a setValue() method. If the handle object does not have these properties you must pass your own callbacks. Or you can rename value and setValue() to suit your needs.

Note that these functions, or your overriding functions/methods, are invoked as methods of handle, thus in those functions this is handle.

The class methods (together with the active class property):

Note the selective use of evt.preventDefault() in the keydown() function. This is a key factor and although it’s very obvious to many, it warrants a mention. If used without discernment it would prevent quite a few things from happening, not just the default actions!

Browsers’ Oddities

Among browsers’ oddities there are keyboard shortcuts for default actions assigned to punctuation characters commonly used while typing. This is the case for example in Opera where the + and - keys are assigned to zooming, or in Firefox where both the / (slash) and ' (apostrophe) keys open the search box. We cannot simply state evt.preventDefault() for those cases because we wouldn’t get the keypress either, and therefore no printed character. We will see how to prevent the default action and yet get the printed character further down in the example’s custom keydown handler.

The following paragraph is caustic and you may want to skip it. What seems an important consideration to me is that the browser is no longer just a hypertext viewer, but has become a platform for running portable software and for hosting web operating systems, and it would definitely make sense to have a specific and standard window method for programmatically deactivating/reactivating default browsing keys altogether without having to go through the pain of case by case treatment. A few of these cases are treated in the custom keydown callback of the example further down. They are not included in the default Key.keydown() callback for two simple reasons: first, to impose on programmers the obligation to pollute their code with fastidious tests for proprietary shortcuts is not correct; second, if you consider that you would normally also have to test at least the shiftKey property for each case, what happens essentially? Well, instead of having your lean and clean blocks for your editing needs you end up with a higher number of processing blocks that are not even needed for all browsers–don’t bother with browser sniffing, it’s only an aggravation; just push the Unicode characters. You will say “Yes, but then I might end up processing keydown for output more than keypress! Is this normal?” No, that’s not normal, you are being abused. I believe web application developers should put forward an official request for such a method. Or, ordinary typing keys should not be assigned to browser navigation/services without a control key. Commands should only be given through command/control keys. It’s for the Police to put order in no-man’s-land, not the citizens. It seems to me that the first one who wakes up in the morning decides to change what has been the custom since the beginning of time and assign a find control to the slash key. Could this be because with the slash key it is somehow easier on some smartphone? If so, are we sacrificing on the altar of the “information” nonsense of the Hysterical Web? Stop the world, I want to get off. But this is not all, you also have the hijacker method: some browsers go as far as intercepting some key codes to prevent you from preventing them; it's the case for example with Internet Explorer intercepting the F1 key to open the help window in any case, and Opera intercepting the F3 key to open the search box (this affects the input because the search box gets the focus). If the browser can override the system's default for the F1 key, then an application/system running in the browser must be able to do that, as well.

Output Filtering

The extra keypress callbacks are designed to filter. For instance, if you have a hex color input box the Key.keypressHex() function will prevent the user from entering unwanted characters, and will save you the pain of filtering, or give frustrating warnings to the user after the input has ended. Key.keypress() is the default handler, and if for example you need to use Key.keypressHex() instead, you must specify it in the callback object, as shown in the next section.

Keypress filter functions:

You will have probably noticed in the Key.keypress() function that the character code 127 is not considered in the conditions: the ASCII code 127 (Delete) does not have a printable representation, and therefore it is not returned by the keypress event.

Instantiation

A Key instance is designed to be assigned to a property of some object like a text box, or an input box, for example. In a UI environment activation would normally happen upon clicking a text tool, or selection of an existing text object for editing, or clicking an input box, or whichever action including the selection of a shoot-’em-up weapon. The basic example that we will see further down is instead noncontextual, with no control over the text element after input.

After instantiation of the Key class you must invoke the register() method passing an object whose properties are inherited by the instance, where the handle property is required:

To override the default callback functions you define the callback property, where you can specify any or all of the custom callback functions:

Note that if there exists an instance or prototype method of the handle object named keypress you don’t actually need to specify it in the callback object, as it will automatically override the default Key.keypress(). This is true for keydown and keyup as well.

As you can see in the register() code (line 27 in The Constructor) the callback property can also get a function. In this case the function that you specify will be the unique callback for the three event types. Good luck!

There are three other properties that the class can process:

cursor – Activates a blinking cursor. To present a cursor class is not within the scope of this article, and this is why the lines 41–43 and line 62 in The Constructor section are commented. A cursor class will be treated in a future issue of SVG magazine, along with a Type class.

stop – When statically or dynamically set to false it will prevent some actions in a user interface (acting on a component–like top bar or scrollbar for example–of the input container) from causing end of input. In practice you would never set this property to false when you invoke register(), and you actually don’t need to set it at all (see line 49 in The Constructor section). You would instead set it statically in the event handlers of those interface objects that you do not wish to interfere with the input, or set it dynamically on certain conditions. For those cases, after the interfering action has ended don’t forget to set it to true:

otherwise input will never end. We cannot see this in action in a simple environment like the example below.

destroy – Gets the name (string) of the property to which the Key instance was assigned. If set, the object will cease to exist–unless a reference to it exists, in which case the object will be kept in memory (lucky enough!). Other than that, delete deletes, even though the garbage collector may not free the memory right then.

Examples

For our needs here (live example [8]) we are going to define a very basic text constructor, with no possibility to override text attributes, or edit attributes or strings on selection (in a future issue we will present a complete Type class):

As it stands, this example uses the default callbacks. Uncommenting the custom keydown method (lines 28–92) and the callback property (lines 98-100), overrides the default Key.keydown(), allowing the use of tabs and the use of <ENTER> for line feed (for which you will have to write the code, and in this case end of input only happens by clicking anywhere on the document).

It also shows at lines 55-90 how to prevent the browser’s shortcuts for the keys =+ -_ + (numeric pad) - (numeric pad) /? and '" on keydown, while outputting the printable characters that keypress will fail to provide. Unfortunately this overriding of the krypress event doesn't come without pain: if you are using one of the keypress filtering functions keypressPos, keypressPosInt, or keypressHex, the - and + characters will be printed anyway, the keystroke sequence is interrupted and the event will not reach the specific keypress function. This is a further aggravation beyond those already exposed in the Browsers' Oddities paragraph, like a chain reaction. While we wait for things being straightened, we have to cope with these fantasies. The way to do that is to add for the Text class a new prototype property referencing the keypress function to use (with initial value the generic Key.keypress), and an instance boolean property to test in the custom keydown hack extensions, to exclude printing of shortcut characters. This simple Text class is not designed to be a complete input class, and within this context and layout these operations would sum up to a sequel of hacks. The procedure for initializing the input, below, is not good coding, as we've seen in the introduction. The examples listed in the More Examples section below use instead a fully-fledged Input class, which we will publish in the next issue of SVG magazine.

The rest of the custom keydown is the same as the default keydown callback. Beyond preventing the default actions for the keyboard’s function keys F1–F12 as a block, you can try breaking into individual cases and reassign some of them, knowing however that IE and Opera won't let you reassign the F1 and F3 keys, respectively.

We have already seen that a custom callback with the same name as the default callback automatically overrides it; therefore in this particular case you only need to uncomment the code for the keydown method (lines 28–92); you don’t actually need to uncomment the lines 98-100, which are there to simply show the use of the callback property. Note: the live example [8] uses the custom keydown callback.

Finally you can add an event listener and define its handler (or decree any arbitrary action), which instantiates the Text class and invokes the keyboard initializer:

Every click on the document stops the current typing process, if any, and creates a new one.

In this state, the anonymous Text instance is pretty much useless. In a more credible situation it could be something like: object.text = new Text(), where object.text.element is a reference to the DOM object.

Note: SVG() (2) and mousePoint() are library functions (included in the example files). mousePoint() is necessary in a HTML–SVG context, where the SVG is in some <div> container. In standalone SVG you can use evt.clientX and evt.clientY directly. The added units are needed to adjust the text baseline to the system text cursor’s bizarre hotspot.

You can download the Key class with the example here [9].

More Examples

RGB and Hex values input [10] (keypressPosInt and keypressHex)
Longitude Latitude input [11] (keypressNum)
Find Dialog [12] (default keypress)
Datagrid [13] (several types)

Compatibility

The Key class is compatible with all the major implementations.

Notes

(1) At writing time three anomalies persist in Firefox: 1) the ;: key returns key code 59 instead of 186; 2) the -_ key returns key code 173 instead of 189; 3) the =+ key returns key code 61 instead of 187.

(2) See the DOM Helper [14] article in the July 2011 issue of SVG magazine.