Sunday, January 31, 2010

JavaScript Virtual Keyboard

Introduction

Imagine that you are sitting in a London Internet cafe wishing to write an e-mail to your family living in Athens. It's good if someone in your family speaks English. If not, where would you find a keyboard with a Greek layout? I'm sure you can recall a dozen situations when you thought, "I wish I had another keyboard." This article presents the Virtual Keyboard that solves this usability problem. The design task for it can be specified as follows:

* Allow text input from computers without the user's native language layout installed, therefore allowing the creation of national and multilingual interfaces -- e.g. Web-based e-mail -- that can be used worldwide.
* Allow text input from computers without keyboards or with sensor screens -- hand-held PCs, smartphones, etc. -- or with remote controls such as mice, e-pens, etc. being the only input devices.
* Protect users from keylogger-type spyware.

Installation of the Virtual Keyboard requires a casual knowledge of HTML and JavaScript. To be able to fine-tune the script, you must be familiar with W3C DOM Level 1, CSS Level 1 and the DOM/Microsoft Internet Explorer event model. Virtual Keyboard is an open-source script distributed under the zlib/libpng license.
Setup

Six easy steps:

1.

Download the source archive.
2.

Choose your installation:
*

vkboard.js (1-vkboard folder in the archive) is the primary script. Full keyboard is simulated as follows:

Screenshot - jvk-full.jpg
*

vkboards.js (2-vkboard_slim folder) is almost the same as previous, but the language menu, which can be accessed by clicking on the red rectangle in the left-bottom corner of the keyboard, has a special configuration. The cells are arranged in rows, not as a simple drop-down menu like in the previous variant.

Screenshot - jvk-slim.jpg

This installation is recommended if you're short on space (UMPC, touchscreen kiosk, etc.) or if you have 4 or more layouts installed.
*

vnumpad.js (3-numpad_full folder) is the numpad part of the keyboard.

numpad variant
*

vatmpad.js (4-numpad_atm folder) is a stripped numpad that contains only the Enter and number keys.

numpad_atm variant
3.

Include a reference to the chosen script file in your HTML page:
Collapse






...


...

Note that for each type of installation, two script files are available:
* vkboard.js /vkboards.js /vnumpad.js /vatmpad.js are the original script. If you wish to change the script or just want to learn how it works, this is the file for you to look at.
* vkboardc.js /vkboardsc.js /vnumpadc.js /vatmpadc.js is a compressed version of the script, respectively 30.5%/30.5%/39.8%/39.5% smaller than the original. This is the file you should use on the Web.
4.

Define a callback function:
Collapse









...

The callback function must have one or two parameters. The first parameter is mandatory and accepts a string value returned by the vkeyboard script. The second parameter is optional and accepts the id of the container of the vkeyboard that called the callback. It may be useful if you bind a single callback function to multiple vkeyboards. Note that this is the most basic callback function. An example of a more advanced code snippet is given later.
5.

Define a container for the keyboard, which must be an empty DIV or SPAN.
Collapse



...



...









6.

Finally, show the keyboard.
Collapse





Of course, the creation of VKeyboard -- numpad: VNumpad, ATM-style numpad: VATMpad -- can be used anywhere a JavaScript code can be.

API

The VKeyboard constructor has a myriad of parameters to help you control its look and feel. Here is the full list, together with parameter defaults.
Collapse

var vkb =
new VKeyboard("keyboard", // container's id, mandatory
keyb_callback,// reference to callback function, mandatory
// (this & following parameters are optional)
true, // create the arrow keys or not?
true, // create up and down arrow keys?
false, // reserved
true, // create the numpad or not?
"", // font name ("" == system default)
"14px", // font size in px
"#000", // font color
"#F00", // font color for the dead keys
"#FFF", // keyboard base background color
"#FFF", // keys' background color
"#DDD", // background color of switched/selected item
"#777", // border color
"#CCC", // border/font color of "inactive" key
// (key with no value/disabled)
"#FFF", // background color of "inactive" key
// (key with no value/disabled)
"#F77", // border color of language selector's cell
true, // show key flash on click? (false by default)
"#CC3300", // font color during flash
"#FF9966", // key background color during flash
"#CC3300", // key border color during flash
false, // embed VKeyboard into the page?
true, // use 1-pixel gap between the keys?
0); // index (0-based) of the initial layout

Please be careful when upgrading vkeyboard from earlier versions. Always check the number/flow of the parameters. VNumpad and VATMpad have a limited set of parameters:
Collapse

