Skip to main content
Email Password   helpLost your password?
Virtual keyboard bound to a TEXTAREA field

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:

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:

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

    <HTML>
        <HEAD>
    
           <SCRIPT type="text/javascript" src="vkboard.js"></SCRIPT>
    
           ...
    
        </HEAD>
     ...

    Note that for each type of installation, two script files are available:

  4. Define a callback function:

    <HTML>
        <HEAD>
    
            <SCRIPT type="text/javascript" src="vkboard.js"></SCRIPT>
    
            <SCRIPT>
    
            // Minimal callback function:
            function keyb_callback(char)
            {
                // Let's bind vkeyboard to the <TEXTAREA>
    
                // with id="textfield":
                var text =
                    document.getElementById("textfield"), val = text.value;
    
                switch(ch)
                {
                    case "BackSpace":
                    var min=(val.charCodeAt(val.length - 1) == 10) ? 2 : 1;
                    text.value = val.substr(0, val.length - min);
                    break;
    
                   case "Enter":
                       text.value += "\n";
                       break;
    
                   default:
                       text.value += ch;
                }
            }
    
            </SCRIPT>
    
        </HEAD>
        ...

    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.

     <HTML>
    
        ...
    
        <BODY>
    
            ...
    
            <TEXTAREA id="textfield" rows="10" cols="50"></TEXTAREA>
    
            <DIV id="keyboard"></DIV>
    
        </BODY>
    
    </HTML>
  6. Finally, show the keyboard.

     <BODY onload="new VKeyboard("keyboard", keyb_callback);">
    
     <!-- VKeyboard is shown on load, bound to container with
          id="keyboard" and with callback function "keyb_callback" -->

    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.

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:

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:

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:

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:

  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.

