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.

tl;dr: use $.screenfade(callback) to gently fade the screen to white, have the callback make changes, then fade the screen back in.

Download the code.

See the demo.

I like WordPress's full screen editor mode for distraction-free editing, and I especially liked how gradually it fades the full screen in and out. Making a text box fill the screen width is straightforward as long as you remember to set the box-sizing as follows: textarea { width: 100%; box-sizing: border-box; -moz-box-sizing: border-box;}. There's no way to make the box fill the available screen height, though, with CSS alone and we have to resort to Javascript:

	var defaultHeight = $('#editor').height();
$(window).on('resize', function(){
	var restHeight = $('body').outerHeight(true)-$('#editor').height();
	$('#editor').height(Math.max($(window).height()-restHeight, editorHeight));
});

And then use CSS to remove the distractions:

body.fullscreen head, body.fullscreen footer, body.fullscreen nav {display: none}
body.fullscreen article {width: 100%; margin: 0; background: white}

And use $('body').toggleClass('fullscreen'); $(window).trigger('resize') to switch between full screen and full distractions modes.

But the other thing WordPress does is make the transition gradual, fading out the screen then fading the new mode back in, which I find much more pleasant. I initially used CSS3 transitions, which work wonderfully for the CSS changes but we need to trigger the Javascript as well. There is a transitionend event that we could bind to, but that would mean having the height adjust after the other transitions. I didn't like that.

Then I tried fadeOut of the entire body then fading back in, but jQuery adds a display: none at the end of fading out via opacity, so that ended up being too jerky. $('body').animate{opacity: 0} was smooth enough, but left the body background color in place, which then changed abruptly to the new color. $('html').animate{opacity: 0} worked beautifully, almost.

There's a weird Chrome bug (seems to be http://code.google.com/p/chromium/issues/detail?id=131588 but they claim to have fixed it) where the scroll bars lose their alpha-channel when the opacity is less than 1, so they turn black rather than transparent. Ugly (demo page).

So I decided instead of fading out the real elements, I'd fade in a covering element, make my changes, then fade the cover out.

The plugin is called as $.screenfade(callback, [options]) where callback is a function to be called after the fadeout and before the fadein, and options is a bit of a hack: it is passed both as the CSS of the covering element, and as the options object for the animation. Since the two do not have overlapping parameters, it should work. Thus $.screenfade(callback, {background: black, duration: 2000 }) will fade the screen to black over 2 seconds, execute the callback, then fade back to normal over two seconds.

Turns out jQuery UI version 1.9 (which is listed on their site as the stable one, but is not served as the latest by the Google library API or listed as such on jquery.com; I assume things are still in a bit of flux) incorporates many of my ideas for subclassing widgets. So you could replace the entire section headed "Subclassing Widgets" with the following:

Note that this post throws an error since it loads UI twice, once as the 1.8 (which is what Google serves as the latest stable version) and again to use 1.9. That doesn't affect the code below.

Subclassing Widgets

When you use an existing widget as the base for a new one, it becomes a subclass of the original, with the original methods available from within the new code with the _super method


$.widget('ui.superbox',{
	_init: function(){
		var self = this;
		this.element.click(function(){
			self.move();
		});
	},
	move: function(){
		this.element.css (this._newPoint());
	},
	_newPoint: function(){
		return {top: this._distance(), left: this._distance()};
	},	
	_distance: function(){
		return Math.round (Math.random()*this.options.distance);
	},
	options: {
		distance: 200
	}
});

$.widget ('ui.supererbox', $.ui.superbox, {
	// overriding and new methods
	move: function(){
		this.element.animate(this._newPoint(), this.options.speed);
	},
	home: function(){
		this.element.animate({top:0, left:0}, this.options.speed);
	},
	options: {
		speed: 'normal'
	}
});

We now have a new widget called supererbox that is just like superbox but moves smoothly.

Experiment 2 (Click Me)

Calling Superclass Methods

If we want to use the superclass methods in our method, we use this._super:


$.widget('ui.superboxwithtext', $.ui.supererbox, {
	move: function(){
		this.options.count = this.options.count || 0;
		++this.options.count;
		this.element.text('Move number '+this.options.count);
		this._super(); 
	}
});
Experiment 3 (Click Me)

Note that the builtin subclassing does not include the aspect-oriented code, and does not include the code to automatically call the superclass _init and _create methods. It also adds the _super wrapper to every method, not just those that use _super. So I still like my version better. But this means you don't have to use my $.ui.widget; you can use vanilla jQuery UI to get much of the same benefit.

