Archive for March, 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.

Well, I've finally decided to join the 21st century and get my projects on github. Some of them are useful enough that others have expressed an interest in extending them, and it will force me to have the discipline to use version control consistently. The projects I have on there are:

bililiteRange
Includes the original bililiteRange (for which I know there are some bugs, especially in IE and Opera; another advantage of github is to allow others to formally raise bug reports) and sendkeys, plus other associated libraries and jQuery plugins.
jquery-css-parser
My CSS parser, along with the demo page and ancillary functions.
flexcal
My jQuery UI date picker for multiple calendar systems, along with timepickr, a mouse-friendly time picker, and their dependencies.

Edited 2013-12-11 to use focusout rather than blur event

Continuing my adventures in developing an online editor, I wanted to be able to do "live" searching: having the text scroll to the sought string as it is being typed. I can use my bililiteRange utilities to find an arbitrary regular expression, but after much hair-pulling, realized that my texthighlight plugin is just too flaky and hack-dependent to be used reliably. So I went a different route: instead of trying to highlight strings in a <textarea>, I overlay a <pre> element desinged to look similar enough to the <textarea> and highlight the string in that with normal HTML. When the user is done typing his search string (signaled by a blur event on the <input> element being typed in), remove the <pre> and select the desired string in the original <textarea>. There's a jump but not as bad as with texthighlight.

Download the code.

See a simple demo, a demo that uses the parameters, and a demo that searches three elements at once.

Continue reading ‘New jQuery plugin, livesearch’ »

I liked the hack that I used in the bililiteRange utilities demo to highlight the ranges in a textarea: creating a "shadow" preelement and using that to set the background of the desired text. It's based on this trick (explained in a ddforum post) by someone with a screen name of Trinithis.

I put it into a jQuery plugin, $.fn.texthighlight(bounds, id, color) that lets you quickly highlight a section of text in a textarea that will scroll with the text (having the highlighting stay with the text as it's edited is a different story; see the source for the bililiteRange utilities demo for that). It is a hack, and remains flaky (especially for Internet Explorer, for which I cannot get white-space: pre-wrap to work consistently).

Download the code.

See the demo.

Parameters

bounds
{Array [start, end]} | "remove" | "delete". Required. The starting and ending character positions to highlight. "remove" removes the given highlighter, and "delete" removes all of them and cleans up the wrapping element and data.
id
{String}. Optional; if falsy, uses "default". An arbitrary string to identify the highighter for future changing or removing.
color
{String}. Optional; if falsy, uses "#f0f" (a nice, bright magenta:    ). The CSS color to use for the highighting (at 0.25 opacity).

Events

After highlighting, triggers a "texthighlight" event, with parameters [span, bounds] where span is a jQuery collection with one item, the highlighter elemeent, and bounds is the array originally passed into the plugin. So $('textarea').on('texthighlight', function (event, span, bounds) {span[0].scrollIntoView()}) will scroll the highlighted area to the top of the window whenever the highlighter is set.