Archive for March 20th, 2013

This post is obsolete; the $.keymap has changed significantly. Documentation is now at http://bililite.com/blog/2015/01/12/rethinking-keymap/.

Inspired by John Resig's hotkeys, I created a version that uses the modern on, and uses an object rather than a string for the keys (which would be confused with a selector). It also uses my $.keymap codes.

It's written as a plugin to $.keymap.

Download the code.

See the demo, or the demo with regular expressions.

It's part of my bililiteRange repo on github.

Usage

This modifies the event handling code so that you can listen for specific keys or sequences of keys (note that you can use keyup as well as keydown, but that is less useful):

$('input').on('keydown', {keys: '^A'}, function() {alert('Control A was pressed')} );

The original $('input').on('keydown', function() {alert('A key was pressed')} ); is unchanged.

Remove the handler with $('input').off('keydown', {keys: '^A'} );. Use exactly the same keys expression as when the handler was attached.

It handles selectors correctly (I think):

$('body').on('keydown', {keys: '^A'}, function() {alert('Control A')} );
$('body').on('keydown', 'input', {keys: '^A'}, function() {alert('Control A was pressed in an input')} );

Attaches the handler for <body> and keys ^A, then adds a handler that is only triggered when the target element is an <input> and the event bubbled up to the <body>.

Remove selector-specific handlers with $('body').off('keydown', 'input', {keys: '^A'} );.

Note that the handler function is replaced internally, so removing handlers with $('body').off('keydown', handler); will fail. It's not a good idea anyway (see the documentation); use namespaces instead ($('body').off('keydown.myinstance');).

Sequences of keystrokes are indicated by a space-delimited list:

$('body').on('keydown', {
    keys: '{up} {up} {down} {down} {left} {right} {left} {right} b a'
  }, 
  function() {alert('Konami!')}
);

Listens for the Konami code.

Normally, when listening for a sequence, the keydown events that match are discarded. If you want them to be passed to other handlers, use allowDefault:

$('body').on('keydown', {
    keys: '{up} {up} {down} {down} {left} {right} {left} {right} b a',
    allowDefault: true // the arrow keys will still work
  }, 
  function() {alert('Konami!')}
);

To prevent other handling on the final event of a sequence, use the usual preventDefault or return false in the handler.

Regular Expressions

In addition to specifying the keys with a string, you can use a regular expression:

$('body').on('keydown', { keys: /\d/ }, 
  function(event) {alert('A digit key was pressed:' + event.hotkeys)}
);

The actual key or sequence of keys is returned in event.hotkeys. Note that passing the keys as a string will "normalize" the keys with $.keymap.normalize to match that returned by $.keymap (i.e. {keys: 'ctrl-a'} will work). Passing a regular expression needs to be normalized (i.e. {keys: /ctrl-\w/} will not work; use {keys: /\^\w/}). Multiple keys need to be separated by a single space ({keys: /\d \d/} to catch two digits in a row). This will succeed on the first sequence of characters matched, with no look-ahead, so you can't do {keys: /(\d )+/} to match multiple digits; it will succeed on the first digit pressed.

Events

The plugin also triggers two other events: 'keymapprefix' when the event is the prefix of a valid sequence, and 'keymapcomplete' when a sequence is matched. Both are sent with an additional parameter indicating what keys were pressed so far (in $.keymap notation). So:

$('body').on('keymapprefix keymapcomplete', function(event, keys){
	alert ('sequence pressed: '+keys);
})

More Information

Understanding special event handling in jQuery is very complicated; there is an article on learn.jquery.com that is invaluable. But this is one area where you will have to go through the source code. No way around that, but it's a very good way to get your brain about the way jQuery works, in a deep way.