The bilirubin graph uses jQuery UI and used to use the official theme switching widget, which I just discovered has been broken since September 2012. That's the problem with graceful degradation; when something breaks the page still works so smoothly I don't notice unless I'm paying close attention. Anyway, David Hoff has written a replacement, Super Theme Switcher which works well, but requires an explicit option to tell the plugin where to find the sample images:

$('#themeswitcher').themeswitcher({
	imgpath: '/inc/themeswitcher/images/'
});

The other thing I needed was a way to detect the change in theme. themeswitcher has an onSelect callback, but that is called when the new stylesheet is added to the page, not when it is actually loaded. I previously used a hack with fake images. But that was a terrible hack. Chrome still doesn't trigger load events for stylesheets, so that won't work. But all the modern browsers support CSS transitions, including the transitionend event (webkit requires a prefix).

So to detect a change in stylesheet that affects, say, elements with a class of ui-state-default, do the following:

var div = $('<div class=ui-state-default>').css({
  transition: 'color 0.01s',
  webkitTransition: 'color 0.01s',
  visibility: 'hidden' // make sure the element is invisible
}).appendTo('body').on('transitionend webkitTransitionEnd', function(){
  alert $(this).css('color'); // or whatever
});

Internet Explorer, of course, doesn't support CSS transitions (we'll see about IE 10), but luckily they are the only ones to correctly support stylesheet load events and we can use $('link:last').one('load', function(){ whatever });. So we can detect stylesheet loading in either case, and create a new version of themeswitcher that calls the onSelect callback when the stylesheet is loaded, not when it is added:

// create the test div
var div = $('<div class=ui-state-default>').css({
	transition: 'color 0.01s',
	webkitTransition: 'color 0.01s',
	visibility: 'hidden'
}).appendTo('body');

 // decide if we can handle CSS transitions
$.support.transition =
 document.body.style.transition !== undefined ||
 document.body.style.webkitTransition !== undefined;

$.fn.themeswitcher2 = function(opts){
	if (opts.onSelect && $.support.transition){
		div.on('transitionend webkitTransitionEnd', opts.onSelect);
		delete opts.onSelect;
	}else if (opts.onSelect){
		// this is really browser sniffing; it turns out that the only major browser that doesn't support transitions is IE, and they support
		// the stylesheet onload event
		// We know that themeswitcher adds the last stylesheet, so tie into that load event
		var onSelect = opts.onSelect;
		opts.onSelect = function(){
			$('link:last').one('load', onSelect);
		};
	}
	return this.themeswitcher(opts);
};

And this works in Chrome, Firefox, Opera and IE. See it in action on the bilirubin graph.

I was playing with a minor project and wanted to simulate a command line interface, like the old April Fools xkcd, and realized that their code was far more complicated than what you need if you use jQuery and contenteditable.

My quick jQuery plugin to turn any <div> into a terminal emulator is:

	$.fn.cli = function(handler, prompt, effect){
		if (!prompt) prompt = '&gt;&nbsp;';
		if (!effect) effect = $.fn.text;
		return this.each(function(){
			var self = $(this);
			function newline(){
				self.
				 append('<p class=input><span class=prompt>'+prompt+'</span><span  style=outline:none contenteditable></span>').
				 find('[contenteditable]')[0].focus(); // focus only works on the element, not the jQuery object
			}
			newline();
			self.on('keydown', '[contenteditable]', function(evt){
				if (evt.keyCode == 13){
					this.removeAttribute('contenteditable'); // the old input line should not be editable
					// standards use textContent, IE uses innerText
					effect.call($('<p class=response>').appendTo(self),handler(this.textContent || this.innerText));
					newline();
					return false;
				}
			});
		});
	};

And you use it like:

$('div').cli(function(text){
  return 'You wrote: '+text;
});

See the demo. The font there is Peter Hull's VT323.

handler is a function that is passed the text in the input line and should return the text to output. prompt is the prompt string. effect is the function that lets you do fancy things to the output. It is called with this set to a <p> element that is to contain the text, and is passed one parameter, the text to output. It defaults to jQuery.fn.text, just showing the text.

It's not fancy; any actual processing of the text is up to you. Catching special characters like the up-arrow for history is also up to you. It doesn't have the old-fashioned blinking block cursor, since the insertion point caret is not styleable, and faking it is not worth the effort.

If you want a mind-blowingly cool terminal emulator, see mass:werk's termlib. And check out their cool Space Invaders game on their 404 page.