Archive for the ‘Javascript’ Category

Fixed a bug where the selection was not set correctly (a regression from when I updated bililiteRange). It's now at version 2.2.

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.

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.

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

I've been playing around with creating an online editor, and one thing it needs is keyboard shortcuts. Control keys and the like don't register keypress events, so I have to use keydown to get the event.which (and I have to use jQuery to normalize that across browsers), and then I have a random keyboard code and a bunch of modifier key flags to work with. The W3C once tried to rationalize all this, but no one picked up on it. So I wrote a jQuery plugin to turn the event into a friendlier string (using a notation based on Microsoft's sendkeys). Thus:

  • Pressing the 'a' key gives $.keymap(event) == 'a'
  • Pressing the 'a' key with shift held gives $.keymap(event) == 'A'
  • Pressing the 'a' key with control held gives $.keymap(event) == '^A'
  • Pressing the 'a' key with alt held gives $.keymap(event) == '%A'
  • Pressing the 'a' key with control and alt held gives $.keymap(event) == '^%A'
  • Pressing the 'Esc' key gives $.keymap(event) == '{esc}'
  • Pressing the 'Esc' key with shift held gives $.keymap(event) == '+{esc}'
  • Pressing the '*' key on the numeric keypad gives $.keymap(event) == '{multiply}'
  • Pressing the '1' key on the numeric keypad with control held gives $.keymap(event) == '^{1}'

Download the code.

See the demo.

Continue reading ‘Parsing keydown events’ »

One thing I wanted to add to my bililiteRange was the ability to scroll the range into view without changing the selection. As with all things having to do with ranges, there's no consistent way to do it, and Internet Explorer does it best (this is the only time you'll hear me say that).

Continue reading ‘Scrolling to Cross-browser Ranges’ »

Edited 2014-02-27 to add the indentation code.

I created a collection of useful plugins for my bililiteRange, for more sophisticated manipulations. Now you can search the element with a regular expression, and you can keep the range "live", adjusting it when the text is edited.

Download the code.

See the demo. (The dynamic highlighting is off by a few pixels in iOS Safari. I'm still working on that)

Continue reading ‘bililiteRange Plugins’ »

I've been working on a project that uses bililiteRange a lot, so I added two improvements: plugins and events.

bililiteRange.fn.myplugin = function() {}; or bililiteRange.extend({ myplugin1: function() {}, myplugin2: function(){} }); creates new functions for a bililiteRange (just like with jQuery).

Also, the text method now triggers an input event on the element. Only works on browsers that implement CustomEvent.

I've been using Ilya Lebedev's JavaScript VirtualKeyboard for a while, and even created a small plugin to integrate it with jQuery. But I wanted to make it more jQuery-ish, so I made a new version of jsvk, the plugin to wrap Ilya's code.

See a demo that allows dynamic control of keyboard layout and skin (CSS), and a demo that shows multiple inputs, including contenteditable and jQuery UI's draggable.

Download the code.

Usage

$(element).jsvk(opts) to initialize a text-inputing element (either <input>, <textarea> or <div contenteditable>), $(element).jsvk('off') to detach the keyboard, and $(element).jsvk() reattach the keyboard leaving the options the same. Note that the VirtualKeyboard itself is a Singleton, so there can only be one open at a time (attaching to one element removes it from another) and that setting opts changes the appearance for every attached keyboard. You can do $(element).jsvk(opts) on an existing attached keyboard to change the options.

It requires bililiteRange to set the text and selections (the original code does not handle <div contenteditable> and does not trigger input events).

For <div contenteditable>, the enter key just inserts a newline, which is treated as white space and thus does not go to a new line. You could try to listen to the input event and insert a <br>, or do what I do in the demo and set the style to white-space: pre-wrap. This makes all white space significant, which is generally what I want anyway.

Options

dir
{String} URL of the directory (without a trailing slash) where the VirtualKeyboard is found. It expects a fixed structure of the directory, so just install it as is from the download. This is used exactly once per page; until it is set in some call of jsvk all other calls are held and once it's set any future uses of this option is ignored.
kbdiv
{jQuery object | Element | String } The container that the keyboard will be placed into (actually $(kbdiv)[0]). If not set, creates a new element with $('<div>').insertAfter(this).
skin
{String} Name of the CSS directory for styling. The choices are the names of the folders in the css directory in opts.dir; they are listed in the subversion directory and the right of the online demo.
layout
{String} Name of language/layout, like 'IL Hebrew' (see the bottom of the online demo for choices).
langfilter
{Array of String} list of language codes (the two-letter codes; must be uppercase) to allow in the drop-down menu for language choices (see the right of the online demo for choices). Note that this overrides opts.layout; if the selected layout is not in the filter list the first layout that is will be chosen.
nokeyevents
{Boolean} The Virtual Keyboard normally captures keystroke events to translate them into the virtual keys. Set this to true to detach the event capturing (the on-screen keyboard still works with clicking the buttons). This uses a very hacky way of grabbing the event handler, so it may be buggy, though it seems to work for me.

Examples

  • $('#input').jsvk({ dir: '/inc/VirtualKeyboard', skin: 'air_mid' }). Open a VirtualKeyboard with a specific skin but the default layout.
  • $('#input').jsvk('off'). Hide and detach the VirtualKeyboard.
  • $('#input').jsvk(). Show the VirtualKeyboard, with skin and layout unchanged.
  • $('#input').jsvk({layout: 'US US'}). Show the VirtualKeyboard, with skin unchanged but set the language and layout.
  • $('#input').jsvk({langfilter: ['GR', 'BA']}). Show the VirtualKeyboard, but only allow Greek and Bosnian layouts.
  • The Bililite Hebrew Keyboard uses this plugin, including the nokeyevents option.

Not sure when this happened, but google.load('jquery', '1') is frozen at 1.7.1. Now the official Google Libraries recommends loading the version explicitly: <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>. That means I have to edit my pages by hand every time they come out with a new version. I suppose that makes things stabler; I don't upgrade until I want to. Annoying for me. Ah, well.