var vkb =
new VNumpad("numpad", // container's id, mandatory
pad_callback, // reference to the callback function, mandatory
"", // font name ("" == system default)
// (this and following parameters are optional)
"14px", // font size in px
"#000", // font color
"#FFF", // keyboard base background color
"#FFF", // keys' background color
"#777", // border color
true, // show key flash on click? (false by default*)
"#CC3300", // font color for flash event
"#FF9966", // key background color for flash event
"#CC3300", // key border color for flash event
false, // embed VNumpad into the page?
true); // use 1-pixel gap between the keys?

Note that although "flash switch" is off by default, it is switched on in all of the provided samples. VKeyboard has the following public methods:

* Show: Shows or hides the whole vkeyboard. It has a single optional parameter, a boolean value that states whether to show or hide.
Collapse

vkb.Show(true); // Shows the vkeyboard
vkb.Show(false); // Hides the vkeyboard
vkb.Show(); // Shows the vkeyboard again. Action is by default.

* ShowNumpad (full and slim variants only): Shows or hides the numpad. It has a single optional parameter, a boolean value that dictates whether to show or hide.
Collapse

vkb.ShowNumpad(true); // Shows the numpad
vkb.ShowNumpad(false); // Hides the numpad
vkb.ShowNumpad(); // Shows the numpad again. This action is by default.

* """"set-param"""">SetParameters: Allows you to adjust vkeyboard parameters at runtime. The function accepts an even number of values in pairs: the vkeyboard parameter name and parameter value.
Collapse

// Adjusting single parameter:
vkb.SetParameters("font-color", "#F0A050");
// Adjusting two parameters at once:
vkb.SetParameters("font-name", "Arial", "font-color", "#F0A050");

Valid parameter names are:
o callback - callback function reference. Valid parameter value: reference to any function with 1 or 2 formal parameters
o font-name - name of keyboard font. Valid parameter value: font name or empty string (= default font)
o font-size - font size in pixels. Valid parameter value: font size in pixels
o font-color - font color. Valid parameter value here and further on: string in format of #xxxxxx or #xxx, where x is any valid hexadecimal digit
o dead-color - color of the dead keys
o base-color - color of the keyboard base
o key-color - color of the keys
o selection-color - color of: selected item in the language menu or active modifier key
o border-color - color of the key border
o inactive-border-color - border color of the disabled key
o inactive-key-color - background color of the disabled key
o lang-cell-color - border color of the language menu item
o click-font-color - border color of the language menu item
o click-key-color - border color of the language menu item
o click-border-color - border color of the language menu item
o show-click - show key flash on click. Valid parameter value: true or false
o layout - index (0-based integer) of the layout to switch to. Use it to programmatically switch layouts

Test Suite

The script comes with the following samples:

1.

1-edit-simple.html: The most basic installation of the JavaScript Virtual Keyboard.
2.

2-edit-full.html: Almost the same as above, but the keyb_callback function is not so simple as it was. While simple installation only appends or deletes characters from the end of the text in a TEXTAREA, this sample allows you to edit text in the way you're used to with any other text processor. This sample shows how the JavaScript Virtual Keyboard should normally be used. All other samples are derived from this one.
3.

3-edit-translator.html: This one is useful if you do have the keyboard, but it doesn't have your native layout on its keys. Simply run this installation, switch vkeyboard to your native layout and type with a real keyboard, not a mouse. The script will substitute the typed characters with those chosen in vkeyboard.
4.

4-test-change.html: Sample illustrates the use of the SetParameters API. Watch how vkeyboard changes its color.
5.

5-test-fly.html: Sample shows how to handle multiple INPUT fields with a single vkeyboard.
6.

6-test-fly-anonym.html: Almost the same as above, but INPUT fields are allowed to omit the id property. Can be very useful for introducing vkeyboard into existing sites/pages where page changes are undesirable.
7.

7-test-any-css.html: Sample shows how to set the vkeyboard font size with units other than pixels. A special function, convert_to_px, is used to convert arbitrary unit strings to pixel-based values. The following units are allowed: px, %, em, ex, pt, pc, cm, mm and in. Note that the convert_to_px function is still quite experimental. The main problem is that only the Microsoft Internet Explorer browser provides a way -- via the window.screen.logicalXDPI property -- to retrieve the current screen DPI setting, which is used to convert from absolute to relative length units. With any other browser, only px, %, em and ex can be used safely. A "usual" setting of 96 dots per inch -- quite common for Windows machines -- is used if other length units are specified.
8.