<HEAD>
    <SCRIPT type="text/javascript"><!--

    var opened = false,
        insertionS = -1,  // selection start

        insertionE =  0;  // selection end


    var userstr = navigator.userAgent.toLowerCase();
    var safari = (userstr.indexOf('applewebkit') != -1);
    var gecko  = (userstr.indexOf('gecko') != -1) && !safari;
    var standr = gecko || window.opera || safari;

    ...

    // Advanced callback function:
    //

    function keyb_callback(ch)
    {
        var val = text.value;

        switch(ch)
        {
            case "BackSpace":
            if(val.length)
            {
                var span = null;

                if(document.selection)
                span = document.selection.createRange().duplicate();

                if(span && span.text.length > 0)
                {
                    span.text = "";
                    getCaretPositions(text);
                }
                else
                deleteAtCaret(text);
            }
            break;

            case "<":
                if(insertionS > 0)
                setRange(text, insertionS - 1, insertionE - 1);
            break;

            case ">":
                if(insertionE < val.length)
                setRange(text, insertionS + 1, insertionE + 1);
            break;

            case "/\\":
                if(!standr) break;
                var prev  = val.lastIndexOf("\n", insertionS) + 1;
                var pprev = val.lastIndexOf("\n", prev - 2);
                var next  = val.indexOf("\n", insertionS);

                if(next == -1) next = val.length;
                var nnext = next - insertionS;

                if(prev > next)
                {
                    prev  = val.lastIndexOf("\n", insertionS - 1) + 1;
                    pprev = val.lastIndexOf("\n", prev - 2);
                }

                // number of chars in current line to the left of the caret:
                var left = insertionS - prev;

                // length of the prev. line:
                var plen = prev - pprev - 1;

                // number of chars in the prev. line to
                // the right of the caret:
                var right = (plen <= left) ? 1 : (plen - left);

                var change = left + right;
                setRange(text, insertionS - change, insertionE - change);

            break;

            case "\\/":
                if(!standr) break;

                var prev  = val.lastIndexOf("\n", insertionS) + 1;
                var next  = val.indexOf("\n", insertionS);
                var pnext = val.indexOf("\n", next + 1);

                if( next == -1)  next = val.length;
                if(pnext == -1) pnext = val.length;

                if(pnext < next) pnext = next;

                if(prev > next)
                prev  = val.lastIndexOf("\n", insertionS - 1) + 1;

                // number of chars in current line to the left of the caret:
                var left = insertionS - prev;

                // length of the next line:
                var nlen = pnext - next;

                // number of chars in the next line to the left of the caret:
                var right = (nlen <= left) ? 0 : (nlen - left - 1);

                var change = (next - insertionS) + nlen - right;
                setRange(text, insertionS + change, insertionE + change);

            break;

            default:
                insertAtCaret(text,
                   (ch == "Enter" ? (window.opera ? '\r\n' : '\n') : ch));
            }
        }
    }

    // This function retrieves the position (in chars, relative to
    // the start of the text) of the edit cursor (caret), or, if
    // text is selected in the TEXTAREA, the start and end positions
    // of the selection.
    //
    function getCaretPositions(ctrl)
    {
        var CaretPosS = -1, CaretPosE = 0;

        // Mozilla way:
        if(ctrl.selectionStart || (ctrl.selectionStart == '0'))
        {
            CaretPosS = ctrl.selectionStart;
            CaretPosE = ctrl.selectionEnd;

            insertionS = CaretPosS == -1 ? CaretPosE : CaretPosS;
            insertionE = CaretPosE;
        }
        // IE way:
        else if(document.selection && ctrl.createTextRange)
        {
            var start = end = 0;
            try
            {
                start =
                    Math.abs(
                    document.selection.createRange().moveStart("character",
                    -10000000)); // start

                if (start > 0)
                {
                    try
                    {
                        var endReal =
                            Math.abs(
                            ctrl.createTextRange().moveEnd("character",
                            -10000000));

                        var r = document.body.createTextRange();
                        r.moveToElementText(ctrl);
                        var sTest =
                            Math.abs(r.moveStart("character", -10000000));
                        var eTest =
                            Math.abs(r.moveEnd("character", -10000000));

                        if((ctrl.tagName.toLowerCase() != 'input') &&
                            (eTest - endReal == sTest))
                            start -= sTest;
                    }
                    catch(err) {}
                }
            }
            catch (e) {}

            try
            {
                end =
                    Math.abs(
                    document.selection.createRange().moveEnd("character",
                    -10000000)); // end
                if(end > 0)
                {
                    try
                    {
                        var endReal =
                            Math.abs(
                            ctrl.createTextRange().moveEnd("character",
                            -10000000));

                        var r = document.body.createTextRange();
                        r.moveToElementText(ctrl);
                        var sTest =
                            Math.abs(r.moveStart("character", -10000000));
                        var eTest =
                            Math.abs(r.moveEnd("character", -10000000));

                        if ((ctrl.tagName.toLowerCase() != 'input') &&
                            (eTest - endReal == sTest))
                             end -= sTest;
                    }
                    catch(err) {}
                }
            }
            catch (e) {}

            insertionS = start;
            insertionE = end
        }
    }

    function setRange(ctrl, start, end)
    {
        if(ctrl.setSelectionRange) // Standard way (Mozilla, Opera, ...)
        {
            ctrl.setSelectionRange(start, end);
        }
        else // Microsoft Internet Explorer
        {
            var range;

            try
            {
                range = ctrl.createTextRange();
            }
            catch(e)
            {
                try
                {
                    range = document.body.createTextRange();
                    range.moveToElementText(ctrl);
                }
                catch(e)
                {
                    range = null;
                }
            }

            if(!range) return;

            range.collapse(true);
            range.moveStart("character", start);
            range.moveEnd("character", end - start);
            range.select();
        }

        insertionS = start;
        insertionE = end;
    }

    function deleteSelection(ctrl)
    {
        if(insertionS == insertionE) return;

        var tmp =
            (document.selection &&
            !window.opera) ? ctrl.value.replace(/\r/g,"") : ctrl.value;
        ctrl.value =
            tmp.substring(0, insertionS) + tmp.substring(insertionE,
            tmp.length);

        setRange(ctrl, insertionS, insertionS);
    }

    function deleteAtCaret(ctrl)
    {
        if(insertionS != insertionE)
        {
            deleteSelection(ctrl);
            return;
        }

        if(insertionS == insertionE)
        insertionS = insertionS - 1;

        var tmp =
            (document.selection &&
            !window.opera) ? ctrl.value.replace(/\r/g,"") : ctrl.value;
        ctrl.value =
            tmp.substring(0,
            insertionS) + tmp.substring(insertionE, tmp.length);

        setRange(ctrl, insertionS, insertionS);
    }

    // This function inserts text at the caret position:
    //
    function insertAtCaret(ctrl, val)
    {
        if(insertionS != insertionE) deleteSelection(ctrl);

        if(isgecko && document.createEvent && !window.opera)
        {
            var e = document.createEvent("KeyboardEvent");

            if(e.initKeyEvent && ctrl.dispatchEvent)
            {
                e.initKeyEvent("keypress", // in DOMString typeArg,
                    false,                 // in boolean canBubbleArg,
                    true,                  // in boolean cancelableArg,
                    null,                  // in nsIDOMAbstractView viewArg,
                    false,                 // in boolean ctrlKeyArg,
                    false,                 // in boolean altKeyArg,
                    false,                 // in boolean shiftKeyArg,
                    false,                 // in boolean metaKeyArg,
                    null,                  // key code;
                    val.charCodeAt(0));    // char code.

                ctrl.dispatchEvent(e);
            }
        }
        else
        {
            var tmp =
                (document.selection &&
                !window.opera) ? ctrl.value.replace(/\r/g,"") : ctrl.value;
            ctrl.value =
                tmp.substring(0,
                insertionS) + val + tmp.substring(insertionS,
                tmp.length);
        }

        setRange(ctrl, insertionS + val.length, insertionS + val.length);
    }
    //--></SCRIPT>

