Archive for January, 2013

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.