8-test-scale.html: This sample shows how to sidestep the font scaling problem in Mozilla and Microsoft Internet Explorer browsers. The problem with Microsoft Internet Explorer is that it only zooms, i.e. changes font size, on elements that do not have the font-size style set explicitly. The Mozilla browser has a similar problem: it does change the font-size, but does not change the dimensions of the text's container element. In this sample, script tracks the changes to the base font-size and uses the SetParameters API to appropriately scale the vkeyboard layout. This sample should be viewed only in Mozilla (Firefox) and Microsoft Internet Explorer. Opera browser implements a smart zoom -- i.e. it just zooms the entire page -- and thus does not suffer from the problem. You can also overcome this trouble with one of the Microsoft Internet Explorer's wrapper browsers. For example, MyIE2 (Maxthon) also implements a smart zoom. There are also rumors that the Firefox 3 browser will feature an Opera-like page zoom behaviour.

Note that all of the above samples can be found in the vkboard folder of the source archive. Variants other than the full keyboard are not provided with the full test suite, only with a single sample that shows the most simple installation of every variant.
Creating Your Own Language Layout

Two easy steps:

* Append the avail_langs array with a two-member array consisting of the language abbreviation -- usually an ISO 639-1 language code -- and the layout name written in that language, using Unicode hex values where required.
Collapse

avail_langs:
[["Us", "English (US)"],
["Ca", "Canadian"],
["Ru", "Рус" +
"ский"],
["De", "Deutsch"],
["Fr", "Français"],
["Es", "Español"],
["It", "Italiano"],
["Cz", "Česky"],
["El", "Έλλ" +
"ηνας"],
["He", "עברית"]];

* Define "normal" and, optionally, the "Caps Lock"ed, "Shift"ed, "AltGr"ed and "AltGr+Shift"ed arrays. The following rules apply:
o Each array's name must begin with the language abbreviation and the underscore symbol.
o Names of arrays with values representing symbols for a keyboard with "Caps Lock" pressed must end with caps.
Collapse

// Czech layout:
Cz_caps: [";", ... , "-"];

o Names of arrays with values representing symbols for a keyboard with "Shift" pressed must end with shift.
Collapse

Cz_shift: ["º", ... , "_"];

o Names of arrays with values representing symbols for a keyboard with "AltGr" pressed must end with alt_gr.
Collapse

Cz_alt_gr: [..., "!", "/"];

o Names of arrays with values representing symbols for a keyboard with the "AltGr" and "Shift" keys pressed must end with alt_gr_shift.
Collapse

Cz_alt_gr_shift: ["~", ... , "?"];

o Names of arrays with values representing symbols for a keyboard's normal condition -- i.e. with no modifier keys pressed -- must end with normal.
Collapse

Cz_normal: [";", ... , "-"];