</HEAD>

<BODY>

    ...

    <-- Don't forget this 'onclick' and 'onkeyup': -->
    <TEXTAREA onkeyup="getCaretPositions(this);"
        onclick="getCaretPositions(this);"

        id="textfield" rows="12" cols="50">
    </TEXTAREA>

    ...

</BODY>

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.

Code

Virtual Keyboard Features at a Glance

Script Requirements

Any browser that is aware of:

Links

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralHide the language Pin
Damal
15:41 16 Nov '09  
Generalhow to set default language as "Us' disable language selection button Pin
Parvai
20:11 13 Oct '09  
GeneralVKeyboard disapears in CHROME browser. Pin
n3v3rl0v3
1:02 27 Sep '09  
GeneralBug in fly-anonym Pin
radmor11
3:57 20 Aug '09  
GeneralHow to show up the keyboard at once (without clicking on Show keyboard)? Pin
soezkan
5:12 14 Aug '09  
GeneralTyping keyboard keys to produce selected language Pin
serouj
22:51 10 Aug '09  
GeneralRe: Typing keyboard keys to produce selected language Pin
Dmitry Khudorozhkov
1:12 11 Aug '09  
GeneralGreat Work! Pin
.Shoaib
1:09 17 Apr '09  
GeneralEliminating the "international" keys Pin
Jelks
3:57 27 Mar '09  
GeneralRe: Eliminating the "international" keys Pin
Dmitry Khudorozhkov
4:09 27 Mar '09  
GeneralRe: Eliminating the "international" keys Pin
Jelks
10:01 27 Mar '09  
QuestionDelete key? Pin
Jelks
17:42 11 Mar '09  
AnswerRe: Delete key? Pin
Jelks
12:13 26 Mar '09  
GeneralCustomizing the keyboard's layout Pin
Jelks
3:15 11 Mar '09  
GeneralRe: Customizing the keyboard's layout Pin
Dmitry Khudorozhkov
7:22 11 Mar '09  
GeneralRe: Customizing the keyboard's layout Pin
Jelks
12:16 11 Mar '09  
GeneralRe: Customizing the keyboard's layout Pin
lordkaya
0:17 7 Jun '09  
Answerjvk_src.zip file Pin
w_larcipretti
1:52 6 Jan '09  
Generaljvk_src.zip file Pin
w_larcipretti
6:42 5 Jan '09  
GeneralRe: jvk_src.zip file Pin
Dmitry Khudorozhkov
6:58 5 Jan '09  
GeneralIncreasing security Pin
azizhocaoglu_70
16:53 10 Dec '08  
GeneralRe: Increasing security Pin
Dmitry Khudorozhkov
17:02 10 Dec '08  
GeneralRe: Increasing security Pin
azizhocaoglu_70
9:15 11 Dec '08  
Generalvirtual keyboard in popup control ? Pin
hungud
0:45 1 Dec '08  
GeneralHow can I Handle Keyboard on IFRAME Pin
Dabara
0:09 22 Nov '08  


Last Updated 30 Mar 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009