o Each array must have exactly 48 entries, each containing either the hexadecimal value of the appropriate symbol or, in the case of a dead key, the array consisting of the hex value for this dead symbol and the name of one of the following predefined arrays:
+ Acute (?)
+ Breve (?)
+ Caron (?)
+ Cedilla (?)
+ Circumflex (^)
+ DialytikaTonos (?, dialytika tonos, combined acute + umlaut)
+ DotAbove (?, dot above)
+ DoubleAcute (?, double acute)
+ Grave (`)
+ Ogonek (?)
+ RingAbove (°, ring above)
+ Tilde (~)
+ Umlaut (?)

Also, the Macron (¯) diacritic array is available via the macron.txt file in the 1-vkboard folder of the archive. It is not included to the main script because no layout I know implements it as a dead key.
Collapse

Cz_alt_gr: [["`", "Grave"], // dead key
"!", ... // simple key

o Arrays are mapped to the layout according to the following illustration, where numbers within cells are the array indices.

array-to-key mapping
o The "normal" array is mandatory; others are optional.

The following layouts are built into the full and slim keyboard variants. The number/index next to the layout name is the layout index in the IBM Globalization database:
o English (US International) - 103P
o Canadian (multilingual standard) - taken from Wikipedia article on keyboard layouts
o German - 129
o French - 189
o Spanish - 173
o Italian - 142
o Russian - 443
o Czech - 243
o Greek - 319
o Hebrew - 212

24 other layouts are available in a separate layout pack that can be found in the 5-layout_pack folder in the archive. It includes:
o European:
+ Bulgarian - 442
+ Czech - alternative variant of Bohemica.com
+ Danish - 159 and 281
+ Dutch - 143
+ Estonian - 454
+ Finnish - 153
+ Hungarian - 208
+ Icelandic - 197
+ Latvian - 455
+ Lithuanian - 456
+ Macedonian - 449
+ Norwegian - 155 and 281N
+ Polish - 214 and 457 (programmer's)
+ Portuguese - 163
+ Romanian - 446
+ Serbian - 450 (cyrillic script)
+ Serbo-Croatian/Slovene - 234 (Latin script)
+ Slovak - 245
+ Swedish - 285
+ Ukrainian - 465
o Asian:
+ Arabic - 470

Please see the readme.txt file in the 5-layout_pack folder in the archive for instructions regarding these layouts.

Creating Your Own Keyboard Layout

You may wish to create a custom key layout. There are two ways to achieve this:

1.

Use atm.js as a template; it is the simplest script of four. See the numpad_atm folder in the archive for more details. In short, the script flow is as follows:
*

Create the outer box:
Collapse

var initX = 0, initY = 0;

...

var kb = document.createElement("DIV");
ct.appendChild(kb);

ct.style.display = "block";
ct.style.position = "absolute";
ct.style.top = initY + "px", ct.style.left = initX +"px";

kb.style.position = "relative";
kb.style.top = "0px", kb.style.left = "0px";
kb.style.border = "1px solid " + bc;

var kb_main = document.createElement("DIV");
kb.appendChild(kb_main);

kb_main.style.position = "relative";
kb_main.style.width = "1px";
kb_main.style.cursor = "default";
kb_main.style.backgroundColor = bkc;

...

*

Create the keys with the _setup_key method:
Collapse

var kb_pad_7 = this._setup_key(kb_main, "1px",
"1px", cp, cp, bc, c, lh, fs);
kb_pad_7.sub.innerHTML = "7";
kb_pad_7.sub.id = container_id + "___pad_7";

*

Route all output to the _generic_callback_proc method. For the VATMpad object, this is done in the _set_key_state method while refreshing the layout:
Collapse

this._setup_event(key_sub, 'mousedown',
this._generic_callback_proc);

*

Invoke the callback function. This happens when the user "presses" a "key":
Collapse

_generic_callback_proc: function(event)
{
...

if(val && vkboard.Callback)
vkboard.Callback(val);
}

Sample layout
2.

Hire me. It is the best way to get a custom modification of a JavaScript Virtual Keyboard, specialized tuning and optimizations, pro support and fast, authoritative answers to JavaScript programming questions.

Call from Beyond

One natural feature that all users expect from a text field is the ability to edit the text at any position within a field. However, it is impossible to do so with a function described earlier, which only appends symbols to or removes from the end of the text. The following script is an attempt to write a compatible callback function to fulfill the described task. It is largely based on the discussion in the thescripts.com forum.
Collapse








...

<-- Don't forget this 'onclick' and 'onkeyup': -->


...



Note that the up and down arrows on a virtual keyboard work only on standards-compliant browsers! Take this into account when creating touch screen applications. You can test the above script by running the 2-edit-full.html file found in the vkboard folder of the attached archive. Basic callback is demonstrated in 1-edit-simple.html.
Tips and Tricks

Script flow is quite straightforward, so I hope it won't be hard to dive into it. Here are a couple of words on a few tricky places.

*

Event set-up. We need to handle both Microsoft Internet Explorer and W3C DOM event models:
Collapse

_setup_event: function(elem, eventType, handler)
{
return (elem.attachEvent ? // Microsoft Internet Explorer way

elem.attachEvent("on" + eventType, handler) :
((elem.addEventListener) ? // W3C way

elem.addEventListener(eventType, handler, false) : null));
}

*

Key container set-up. Each key consists of the "outer" DIV -- where we set the top, left, width and height parameters only -- and the "inner" DIV, which accepts padding, border color and other parameters. We use such a complex construction to circumvent the box model problem of modern browsers. Note that there is the JavaScript solution. If you wish to avoid the box model problem using CSS, you may wish to see the article by Trenton Moss (see item #6).
Collapse

_setup_key: function(parent, id, top, left, width, height,
text_align, line_height, font_size,
font_weight, padding_left, padding_right)
{
var exists = document.getElementById(id);

// Outer DIV:

var key =
exists ? exists.parentNode : document.createElement("DIV");
this._setup_style(key,
!exists, top, left, width, height, "absolute");

// Inner DIV:

var key_sub = exists ? exists : document.createElement("DIV");
key.appendChild(key_sub); parent.appendChild(key);

this._setup_style(key_sub,
!exists, "", "", "", line_height, "relative",
text_align, line_height, font_size, font_weight,
padding_left, padding_right);
key_sub.id = id;

return key_sub;
}

*

Disabling content selection. This one is very important due to the very high typing speed that some people can achieve and, as a result, the inevitable vkeyboard content selection. It can be used instead of the UNSELECTABLE (Microsoft Internet Explorer) and -moz-user-select (Gecko-based browsers) properties.
Collapse

this._setup_event(kb_main, "selectstart",
function(event)
{
return false;
});
this._setup_event(kb_main, "mousedown",
function(event)
{
if(event.preventDefault)
event.preventDefault();
return false;
});

Followers