Statistics
| Branch: | Revision:

blinker / firefox.plugin / data / scripts / text / keyboard.js @ master

History | View | Annotate | Download (124.321 KB)

1 76dd22bd KevinTaron
/*! jQuery UI Virtual Keyboard v1.26.4 *//*
2
Author: Jeremy Satterfield
3
Maintained: Rob Garrison (Mottie on github)
4
Licensed under the MIT License
5

6
An on-screen virtual keyboard embedded within the browser window which
7
will popup when a specified entry field is focused. The user can then
8
type and preview their input before Accepting or Canceling.
9

10
This plugin adds default class names to match jQuery UI theme styling.
11
Bootstrap & custom themes may also be applied - See
12
https://github.com/Mottie/Keyboard#themes
13

14
Requires:
15
    jQuery v1.4.3+
16
    Caret plugin (included)
17
Optional:
18
    jQuery UI (position utility only) & CSS theme
19
    jQuery mousewheel
20

21
Setup/Usage:
22
    Please refer to https://github.com/Mottie/Keyboard/wiki
23

24
-----------------------------------------
25
Caret code modified from jquery.caret.1.02.js
26
Licensed under the MIT License:
27
http://www.opensource.org/licenses/mit-license.php
28
-----------------------------------------
29
*/
30
/*jshint browser:true, jquery:true, unused:false */
31
/*global require:false, define:false, module:false */
32
;(function (factory) {
33
    if (typeof define === 'function' && define.amd) {
34
        define(['jquery'], factory);
35
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
36
        module.exports = factory(require('jquery'));
37
    } else {
38
        factory(jQuery);
39
    }
40
}(function ($) {
41
    'use strict';
42
    var $keyboard = $.keyboard = function (el, options) {
43
    var o, base = this;
44
45
    base.version = '1.26.4';
46
47
    // Access to jQuery and DOM versions of element
48
    base.$el = $(el);
49
    base.el = el;
50
51
    // Add a reverse reference to the DOM object
52
    base.$el.data('keyboard', base);
53
54
    base.init = function () {
55
        var k, position, tmp,
56
            kbcss = $keyboard.css,
57
            kbevents = $keyboard.events;
58
        base.settings = options || {};
59
        // shallow copy position to prevent performance issues; see #357
60
        if (options && options.position) {
61
            position = $.extend({}, options.position);
62
            options.position = null;
63
        }
64
        base.options = o = $.extend(true, {}, $keyboard.defaultOptions, options);
65
        if (position) {
66
            o.position = position;
67
            options.position = position;
68
        }
69
70
        // keyboard is active (not destroyed);
71
        base.el.active = true;
72
        // unique keyboard namespace
73
        base.namespace = '.keyboard' + Math.random().toString(16).slice(2);
74
        // extension namespaces added here (to unbind listeners on base.$el upon destroy)
75
        base.extensionNamespace = [];
76
        // Shift and Alt key toggles, sets is true if a layout has more than one keyset
77
        // used for mousewheel message
78
        base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
79
        // Class names of the basic key set - meta keysets are handled by the keyname
80
        base.rows = ['', '-shift', '-alt', '-alt-shift'];
81
82
        base.inPlaceholder = base.$el.attr('placeholder') || '';
83
        // html 5 placeholder/watermark
84
        base.watermark = $keyboard.watermark && base.inPlaceholder !== '';
85
        // convert mouse repeater rate (characters per second) into a time in milliseconds.
86
        base.repeatTime = 1000 / (o.repeatRate || 20);
87
        // delay in ms to prevent mousedown & touchstart from both firing events at the same time
88
        o.preventDoubleEventTime = o.preventDoubleEventTime || 100;
89
        // flag indication that a keyboard is open
90
        base.isOpen = false;
91
        // is mousewheel plugin loaded?
92
        base.wheel = $.isFunction($.fn.mousewheel);
93
        // special character in regex that need to be escaped
94
        base.escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
95
96
        // keyCode of keys always allowed to be typed
97
        k = $keyboard.keyCodes;
98
        // base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
99
        base.alwaysAllowed = [
100
            k.capsLock,
101
            k.pageUp,
102
            k.pageDown,
103
            k.end,
104
            k.home,
105
            k.left,
106
            k.up,
107
            k.right,
108
            k.down,
109
            k.insert,
110
            k.delete
111
        ];
112
        base.$keyboard = [];
113
        // keyboard enabled; set to false on destroy
114
        base.enabled = true;
115
        // make a copy of the original keyboard position
116
        if (!$.isEmptyObject(o.position)) {
117
            o.position.orig_at = o.position.at;
118
        }
119
120
        base.checkCaret = (o.lockInput || $keyboard.checkCaretSupport());
121
122
        base.last = {
123
            start: 0,
124
            end: 0,
125
            key: '',
126
            val: '',
127
            preVal: '',
128
            layout: '',
129
            virtual: true,
130
            keyset: [false, false, false], // [shift, alt, meta]
131
            wheel_$Keys: null,
132
            wheelIndex: 0,
133
            wheelLayers: []
134
        };
135
        // used when building the keyboard - [keyset element, row, index]
136
        base.temp = ['', 0, 0];
137
138
        // Callbacks
139
        $.each([
140
            kbevents.kbInit,
141
            kbevents.kbBeforeVisible,
142
            kbevents.kbVisible,
143
            kbevents.kbHidden,
144
            kbevents.inputCanceled,
145
            kbevents.inputAccepted,
146
            kbevents.kbBeforeClose,
147
            kbevents.inputRestricted
148
        ], function (i, callback) {
149
            if ($.isFunction(o[callback])) {
150
                // bind callback functions within options to triggered events
151
                base.$el.bind(callback + base.namespace + 'callbacks', o[callback]);
152
            }
153
        });
154
155
        // Close with esc key & clicking outside
156
        if (o.alwaysOpen) {
157
            o.stayOpen = true;
158
        }
159
160
        tmp = $(document);
161
        if (base.el.ownerDocument !== document) {
162
            tmp = tmp.add(base.el.ownerDocument);
163
        }
164
165
        var bindings = 'keyup checkkeyboard mousedown touchstart ';
166
        if (o.closeByClickEvent) {
167
            bindings += 'click ';
168
        }
169
        tmp.bind(bindings.split(' ').join(base.namespace + ' '), base.checkClose);
170
171
        // Display keyboard on focus
172
        base.$el
173
            .addClass(kbcss.input + ' ' + o.css.input)
174
            .attr({
175
                'aria-haspopup': 'true',
176
                'role': 'textbox'
177
            });
178
179
        // set lockInput if the element is readonly; or make the element readonly if lockInput is set
180
        if (o.lockInput || base.el.readOnly) {
181
            o.lockInput = true;
182
            base.$el
183
                .addClass(kbcss.locked)
184
                .attr({
185
                    'readonly': 'readonly'
186
                });
187
        }
188
        // add disabled/readonly class - dynamically updated on reveal
189
        if (base.$el.is(':disabled') || (base.$el.attr('readonly') &&
190
                !base.$el.hasClass(kbcss.locked))) {
191
            base.$el.addClass(kbcss.noKeyboard);
192
        }
193
194
        if (o.openOn) {
195
            base.bindFocus();
196
        }
197
198
        // Add placeholder if not supported by the browser
199
        if (!base.watermark && base.$el.val() === '' && base.inPlaceholder !== '' &&
200
            base.$el.attr('placeholder') !== '') {
201
            base.$el
202
                .addClass(kbcss.placeholder) // css watermark style (darker text)
203
                .val(base.inPlaceholder);
204
        }
205
206
        base.$el.trigger(kbevents.kbInit, [base, base.el]);
207
208
        // initialized with keyboard open
209
        if (o.alwaysOpen) {
210
            base.reveal();
211
        }
212
213
    };
214
215
    base.toggle = function () {
216
        var $toggle = base.$keyboard.find('.' + $keyboard.css.keyToggle),
217
            locked = !base.enabled;
218
        // prevent physical keyboard from working
219
        base.$preview.prop('readonly', locked || base.options.lockInput);
220
        // disable all buttons
221
        base.$keyboard
222
            .toggleClass($keyboard.css.keyDisabled, locked)
223
            .find('.' + $keyboard.css.keyButton)
224
            .not($toggle)
225
            .prop('disabled', locked)
226
            .attr('aria-disabled', locked);
227
        $toggle.toggleClass($keyboard.css.keyDisabled, locked);
228
        // stop auto typing
229
        if (locked && base.typing_options) {
230
            base.typing_options.text = '';
231
        }
232
    };
233
234
    base.setCurrent = function () {
235
        var kbcss = $keyboard.css,
236
            // close any "isCurrent" keyboard (just in case they are always open)
237
            $current = $('.' + kbcss.isCurrent),
238
            kb = $current.data('keyboard');
239
        // close keyboard, if not self
240
        if (!$.isEmptyObject(kb) && kb.el !== base.el) {
241
            kb.close(kb.options.autoAccept ? 'true' : false);
242
        }
243
        $current.removeClass(kbcss.isCurrent);
244
        // ui-keyboard-has-focus is applied in case multiple keyboards have
245
        // alwaysOpen = true and are stacked
246
        $('.' + kbcss.hasFocus).removeClass(kbcss.hasFocus);
247
248
        base.$el.addClass(kbcss.isCurrent);
249
        base.$keyboard.addClass(kbcss.hasFocus);
250
        base.isCurrent(true);
251
        base.isOpen = true;
252
    };
253
254
    base.isCurrent = function (set) {
255
        var cur = $keyboard.currentKeyboard || false;
256
        if (set) {
257
            cur = $keyboard.currentKeyboard = base.el;
258
        } else if (set === false && cur === base.el) {
259
            cur = $keyboard.currentKeyboard = '';
260
        }
261
        return cur === base.el;
262
    };
263
264
    base.isVisible = function () {
265
        return base.$keyboard && base.$keyboard.length ? base.$keyboard.is(':visible') : false;
266
    };
267
268
    base.focusOn = function () {
269
        if (!base && base.el.active) {
270
            // keyboard was destroyed
271
            return;
272
        }
273
        if (!base.isVisible()) {
274
            clearTimeout(base.timer);
275
            base.reveal();
276
        }
277
    };
278
279
    // add redraw method to make API more clear
280
    base.redraw = function () {
281
        // update keyboard after a layout change
282
        if (base.$keyboard.length) {
283
284
            base.last.preVal = '' + base.last.val;
285
            base.last.val = base.$preview && base.$preview.val() || base.$el.val();
286
            base.$el.val( base.last.val );
287
288
            base.removeKeyboard();
289
            base.shiftActive = base.altActive = base.metaActive = false;
290
        }
291
        base.isOpen = o.alwaysOpen;
292
        base.reveal(true);
293
    };
294
295
    base.reveal = function (redraw) {
296
        var alreadyOpen = base.isOpen,
297
            kbcss = $keyboard.css;
298
        base.opening = !alreadyOpen;
299
        // remove all 'extra' keyboards by calling close function
300
        $('.' + kbcss.keyboard).not('.' + kbcss.alwaysOpen).each(function(){
301
            var kb = $(this).data('keyboard');
302
            if (!$.isEmptyObject(kb)) {
303
                kb.close(kb.options.autoAccept && kb.options.autoAcceptOnEsc ? 'true' : false);
304
            }
305
        });
306
307
        // Don't open if disabled
308
        if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass(kbcss.locked))) {
309
            base.$el.addClass(kbcss.noKeyboard);
310
            return;
311
        } else {
312
            base.$el.removeClass(kbcss.noKeyboard);
313
        }
314
315
        // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
316
        if (o.openOn) {
317
            base.$el.unbind($.trim((o.openOn + ' ').split(/\s+/).join(base.namespace + ' ')));
318
        }
319
320
        // build keyboard if it doesn't exist; or attach keyboard if it was removed, but not cleared
321
        if (!base.$keyboard || base.$keyboard &&
322
            (!base.$keyboard.length || $.contains(document.body, base.$keyboard[0]))) {
323
            base.startup();
324
        }
325
326
        // clear watermark
327
        if (!base.watermark && base.el.value === base.inPlaceholder) {
328
            base.$el
329
                .removeClass(kbcss.placeholder)
330
                .val('');
331
        }
332
        // save starting content, in case we cancel
333
        base.originalContent = base.$el.val();
334
        base.$preview.val(base.originalContent);
335
336
        // disable/enable accept button
337
        if (o.acceptValid) {
338
            base.checkValid();
339
        }
340
341
        if (o.resetDefault) {
342
            base.shiftActive = base.altActive = base.metaActive = false;
343
        }
344
        base.showSet();
345
346
        // beforeVisible event
347
        if (!base.isVisible()) {
348
            base.$el.trigger($keyboard.events.kbBeforeVisible, [base, base.el]);
349
        }
350
        base.setCurrent();
351
        // update keyboard - enabled or disabled?
352
        base.toggle();
353
354
        // show keyboard
355
        base.$keyboard.show();
356
357
        // adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
358
        if (o.usePreview && $keyboard.msie) {
359
            if (typeof base.width === 'undefined') {
360
                base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing
361
                base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row
362
                base.$preview.show();
363
            }
364
            base.$preview.width(base.width);
365
        }
366
367
        base.position = $.isEmptyObject(o.position) ? false : o.position;
368
369
        // position after keyboard is visible (required for UI position utility) and appropriately sized
370
        if ($.ui && $.ui.position && base.position) {
371
            // get single target position || target stored in element data (multiple targets) || default @ element
372
            base.position.of = base.position.of || base.$el.data('keyboardPosition') || base.$el;
373
            base.position.collision = base.position.collision || 'flipfit flipfit';
374
            o.position.at = o.usePreview ? o.position.orig_at : o.position.at2;
375
            base.$keyboard.position(base.position);
376
        }
377
378
        base.checkDecimal();
379
380
        // get preview area line height
381
        // add roughly 4px to get line height from font height, works well for font-sizes from 14-36px
382
        // needed for textareas
383
        base.lineHeight = parseInt(base.$preview.css('lineHeight'), 10) ||
384
            parseInt(base.$preview.css('font-size'), 10) + 4;
385
386
        if (o.caretToEnd) {
387
            base.saveCaret(base.originalContent.length, base.originalContent.length);
388
        }
389
390
        // IE caret haxx0rs
391
        if ($keyboard.allie) {
392
            // sometimes end = 0 while start is > 0
393
            if (base.last.end === 0 && base.last.start > 0) {
394
                base.last.end = base.last.start;
395
            }
396
            // IE will have start -1, end of 0 when not focused (see demo: https://jsfiddle.net/Mottie/fgryQ/3/)
397
            if (base.last.start < 0) {
398
                // ensure caret is at the end of the text (needed for IE)
399
                base.last.start = base.last.end = base.originalContent.length;
400
            }
401
        }
402
403
        if (alreadyOpen || redraw) {
404
            // restore caret position (userClosed)
405
            $keyboard.caret(base.$preview, base.last);
406
            return base;
407
        }
408
409
        // opening keyboard flag; delay allows switching between keyboards without immediately closing
410
        // the keyboard
411
        base.timer2 = setTimeout(function () {
412
            var undef;
413
            base.opening = false;
414
            // Number inputs don't support selectionStart and selectionEnd
415
            // Number/email inputs don't support selectionStart and selectionEnd
416
            if (!/(number|email)/i.test(base.el.type) && !o.caretToEnd) {
417
                // caret position is always 0,0 in webkit; and nothing is focused at this point... odd
418
                // save caret position in the input to transfer it to the preview
419
                // inside delay to get correct caret position
420
                base.saveCaret(undef, undef, base.$el);
421
            }
422
            if (o.initialFocus) {
423
                $keyboard.caret(base.$preview, base.last);
424
            }
425
            // save event time for keyboards with stayOpen: true
426
            base.last.eventTime = new Date().getTime();
427
            base.$el.trigger($keyboard.events.kbVisible, [base, base.el]);
428
            base.timer = setTimeout(function () {
429
                // get updated caret information after visible event - fixes #331
430
                if (base) { // Check if base exists, this is a case when destroy is called, before timers fire
431
                    base.saveCaret();
432
                }
433
            }, 200);
434
        }, 10);
435
        // return base to allow chaining in typing extension
436
        return base;
437
    };
438
439
    base.updateLanguage = function () {
440
        // change language if layout is named something like 'french-azerty-1'
441
        var layouts = $keyboard.layouts,
442
            lang = o.language || layouts[o.layout] && layouts[o.layout].lang &&
443
                layouts[o.layout].lang || [o.language || 'en'],
444
            kblang = $keyboard.language;
445
446
        // some languages include a dash, e.g. 'en-gb' or 'fr-ca'
447
        // allow o.language to be a string or array...
448
        // array is for future expansion where a layout can be set for multiple languages
449
        lang = ($.isArray(lang) ? lang[0] : lang).split('-')[0];
450
451
        // set keyboard language
452
        o.display = $.extend(true, {},
453
            kblang.en.display,
454
            kblang[lang] && kblang[lang].display || {},
455
            base.settings.display
456
        );
457
        o.combos = $.extend(true, {},
458
            kblang.en.combos,
459
            kblang[lang] && kblang[lang].combos || {},
460
            base.settings.combos
461
        );
462
        o.wheelMessage = kblang[lang] && kblang[lang].wheelMessage || kblang.en.wheelMessage;
463
        // rtl can be in the layout or in the language definition; defaults to false
464
        o.rtl = layouts[o.layout] && layouts[o.layout].rtl || kblang[lang] && kblang[lang].rtl || false;
465
466
        // save default regex (in case loading another layout changes it)
467
        base.regex = kblang[lang] && kblang[lang].comboRegex || $keyboard.comboRegex;
468
        // determine if US '.' or European ',' system being used
469
        base.decimal = /^\./.test(o.display.dec);
470
        base.$el
471
            .toggleClass('rtl', o.rtl)
472
            .css('direction', o.rtl ? 'rtl' : '');
473
    };
474
475
    base.startup = function () {
476
        var kbcss = $keyboard.css;
477
        // ensure base.$preview is defined; but don't overwrite it if keyboard is always visible
478
        if (!((o.alwaysOpen || o.userClosed) && base.$preview)) {
479
            base.makePreview();
480
        }
481
        if (!(base.$keyboard && base.$keyboard.length)) {
482
            // custom layout - create a unique layout name based on the hash
483
            if (o.layout === 'custom') {
484
                o.layoutHash = 'custom' + base.customHash();
485
            }
486
            base.layout = o.layout === 'custom' ? o.layoutHash : o.layout;
487
            base.last.layout = base.layout;
488
489
            base.updateLanguage();
490
            if (typeof $keyboard.builtLayouts[base.layout] === 'undefined') {
491
                if ($.isFunction(o.create)) {
492
                    // create must call buildKeyboard() function; or create it's own keyboard
493
                    base.$keyboard = o.create(base);
494
                } else if (!base.$keyboard.length) {
495
                    base.buildKeyboard(base.layout, true);
496
                }
497
            }
498
            base.$keyboard = $keyboard.builtLayouts[base.layout].$keyboard.clone();
499
            base.$keyboard.data('keyboard', base);
500
            if ((base.el.id || '') !== '') {
501
                // add ID to keyboard for styling purposes
502
                base.$keyboard.attr('id', base.el.id + $keyboard.css.idSuffix);
503
            }
504
505
            base.makePreview();
506
            // build preview display
507
            if (o.usePreview) {
508
                // restore original positioning (in case usePreview option is altered)
509
                if (!$.isEmptyObject(o.position)) {
510
                    o.position.at = o.position.orig_at;
511
                }
512
            } else {
513
                // No preview display, use element and reposition the keyboard under it.
514
                if (!$.isEmptyObject(o.position)) {
515
                    o.position.at = o.position.at2;
516
                }
517
            }
518
519
        }
520
521
        base.$decBtn = base.$keyboard.find('.' + kbcss.keyPrefix + 'dec');
522
        // add enter to allowed keys; fixes #190
523
        if (o.enterNavigation || base.el.nodeName === 'TEXTAREA') {
524
            base.alwaysAllowed.push(13);
525
        }
526
527
        base.bindKeyboard();
528
529
        base.$keyboard.appendTo(o.appendLocally ? base.$el.parent() : o.appendTo || 'body');
530
531
        base.bindKeys();
532
533
        // adjust with window resize; don't check base.position
534
        // here in case it is changed dynamically
535
        if (o.reposition && $.ui && $.ui.position && o.appendTo == 'body') {
536
            $(window).bind('resize' + base.namespace, function () {
537
                if (base.position && base.isVisible()) {
538
                    base.$keyboard.position(base.position);
539
                }
540
            });
541
        }
542
543
    };
544
545
    base.makePreview = function () {
546
        if (o.usePreview) {
547
            var indx, attrs, attr, removedAttr,
548
                kbcss = $keyboard.css;
549
            base.$preview = base.$el.clone(false)
550
                .data('keyboard', base)
551
                .removeClass(kbcss.placeholder + ' ' + kbcss.input)
552
                .addClass(kbcss.preview + ' ' + o.css.input)
553
                .attr('tabindex', '-1')
554
                .show(); // for hidden inputs
555
            base.preview = base.$preview[0];
556
557
            // Switch the number input field to text so the caret positioning will work again
558
            if (base.preview.type === 'number') {
559
                base.preview.type = 'text';
560
            }
561
562
            // remove extraneous attributes.
563
            removedAttr = /^(data-|id|aria-haspopup)/i;
564
            attrs = base.$preview.get(0).attributes;
565
            for (indx = attrs.length - 1; indx >= 0; indx--) {
566
                attr = attrs[indx] && attrs[indx].name;
567
                if (removedAttr.test(attr)) {
568
                    // remove data-attributes - see #351
569
                    base.preview.removeAttribute(attr);
570
                }
571
            }
572
            // build preview container and append preview display
573
            $('<div />')
574
                .addClass(kbcss.wrapper)
575
                .append(base.$preview)
576
                .prependTo(base.$keyboard);
577
        } else {
578
            base.$preview = base.$el;
579
            base.preview = base.el;
580
        }
581
    };
582
583
    base.saveCaret = function (start, end, $el) {
584
        var p = $keyboard.caret($el || base.$preview, start, end);
585
        base.last.start = typeof start === 'undefined' ? p.start : start;
586
        base.last.end = typeof end === 'undefined' ? p.end : end;
587
    };
588
589
    base.setScroll = function () {
590
        // Set scroll so caret & current text is in view
591
        // needed for virtual keyboard typing, NOT manual typing - fixes #23
592
        if (base.last.virtual) {
593
594
            var scrollWidth, clientWidth, adjustment, direction,
595
                isTextarea = base.preview.nodeName === 'TEXTAREA',
596
                value = base.last.val.substring(0, Math.max(base.last.start, base.last.end));
597
598
            if (!base.$previewCopy) {
599
                // clone preview
600
                base.$previewCopy = base.$preview.clone()
601
                    .removeAttr('id') // fixes #334
602
                    .css({
603
                        position: 'absolute',
604
                        left: 0,
605
                        zIndex: -10,
606
                        visibility: 'hidden'
607
                    })
608
                    .addClass($keyboard.css.inputClone);
609
                if (!isTextarea) {
610
                    // make input zero-width because we need an accurate scrollWidth
611
                    base.$previewCopy.css({
612
                        'white-space': 'pre',
613
                        'width': 0
614
                    });
615
                }
616
                if (o.usePreview) {
617
                    // add clone inside of preview wrapper
618
                    base.$preview.after(base.$previewCopy);
619
                } else {
620
                    // just slap that thing in there somewhere
621
                    base.$keyboard.prepend(base.$previewCopy);
622
                }
623
            }
624
625
            if (isTextarea) {
626
                // need the textarea scrollHeight, so set the clone textarea height to be the line height
627
                base.$previewCopy
628
                    .height(base.lineHeight)
629
                    .val(value);
630
                // set scrollTop for Textarea
631
                base.preview.scrollTop = base.lineHeight *
632
                    (Math.floor(base.$previewCopy[0].scrollHeight / base.lineHeight) - 1);
633
            } else {
634
                // add non-breaking spaces
635
                base.$previewCopy.val(value.replace(/\s/g, '\xa0'));
636
637
                // if scrollAdjustment option is set to "c" or "center" then center the caret
638
                adjustment = /c/i.test(o.scrollAdjustment) ? base.preview.clientWidth / 2 : o.scrollAdjustment;
639
                scrollWidth = base.$previewCopy[0].scrollWidth - 1;
640
641
                // set initial state as moving right
642
                if (typeof base.last.scrollWidth === 'undefined') {
643
                    base.last.scrollWidth = scrollWidth;
644
                    base.last.direction = true;
645
                }
646
                // if direction = true; we're scrolling to the right
647
                direction = base.last.scrollWidth === scrollWidth ?
648
                    base.last.direction :
649
                    base.last.scrollWidth < scrollWidth;
650
                clientWidth = base.preview.clientWidth - adjustment;
651
652
                // set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
653
                // hug right while scrolling right...
654
                if (direction) {
655
                    if (scrollWidth < clientWidth) {
656
                        base.preview.scrollLeft = 0;
657
                    } else {
658
                        base.preview.scrollLeft = scrollWidth - clientWidth;
659
                    }
660
                } else {
661
                    // hug left while scrolling left...
662
                    if (scrollWidth >= base.preview.scrollWidth - clientWidth) {
663
                        base.preview.scrollLeft = base.preview.scrollWidth - adjustment;
664
                    } else if (scrollWidth - adjustment > 0) {
665
                        base.preview.scrollLeft = scrollWidth - adjustment;
666
                    } else {
667
                        base.preview.scrollLeft = 0;
668
                    }
669
                }
670
671
                base.last.scrollWidth = scrollWidth;
672
                base.last.direction = direction;
673
            }
674
        }
675
    };
676
677
    base.bindFocus = function () {
678
        if (o.openOn) {
679
            // make sure keyboard isn't destroyed
680
            // Check if base exists, this is a case when destroy is called, before timers have fired
681
            if (base && base.el.active) {
682
                base.$el.bind(o.openOn + base.namespace, function () {
683
                    base.focusOn();
684
                });
685
                // remove focus from element (needed for IE since blur doesn't seem to work)
686
                if ($(':focus')[0] === base.el) {
687
                    base.$el.blur();
688
                }
689
            }
690
        }
691
    };
692
693
    base.bindKeyboard = function () {
694
        var evt,
695
            keyCodes = $keyboard.keyCodes,
696
            layout = $keyboard.builtLayouts[base.layout];
697
        base.$preview
698
            .unbind(base.namespace)
699
            .bind('click' + base.namespace + ' touchstart' + base.namespace, function () {
700
                if (o.alwaysOpen && !base.isCurrent()) {
701
                    base.reveal();
702
                }
703
                // update last caret position after user click, use at least 150ms or it doesn't work in IE
704
                base.timer2 = setTimeout(function () {
705
                    if (base){
706
                        base.saveCaret();
707
                    }
708
                }, 150);
709
710
            })
711
            .bind('keypress' + base.namespace, function (e) {
712
                if (o.lockInput || !base.isCurrent()) {
713
                    return false;
714
                }
715
                var k = e.charCode || e.which,
716
                    // capsLock can only be checked while typing a-z
717
                    k1 = k >= keyCodes.A && k <= keyCodes.Z,
718
                    k2 = k >= keyCodes.a && k <= keyCodes.z,
719
                    str = base.last.key = String.fromCharCode(k);
720
                base.last.virtual = false;
721
                base.last.event = e;
722
                base.last.$key = []; // not a virtual keyboard key
723
                if (base.checkCaret) {
724
                    base.saveCaret();
725
                }
726
727
                // update capsLock
728
                if (k !== keyCodes.capsLock && (k1 || k2)) {
729
                    base.capsLock = (k1 && !e.shiftKey) || (k2 && e.shiftKey);
730
                    // if shifted keyset not visible, then show it
731
                    if (base.capsLock && !base.shiftActive) {
732
                        base.shiftActive = true;
733
                        base.showSet();
734
                    }
735
                }
736
737
                // restrict input - keyCode in keypress special keys:
738
                // see http://www.asquare.net/javascript/tests/KeyCode.html
739
                if (o.restrictInput) {
740
                    // allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
741
                    if ((e.which === keyCodes.backSpace || e.which === 0) &&
742
                        $.inArray(e.keyCode, base.alwaysAllowed)) {
743
                        return;
744
                    }
745
                    // quick key check
746
                    if ($.inArray(str, layout.acceptedKeys) === -1) {
747
                        e.preventDefault();
748
                        // copy event object in case e.preventDefault() breaks when changing the type
749
                        evt = $.extend({}, e);
750
                        evt.type = $keyboard.events.inputRestricted;
751
                        base.$el.trigger(evt, [base, base.el]);
752
                    }
753
                } else if ((e.ctrlKey || e.metaKey) &&
754
                    (e.which === keyCodes.A || e.which === keyCodes.C || e.which === keyCodes.V ||
755
                        (e.which >= keyCodes.X && e.which <= keyCodes.Z))) {
756
                    // Allow select all (ctrl-a), copy (ctrl-c), paste (ctrl-v) & cut (ctrl-x) &
757
                    // redo (ctrl-y)& undo (ctrl-z); meta key for mac
758
                    return;
759
                }
760
                // Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
761
                // Set up a key in the layout as follows: 'm(a):label'; m = key to map, (a) = actual keyboard key
762
                // to map to (optional), ':label' = title/tooltip (optional)
763
                // example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
764
                if (layout.hasMappedKeys && layout.mappedKeys.hasOwnProperty(str)) {
765
                    base.last.key = layout.mappedKeys[str];
766
                    base.insertText(base.last.key);
767
                    e.preventDefault();
768
                }
769
                if (typeof o.beforeInsert === 'function') {
770
                    base.insertText(base.last.key);
771
                    e.preventDefault();
772
                }
773
                base.checkMaxLength();
774
775
            })
776
            .bind('keyup' + base.namespace, function (e) {
777
                if (!base.isCurrent()) { return; }
778
                base.last.virtual = false;
779
                switch (e.which) {
780
                    // Insert tab key
781
                case keyCodes.tab:
782
                    // Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab
783
                    // to the keyboard preview area on keyup. Sadly it still happens if you don't release the tab
784
                    // key immediately because keydown event auto-repeats
785
                    if (base.tab && o.tabNavigation && !o.lockInput) {
786
                        base.shiftActive = e.shiftKey;
787
                        // when switching inputs, the tab keyaction returns false
788
                        var notSwitching = $keyboard.keyaction.tab(base);
789
                        base.tab = false;
790
                        if (!notSwitching) {
791
                            return false;
792
                        }
793
                    } else {
794
                        e.preventDefault();
795
                    }
796
                    break;
797
798
                    // Escape will hide the keyboard
799
                case keyCodes.escape:
800
                    if (!o.ignoreEsc) {
801
                        base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false);
802
                    }
803
                    return false;
804
                }
805
806
                // throttle the check combo function because fast typers will have an incorrectly positioned caret
807
                clearTimeout(base.throttled);
808
                base.throttled = setTimeout(function () {
809
                    // fix error in OSX? see issue #102
810
                    if (base && base.isVisible()) {
811
                        base.checkCombos();
812
                    }
813
                }, 100);
814
815
                base.checkMaxLength();
816
817
                base.last.preVal = '' + base.last.val;
818
                base.last.val = base.$preview.val();
819
                e.type = $keyboard.events.kbChange;
820
                // base.last.key may be empty string (shift, enter, tab, etc) when keyboard is first visible
821
                // use e.key instead, if browser supports it
822
                e.action = base.last.key;
823
                base.$el.trigger(e, [base, base.el]);
824
825
                // change callback is no longer bound to the input element as the callback could be
826
                // called during an external change event with all the necessary parameters (issue #157)
827
                if ($.isFunction(o.change)) {
828
                    e.type = $keyboard.events.inputChange;
829
                    o.change(e, base, base.el);
830
                    return false;
831
                }
832
                if (o.acceptValid && o.autoAcceptOnValid) {
833
                    if ($.isFunction(o.validate) && o.validate(base, base.$preview.val())) {
834
                        base.$preview.blur();
835
                        base.accept();
836
                    }
837
                }
838
            })
839
            .bind('keydown' + base.namespace, function (e) {
840
                // ensure alwaysOpen keyboards are made active
841
                if (o.alwaysOpen && !base.isCurrent()) {
842
                    base.reveal();
843
                }
844
                // prevent tab key from leaving the preview window
845
                if (e.which === keyCodes.tab) {
846
                    // allow tab to pass through - tab to next input/shift-tab for prev
847
                    base.tab = true;
848
                    return false;
849
                }
850
851
                if (o.lockInput) {
852
                    return false;
853
                }
854
855
                base.last.virtual = false;
856
                switch (e.which) {
857
858
                case keyCodes.backSpace:
859
                    $keyboard.keyaction.bksp(base, null, e);
860
                    e.preventDefault();
861
                    break;
862
863
                case keyCodes.enter:
864
                    $keyboard.keyaction.enter(base, null, e);
865
                    break;
866
867
                    // Show capsLock
868
                case keyCodes.capsLock:
869
                    base.shiftActive = base.capsLock = !base.capsLock;
870
                    base.showSet();
871
                    break;
872
873
                case keyCodes.V:
874
                    // prevent ctrl-v/cmd-v
875
                    if (e.ctrlKey || e.metaKey) {
876
                        if (o.preventPaste) {
877
                            e.preventDefault();
878
                            return;
879
                        }
880
                        base.checkCombos(); // check pasted content
881
                    }
882
                    break;
883
                }
884
            })
885
            .bind('mouseup touchend '.split(' ').join(base.namespace + ' '), function () {
886
                base.last.virtual = true;
887
                base.saveCaret();
888
            });
889
890
        // prevent keyboard event bubbling
891
        base.$keyboard.bind('mousedown click touchstart '.split(' ').join(base.namespace + ' '), function (e) {
892
            e.stopPropagation();
893
            if (!base.isCurrent()) {
894
                base.reveal();
895
                $(document).trigger('checkkeyboard' + base.namespace);
896
            }
897
            if (!o.noFocus && base.$preview) {
898
                base.$preview.focus();
899
            }
900
        });
901
902
        // If preventing paste, block context menu (right click)
903
        if (o.preventPaste) {
904
            base.$preview.bind('contextmenu' + base.namespace, function (e) {
905
                e.preventDefault();
906
            });
907
            base.$el.bind('contextmenu' + base.namespace, function (e) {
908
                e.preventDefault();
909
            });
910
        }
911
912
    };
913
914
    base.bindKeys = function () {
915
        var kbcss = $keyboard.css;
916
        base.$allKeys = base.$keyboard.find('button.' + kbcss.keyButton)
917
            .unbind(base.namespace + ' ' + base.namespace + 'kb')
918
            // Change hover class and tooltip - moved this touchstart before option.keyBinding touchstart
919
            // to prevent mousewheel lag/duplication - Fixes #379 & #411
920
            .bind('mouseenter mouseleave touchstart '.split(' ').join(base.namespace + ' '), function (e) {
921
                if ((o.alwaysOpen || o.userClosed) && e.type !== 'mouseleave' && !base.isCurrent()) {
922
                    base.reveal();
923
                    base.$preview.focus();
924
                    $keyboard.caret(base.$preview, base.last);
925
                }
926
                if (!base.isCurrent()) {
927
                    return;
928
                }
929
                var $keys, txt,
930
                    last = base.last,
931
                    $this = $(this),
932
                    type = e.type;
933
934
                if (o.useWheel && base.wheel) {
935
                    $keys = base.getLayers($this);
936
                    txt = ($keys.length ? $keys.map(function () {
937
                            return $(this).attr('data-value') || '';
938
                        })
939
                        .get() : '') || [$this.text()];
940
                    last.wheel_$Keys = $keys;
941
                    last.wheelLayers = txt;
942
                    last.wheelIndex = $.inArray($this.attr('data-value'), txt);
943
                }
944
945
                if ((type === 'mouseenter' || type === 'touchstart') && base.el.type !== 'password' &&
946
                    !$this.hasClass(o.css.buttonDisabled)) {
947
                    $this.addClass(o.css.buttonHover);
948
                    if (o.useWheel && base.wheel) {
949
                        $this.attr('title', function (i, t) {
950
                            // show mouse wheel message
951
                            return (base.wheel && t === '' && base.sets && txt.length > 1 && type !== 'touchstart') ?
952
                                o.wheelMessage : t;
953
                        });
954
                    }
955
                }
956
                if (type === 'mouseleave') {
957
                    // needed or IE flickers really bad
958
                    $this.removeClass((base.el.type === 'password') ? '' : o.css.buttonHover);
959
                    if (o.useWheel && base.wheel) {
960
                        last.wheelIndex = 0;
961
                        last.wheelLayers = [];
962
                        last.wheel_$Keys = null;
963
                        $this
964
                            .attr('title', function (i, t) {
965
                                return (t === o.wheelMessage) ? '' : t;
966
                            })
967
                            .html($this.attr('data-html')); // restore original button text
968
                    }
969
                }
970
            })
971
            // keyBinding = 'mousedown touchstart' by default
972
            .bind(o.keyBinding.split(' ').join(base.namespace + ' ') + base.namespace + ' ' +
973
                $keyboard.events.kbRepeater, function (e) {
974
                e.preventDefault();
975
                // prevent errors when external triggers attempt to 'type' - see issue #158
976
                if (!base.$keyboard.is(':visible')) {
977
                    return false;
978
                }
979
                var action, $keys,
980
                    last = base.last,
981
                    key = this,
982
                    $key = $(key),
983
                    // prevent mousedown & touchstart from both firing events at the same time - see #184
984
                    timer = new Date().getTime();
985
986
                if (o.useWheel && base.wheel) {
987
                    // get keys from other layers/keysets (shift, alt, meta, etc) that line up by data-position
988
                    $keys = last.wheel_$Keys;
989
                    // target mousewheel selected key
990
                    $key = $keys && last.wheelIndex > -1 ? $keys.eq(last.wheelIndex) : $key;
991
                }
992
                action = $key.attr('data-action');
993
                if (timer - (last.eventTime || 0) < o.preventDoubleEventTime) {
994
                    return;
995
                }
996
                last.eventTime = timer;
997
                last.event = e;
998
                last.virtual = true;
999
                if (!o.noFocus) {
1000
                    base.$preview.focus();
1001
                }
1002
                last.$key = $key;
1003
                last.key = $key.attr('data-value');
1004
                // Start caret in IE when not focused (happens with each virtual keyboard button click
1005
                if (base.checkCaret) {
1006
                    $keyboard.caret(base.$preview, last);
1007
                }
1008
                if (action.match('meta')) {
1009
                    action = 'meta';
1010
                }
1011
                // keyaction is added as a string, override original action & text
1012
                if (action === last.key && typeof $keyboard.keyaction[action] === 'string') {
1013
                    last.key = action = $keyboard.keyaction[action];
1014
                } else if (action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[action])) {
1015
                    // stop processing if action returns false (close & cancel)
1016
                    if ($keyboard.keyaction[action](base, this, e) === false) {
1017
                        return false;
1018
                    }
1019
                    action = null; // prevent inserting action name
1020
                }
1021
                if (typeof action !== 'undefined' && action !== null) {
1022
                    last.key = $(this).hasClass(kbcss.keyAction) ? action : last.key;
1023
                    base.insertText(last.key);
1024
                    if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
1025
                        base.shiftActive = false;
1026
                        base.showSet($key.attr('data-name'));
1027
                    }
1028
                }
1029
                // set caret if caret moved by action function; also, attempt to fix issue #131
1030
                $keyboard.caret(base.$preview, last);
1031
                base.checkCombos();
1032
                e.type = $keyboard.events.kbChange;
1033
                e.action = last.key;
1034
                base.$el.trigger(e, [base, base.el]);
1035
                last.preVal = '' + last.val;
1036
                last.val = base.$preview.val();
1037
1038
                if ($.isFunction(o.change)) {
1039
                    e.type = $keyboard.events.inputChange;
1040
                    o.change(e, base, base.el);
1041
                    // return false to prevent reopening keyboard if base.accept() was called
1042
                    return false;
1043
                }
1044
1045
            })
1046
            // using 'kb' namespace for mouse repeat functionality to keep it separate
1047
            // I need to trigger a 'repeater.keyboard' to make it work
1048
            .bind('mouseup' + base.namespace + ' ' + 'mouseleave touchend touchmove touchcancel '.split(' ')
1049
                .join(base.namespace + 'kb '), function (e) {
1050
                base.last.virtual = true;
1051
                var offset,
1052
                    $this = $(this);
1053
                if (e.type === 'touchmove') {
1054
                    // if moving within the same key, don't stop repeating
1055
                    offset = $this.offset();
1056
                    offset.right = offset.left + $this.outerWidth();
1057
                    offset.bottom = offset.top + $this.outerHeight();
1058
                    if (e.originalEvent.touches[0].pageX >= offset.left &&
1059
                        e.originalEvent.touches[0].pageX < offset.right &&
1060
                        e.originalEvent.touches[0].pageY >= offset.top &&
1061
                        e.originalEvent.touches[0].pageY < offset.bottom) {
1062
                        return true;
1063
                    }
1064
                } else if (/(mouseleave|touchend|touchcancel)/i.test(e.type)) {
1065
                    $this.removeClass(o.css.buttonHover); // needed for touch devices
1066
                } else {
1067
                    if (!o.noFocus && base.isCurrent() && base.isVisible()) {
1068
                        base.$preview.focus();
1069
                    }
1070
                    if (base.checkCaret) {
1071
                        $keyboard.caret(base.$preview, base.last);
1072
                    }
1073
                }
1074
                base.mouseRepeat = [false, ''];
1075
                clearTimeout(base.repeater); // make sure key repeat stops!
1076
                if (o.acceptValid && o.autoAcceptOnValid) {
1077
                    if ($.isFunction(o.validate) && o.validate(base, base.$preview.val())) {
1078
                        base.$preview.blur();
1079
                        base.accept();
1080
                    }
1081
                }
1082
                return false;
1083
            })
1084
            // prevent form submits when keyboard is bound locally - issue #64
1085
            .bind('click' + base.namespace, function () {
1086
                return false;
1087
            })
1088
            // no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
1089
            .not('.' + kbcss.keyAction)
1090
            // Allow mousewheel to scroll through other keysets of the same (non-action) key
1091
            .bind('mousewheel' + base.namespace, function (e, delta) {
1092
                if (o.useWheel && base.wheel) {
1093
                    // deltaY used by newer versions of mousewheel plugin
1094
                    delta = delta || e.deltaY;
1095
                    var n,
1096
                        txt = base.last.wheelLayers || [];
1097
                    if (txt.length > 1) {
1098
                        n = base.last.wheelIndex + (delta > 0 ? -1 : 1);
1099
                        if (n > txt.length - 1) {
1100
                            n = 0;
1101
                        }
1102
                        if (n < 0) {
1103
                            n = txt.length - 1;
1104
                        }
1105
                    } else {
1106
                        n = 0;
1107
                    }
1108
                    base.last.wheelIndex = n;
1109
                    $(this).html(txt[n]);
1110
                    return false;
1111
                }
1112
            })
1113
            // mouse repeated action key exceptions
1114
            .add('.' + kbcss.keyPrefix + ('tab bksp space enter'.split(' ')
1115
                .join(',.' + kbcss.keyPrefix)), base.$keyboard)
1116
            .bind('mousedown touchstart '.split(' ').join(base.namespace + 'kb '), function () {
1117
                if (o.repeatRate !== 0) {
1118
                    var key = $(this);
1119
                    // save the key, make sure we are repeating the right one (fast typers)
1120
                    base.mouseRepeat = [true, key];
1121
                    setTimeout(function () {
1122
                        // don't repeat keys if it is disabled - see #431
1123
                        if (base && base.mouseRepeat[0] && base.mouseRepeat[1] === key && !key[0].disabled) {
1124
                            base.repeatKey(key);
1125
                        }
1126
                    }, o.repeatDelay);
1127
                }
1128
                return false;
1129
            });
1130
    };
1131
1132
    // Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
1133
    base.insertText = function (txt) {
1134
        if (typeof o.beforeInsert === 'function') {
1135
            txt = o.beforeInsert(base.last.event, base, base.el, txt);
1136
        }
1137
        if (typeof txt === 'undefined' || txt === false) {
1138
            base.last.key = '';
1139
            return;
1140
        }
1141
        var bksp, t,
1142
            isBksp = txt === '\b',
1143
            // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
1144
            val = base.$preview.val(),
1145
            pos = $keyboard.caret(base.$preview),
1146
            len = val.length; // save original content length
1147
1148
        // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1149
        // is still difficult
1150
        // in IE, pos.end can be zero after input loses focus
1151
        if (pos.end < pos.start) {
1152
            pos.end = pos.start;
1153
        }
1154
        if (pos.start > len) {
1155
            pos.end = pos.start = len;
1156
        }
1157
1158
        if (base.preview.nodeName === 'TEXTAREA') {
1159
            // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1160
            if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
1161
                pos.start += 1;
1162
                pos.end += 1;
1163
            }
1164
        }
1165
1166
        if (txt === '{d}') {
1167
            txt = '';
1168
            t = pos.start;
1169
            pos.end += 1;
1170
        }
1171
1172
        bksp = isBksp && pos.start === pos.end;
1173
        txt = isBksp ? '' : txt;
1174
        val = val.substr(0, pos.start - (bksp ? 1 : 0)) + txt + val.substr(pos.end);
1175
        t = pos.start + (bksp ? -1 : txt.length);
1176
1177
        base.$preview.val(val);
1178
        base.saveCaret(t, t); // save caret in case of bksp
1179
        base.setScroll();
1180
    };
1181
1182
    // check max length
1183
    base.checkMaxLength = function () {
1184
        if (!base.isCurrent()) { return; }
1185
        var start, caret,
1186
            val = base.$preview.val();
1187
        if (o.maxLength !== false && val.length > o.maxLength) {
1188
            start = $keyboard.caret(base.$preview).start;
1189
            caret = Math.min(start, o.maxLength);
1190
1191
            // prevent inserting new characters when maxed #289
1192
            if (!o.maxInsert) {
1193
                val = base.last.val;
1194
                caret = start - 1; // move caret back one
1195
            }
1196
1197
            base.$preview.val(val.substring(0, o.maxLength));
1198
            // restore caret on change, otherwise it ends up at the end.
1199
            base.saveCaret(caret, caret);
1200
        }
1201
        if (base.$decBtn.length) {
1202
            base.checkDecimal();
1203
        }
1204
    };
1205
1206
    // mousedown repeater
1207
    base.repeatKey = function (key) {
1208
        key.trigger($keyboard.events.kbRepeater);
1209
        if (base.mouseRepeat[0]) {
1210
            base.repeater = setTimeout(function () {
1211
                if (base){
1212
                    base.repeatKey(key);
1213
                }
1214
            }, base.repeatTime);
1215
        }
1216
    };
1217
1218
    // make it easier to switch keysets via API
1219
    // showKeySet('shift+alt+meta1')
1220
    base.showKeySet = function (str) {
1221
        if (typeof str === 'string') {
1222
            base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
1223
            base.shiftActive = /shift/i.test(str);
1224
            base.altActive = /alt/i.test(str);
1225
            if (/meta/.test(str)) {
1226
                base.metaActive = true;
1227
                base.showSet(str.match(/meta\d+/i)[0]);
1228
            } else {
1229
                base.metaActive = false;
1230
                base.showSet();
1231
            }
1232
        } else {
1233
            base.showSet(str);
1234
        }
1235
    };
1236
1237
    base.showSet = function (name) {
1238
        o = base.options; // refresh options
1239
        var kbcss = $keyboard.css,
1240
            prefix = '.' + kbcss.keyPrefix,
1241
            active = o.css.buttonActive,
1242
            key = '',
1243
            toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1244
        if (!base.shiftActive) {
1245
            base.capsLock = false;
1246
        }
1247
        // check meta key set
1248
        if (base.metaActive) {
1249
            // the name attribute contains the meta set # 'meta99'
1250
            key = (/meta/i.test(name)) ? name : '';
1251
            // save active meta keyset name
1252
            if (key === '') {
1253
                key = (base.metaActive === true) ? '' : base.metaActive;
1254
            } else {
1255
                base.metaActive = key;
1256
            }
1257
            // if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
1258
            if ((!o.stickyShift && base.last.keyset[2] !== base.metaActive) ||
1259
                ((base.shiftActive || base.altActive) &&
1260
                !base.$keyboard.find('.' + kbcss.keySet + '-' + key + base.rows[toShow]).length)) {
1261
                base.shiftActive = base.altActive = false;
1262
            }
1263
        } else if (!o.stickyShift && base.last.keyset[2] !== base.metaActive && base.shiftActive) {
1264
            // switching from meta key set back to default, reset shift & alt if using stickyShift
1265
            base.shiftActive = base.altActive = false;
1266
        }
1267
        toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
1268
        key = (toShow === 0 && !base.metaActive) ? '-normal' : (key === '') ? '' : '-' + key;
1269
        if (!base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow]).length) {
1270
            // keyset doesn't exist, so restore last keyset settings
1271
            base.shiftActive = base.last.keyset[0];
1272
            base.altActive = base.last.keyset[1];
1273
            base.metaActive = base.last.keyset[2];
1274
            return;
1275
        }
1276
        base.$keyboard
1277
            .find(prefix + 'alt,' + prefix + 'shift,.' + kbcss.keyAction + '[class*=meta]')
1278
            .removeClass(active)
1279
            .end()
1280
            .find(prefix + 'alt')
1281
            .toggleClass(active, base.altActive)
1282
            .end()
1283
            .find(prefix + 'shift')
1284
            .toggleClass(active, base.shiftActive)
1285
            .end()
1286
            .find(prefix + 'lock')
1287
            .toggleClass(active, base.capsLock)
1288
            .end()
1289
            .find('.' + kbcss.keySet)
1290
            .hide()
1291
            .end()
1292
            .find('.' + kbcss.keyAction + prefix + key)
1293
            .addClass(active);
1294
1295
        // show keyset using inline-block ( extender layout will then line up )
1296
        base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow])[0].style.display = 'inline-block';
1297
1298
        if (base.metaActive) {
1299
            base.$keyboard.find(prefix + base.metaActive)
1300
                // base.metaActive contains the string "meta#" or false
1301
                // without the !== false, jQuery UI tries to transition the classes
1302
                .toggleClass(active, base.metaActive !== false);
1303
        }
1304
        base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
1305
        base.$el.trigger($keyboard.events.kbKeysetChange, [base, base.el]);
1306
    };
1307
1308
    // check for key combos (dead keys)
1309
    base.checkCombos = function () {
1310
        // return val for close function
1311
        if (!(base.isVisible() || base.$keyboard.hasClass($keyboard.css.hasFocus))) {
1312
            return base.$preview.val();
1313
        }
1314
        var r, t, t2,
1315
            // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
1316
            val = base.$preview.val(),
1317
            pos = $keyboard.caret(base.$preview),
1318
            layout = $keyboard.builtLayouts[base.layout],
1319
            len = val.length; // save original content length
1320
        // return if val is empty; fixes #352
1321
        if (val === '') {
1322
            // check valid on empty string - see #429
1323
            if (o.acceptValid) {
1324
                base.checkValid();
1325
            }
1326
            return val;
1327
        }
1328
1329
        // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
1330
        // is still difficult
1331
        // in IE, pos.end can be zero after input loses focus
1332
        if (pos.end < pos.start) {
1333
            pos.end = pos.start;
1334
        }
1335
        if (pos.start > len) {
1336
            pos.end = pos.start = len;
1337
        }
1338
        // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
1339
        if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
1340
            pos.start += 1;
1341
            pos.end += 1;
1342
        }
1343
1344
        if (o.useCombos) {
1345
            // keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
1346
            // thanks to KennyTM: http://stackoverflow.com/q/4275077
1347
            // original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
1348
            if ($keyboard.msie) {
1349
                // old IE may not have the caret positioned correctly, so just check the whole thing
1350
                val = val.replace(base.regex, function (s, accent, letter) {
1351
                    return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1352
                });
1353
                // prevent combo replace error, in case the keyboard closes - see issue #116
1354
            } else if (base.$preview.length) {
1355
                // Modern browsers - check for combos from last two characters left of the caret
1356
                t = pos.start - (pos.start - 2 >= 0 ? 2 : 0);
1357
                // target last two characters
1358
                $keyboard.caret(base.$preview, t, pos.end);
1359
                // do combo replace
1360
                t2 = ($keyboard.caret(base.$preview).text || '').replace(base.regex, function (s, accent, letter) {
1361
                    return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
1362
                });
1363
                // add combo back
1364
                base.$preview.val($keyboard.caret(base.$preview).replaceStr(t2));
1365
                val = base.$preview.val();
1366
            }
1367
        }
1368
1369
        // check input restrictions - in case content was pasted
1370
        if (o.restrictInput && val !== '') {
1371
            t = layout.acceptedKeys.length;
1372
1373
            r = layout.acceptedKeysRegex;
1374
            if (!r) {
1375
                t2 = $.map(layout.acceptedKeys, function (v) {
1376
                    // escape any special characters
1377
                    return v.replace(base.escapeRegex, '\\$&');
1378
                });
1379
                r = layout.acceptedKeysRegex = new RegExp('(' + t2.join('|') + ')', 'g');
1380
            }
1381
1382
            // only save matching keys
1383
            t2 = val.match(r);
1384
            if (t2) {
1385
                val = t2.join('');
1386
            } else {
1387
                // no valid characters
1388
                val = '';
1389
                len = 0;
1390
            }
1391
        }
1392
1393
        // save changes, then reposition caret
1394
        pos.start += val.length - len;
1395
        pos.end += val.length - len;
1396
        base.$preview.val(val);
1397
        base.saveCaret(pos.start, pos.end);
1398
        // set scroll to keep caret in view
1399
        base.setScroll();
1400
1401
        base.checkMaxLength();
1402
1403
        if (o.acceptValid) {
1404
            base.checkValid();
1405
        }
1406
1407
        return val; // return text, used for keyboard closing section
1408
    };
1409
1410
    // Toggle accept button classes, if validating
1411
    base.checkValid = function () {
1412
        var kbcss = $keyboard.css,
1413
            $accept = base.$keyboard.find('.' + kbcss.keyPrefix + 'accept'),
1414
            valid = true;
1415
        if ($.isFunction(o.validate)) {
1416
            valid = o.validate(base, base.$preview.val(), false);
1417
        }
1418
        // toggle accept button classes; defined in the css
1419
        $accept
1420
            .toggleClass(kbcss.inputInvalid, !valid)
1421
            .toggleClass(kbcss.inputValid, valid)
1422
            // update title to indicate that the entry is valid or invalid
1423
            .attr('title', $accept.attr('data-title') + ' (' + o.display[valid ? 'valid' : 'invalid'] + ')');
1424
    };
1425
1426
    // Decimal button for num pad - only allow one (not used by default)
1427
    base.checkDecimal = function () {
1428
        // Check US '.' or European ',' format
1429
        if ((base.decimal && /\./g.test(base.preview.value)) ||
1430
            (!base.decimal && /\,/g.test(base.preview.value))) {
1431
            base.$decBtn
1432
                .attr({
1433
                    'disabled': 'disabled',
1434
                    'aria-disabled': 'true'
1435
                })
1436
                .removeClass(o.css.buttonHover)
1437
                .addClass(o.css.buttonDisabled);
1438
        } else {
1439
            base.$decBtn
1440
                .removeAttr('disabled')
1441
                .attr({
1442
                    'aria-disabled': 'false'
1443
                })
1444
                .addClass(o.css.buttonDefault)
1445
                .removeClass(o.css.buttonDisabled);
1446
        }
1447
    };
1448
1449
    // get other layer values for a specific key
1450
    base.getLayers = function ($el) {
1451
        var kbcss = $keyboard.css,
1452
            key = $el.attr('data-pos'),
1453
            $keys = $el.closest('.' + kbcss.keyboard)
1454
            .find('button[data-pos="' + key + '"]');
1455
        return $keys.filter(function () {
1456
            return $(this)
1457
                .find('.' + kbcss.keyText)
1458
                .text() !== '';
1459
        })
1460
        .add($el);
1461
    };
1462
1463
    // Go to next or prev inputs
1464
    // goToNext = true, then go to next input; if false go to prev
1465
    // isAccepted is from autoAccept option or true if user presses shift+enter
1466
    base.switchInput = function (goToNext, isAccepted) {
1467
        if ($.isFunction(o.switchInput)) {
1468
            o.switchInput(base, goToNext, isAccepted);
1469
        } else {
1470
            // base.$keyboard may be an empty array - see #275 (apod42)
1471
            if (base.$keyboard.length) {
1472
                base.$keyboard.hide();
1473
            }
1474
            var kb,
1475
                stopped = false,
1476
                all = $('button, input, textarea, a')
1477
                    .filter(':visible')
1478
                    .not(':disabled'),
1479
                indx = all.index(base.$el) + (goToNext ? 1 : -1);
1480
            if (base.$keyboard.length) {
1481
                base.$keyboard.show();
1482
            }
1483
            if (indx > all.length - 1) {
1484
                stopped = o.stopAtEnd;
1485
                indx = 0; // go to first input
1486
            }
1487
            if (indx < 0) {
1488
                stopped = o.stopAtEnd;
1489
                indx = all.length - 1; // stop or go to last
1490
            }
1491
            if (!stopped) {
1492
                isAccepted = base.close(isAccepted);
1493
                if (!isAccepted) {
1494
                    return;
1495
                }
1496
                kb = all.eq(indx).data('keyboard');
1497
                if (kb && kb.options.openOn.length) {
1498
                    kb.focusOn();
1499
                } else {
1500
                    all.eq(indx).focus();
1501
                }
1502
            }
1503
        }
1504
        return false;
1505
    };
1506
1507
    // Close the keyboard, if visible. Pass a status of true, if the content was accepted
1508
    // (for the event trigger).
1509
    base.close = function (accepted) {
1510
        if (base.isOpen && base.$keyboard.length) {
1511
            clearTimeout(base.throttled);
1512
            var kbcss = $keyboard.css,
1513
                kbevents = $keyboard.events,
1514
                val = (accepted) ? base.checkCombos() : base.originalContent;
1515
            // validate input if accepted
1516
            if (accepted && $.isFunction(o.validate) && !o.validate(base, val, true)) {
1517
                val = base.originalContent;
1518
                accepted = false;
1519
                if (o.cancelClose) {
1520
                    return;
1521
                }
1522
            }
1523
            base.isCurrent(false);
1524
            base.isOpen = o.alwaysOpen || o.userClosed;
1525
            // update value for always open keyboards
1526
            base.$preview.val(val);
1527
            base.$el
1528
                .removeClass(kbcss.isCurrent + ' ' + kbcss.inputAutoAccepted)
1529
                // add 'ui-keyboard-autoaccepted' to inputs - see issue #66
1530
                .addClass((accepted || false) ? accepted === true ? '' : kbcss.inputAutoAccepted : '')
1531
                .val(val)
1532
                // trigger default change event - see issue #146
1533
                .trigger(kbevents.inputChange);
1534
            // don't trigger an empty event - see issue #463
1535
            if (!o.alwaysOpen) {
1536
                // don't trigger beforeClose if keyboard is always open
1537
                base.$el.trigger(kbevents.kbBeforeClose, [base, base.el, (accepted || false)]);
1538
            }
1539
            base.$el
1540
                .trigger(((accepted || false) ? kbevents.inputAccepted : kbevents.inputCanceled), [base, base.el])
1541
                .trigger((o.alwaysOpen) ? kbevents.kbInactive : kbevents.kbHidden, [base, base.el])
1542
                .blur();
1543
1544
            // save caret after updating value (fixes userClosed issue with changing focus)
1545
            $keyboard.caret(base.$preview, base.last);
1546
            // base is undefined if keyboard was destroyed - fixes #358
1547
            if (base) {
1548
                // add close event time
1549
                base.last.eventTime = new Date().getTime();
1550
                if (!(o.alwaysOpen || o.userClosed && accepted === 'true') && base.$keyboard.length) {
1551
                    // free up memory
1552
                    base.removeKeyboard();
1553
                    // rebind input focus - delayed to fix IE issue #72
1554
                    base.timer = setTimeout(function () {
1555
                        if(base){
1556
                            base.bindFocus();
1557
                        }
1558
                    }, 500);
1559
                }
1560
                if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') {
1561
                    base.$el
1562
                        .addClass(kbcss.placeholder)
1563
                        .val(base.inPlaceholder);
1564
                }
1565
            }
1566
        }
1567
        return !!accepted;
1568
    };
1569
1570
    base.accept = function () {
1571
        return base.close(true);
1572
    };
1573
1574
    base.checkClose = function (e) {
1575
        if (base.opening) {
1576
            return;
1577
        }
1578
        base.escClose(e);
1579
        var kbcss = $.keyboard.css,
1580
            $target = $(e.target);
1581
        // needed for IE to allow switching between keyboards smoothly
1582
        if ($target.hasClass(kbcss.input)) {
1583
            var kb = $target.data('keyboard');
1584
            // only trigger on self
1585
            if (kb === base && !kb.$el.hasClass(kbcss.isCurrent) && e.type === kb.options.openOn) {
1586
                kb.focusOn();
1587
            }
1588
        }
1589
    };
1590
1591
    base.escClose = function (e) {
1592
        if (e && e.type === 'keyup') {
1593
            return (e.which === $keyboard.keyCodes.escape && !o.ignoreEsc) ?
1594
                base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false) :
1595
                '';
1596
        }
1597
        // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or
1598
        // single stay open keyboard
1599
        if (!base.isOpen) {
1600
            return;
1601
        }
1602
        // ignore autoaccept if using escape - good idea?
1603
        if (!base.isCurrent() && base.isOpen || base.isOpen && e.target !== base.el) {
1604
            // don't close if stayOpen is set; but close if a different keyboard is being opened
1605
            if ((o.stayOpen || o.userClosed) && !$(e.target).hasClass($keyboard.css.input)) {
1606
                return;
1607
            }
1608
            // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
1609
            if ($keyboard.allie) {
1610
                e.preventDefault();
1611
            }
1612
            if (o.closeByClickEvent) {
1613
                // only close the keyboard if the user is clicking on an input or if he causes a click
1614
                // event (touchstart/mousedown will not force the close with this setting)
1615
                var name = e.target.nodeName.toLowerCase();
1616
                if (name === 'input' || name === 'textarea' || e.type === 'click') {
1617
                    base.close(o.autoAccept ? 'true' : false);
1618
                }
1619
            } else {
1620
                // send 'true' instead of a true (boolean), the input won't get a 'ui-keyboard-autoaccepted'
1621
                // class name - see issue #66
1622
                base.close(o.autoAccept ? 'true' : false);
1623
            }
1624
        }
1625
    };
1626
1627
    // Build default button
1628
    base.keyBtn = $('<button />')
1629
        .attr({
1630
            'role': 'button',
1631
            'type': 'button',
1632
            'aria-disabled': 'false',
1633
            'tabindex': '-1'
1634
        })
1635
        .addClass($keyboard.css.keyButton);
1636
1637
    // convert key names into a class name
1638
    base.processName = function (name) {
1639
        var index, n,
1640
            process = (name || '').replace(/[^a-z0-9-_]/gi, ''),
1641
            len = process.length,
1642
            newName = [];
1643
        if (len > 1 && name === process) {
1644
            // return name if basic text
1645
            return name;
1646
        }
1647
        // return character code sequence
1648
        len = name.length;
1649
        if (len) {
1650
            for (index = 0; index < len; index++) {
1651
                n = name[index];
1652
                // keep '-' and '_'... so for dash, we get two dashes in a row
1653
                newName.push(/[a-z0-9-_]/i.test(n) ?
1654
                    (/[-_]/.test(n) && index !== 0 ? '' : n) :
1655
                    (index === 0 ? '' : '-') + n.charCodeAt(0)
1656
                );
1657
            }
1658
            return newName.join('');
1659
        } else {
1660
            return name;
1661
        }
1662
    };
1663
1664
    base.processKeys = function (name) {
1665
        var tmp,
1666
            parts = name.split(':'),
1667
            data = {
1668
                name: null,
1669
                map: '',
1670
                title: ''
1671
            };
1672
        /* map defined keys
1673
        format 'key(A):Label_for_key_(ignore_parentheses_here)'
1674
            'key' = key that is seen (can any character(s); but it might need to be escaped using '\'
1675
            or entered as unicode '\u####'
1676
            '(A)' = the actual key on the real keyboard to remap
1677
            ':Label_for_key' ends up in the title/tooltip
1678
        Examples:
1679
            '\u0391(A):alpha', 'x(y):this_(might)_cause_problems
1680
            or edge cases of ':(x)', 'x(:)', 'x(()' or 'x())'
1681
        Enhancement (if I can get alt keys to work):
1682
            A mapped key will include the mod key, e.g. 'x(alt-x)' or 'x(alt-shift-x)'
1683
        */
1684
        if (/\(.+\)/.test(parts[0]) || /^:\(.+\)/.test(name) || /\([(:)]\)/.test(name)) {
1685
            // edge cases 'x(:)', 'x(()' or 'x())'
1686
            if (/\([(:)]\)/.test(name)) {
1687
                tmp = parts[0].match(/([^(]+)\((.+)\)/);
1688
                if (tmp && tmp.length) {
1689
                    data.name = tmp[1];
1690
                    data.map = tmp[2];
1691
                    data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
1692
                } else {
1693
                    // edge cases 'x(:)', ':(x)' or ':(:)'
1694
                    data.name = name.match(/([^(]+)/)[0];
1695
                    if (data.name === ':') {
1696
                        // ':(:):test' => parts = [ '', '(', ')', 'title' ] need to slice 1
1697
                        parts = parts.slice(1);
1698
                    }
1699
                    if (tmp === null) {
1700
                        // 'x(:):test' => parts = [ 'x(', ')', 'title' ] need to slice 2
1701
                        data.map = ':';
1702
                        parts = parts.slice(2);
1703
                    }
1704
                    data.title = parts.length ? parts.join(':') : '';
1705
                }
1706
            } else {
1707
                // example: \u0391(A):alpha; extract 'A' from '(A)'
1708
                data.map = name.match(/\(([^()]+?)\)/)[1];
1709
                // remove '(A)', left with '\u0391:alpha'
1710
                name = name.replace(/\(([^()]+)\)/, '');
1711
                tmp = name.split(':');
1712
                // get '\u0391' from '\u0391:alpha'
1713
                if (tmp[0] === '') {
1714
                    data.name = ':';
1715
                    parts = parts.slice(1);
1716
                } else {
1717
                    data.name = tmp[0];
1718
                }
1719
                data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
1720
            }
1721
        } else {
1722
            // find key label
1723
            // corner case of '::;' reduced to ':;', split as ['', ';']
1724
            if (name !== '' && parts[0] === '') {
1725
                data.name = ':';
1726
                parts = parts.slice(1);
1727
            } else {
1728
                data.name = parts[0];
1729
            }
1730
            data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
1731
        }
1732
        data.title = $.trim(data.title).replace(/_/g, ' ');
1733
        return data;
1734
    };
1735
1736
    // Add key function
1737
    // keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
1738
    // name = name added to key, or cross-referenced in the display options
1739
    // base.temp[0] = keyset to attach the new button
1740
    // regKey = true when it is not an action key
1741
    base.addKey = function (keyName, action, regKey) {
1742
        var keyClass, tmp, keys,
1743
            data = {},
1744
            txt = base.processKeys(regKey ? keyName : action),
1745
            kbcss = $keyboard.css;
1746
1747
        if (!regKey && o.display[txt.name]) {
1748
            keys = base.processKeys(o.display[txt.name]);
1749
            // action contained in "keyName" (e.g. keyName = "accept",
1750
            // action = "a" (use checkmark instead of text))
1751
            keys.action = base.processKeys(keyName).name;
1752
        } else {
1753
            // when regKey is true, keyName is the same as action
1754
            keys = txt;
1755
            keys.action = txt.name;
1756
        }
1757
1758
        data.name = base.processName(txt.name);
1759
1760
        if (keys.map !== '') {
1761
            $keyboard.builtLayouts[base.layout].mappedKeys[keys.map] = keys.name;
1762
            $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
1763
        } else if (regKey) {
1764
            $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
1765
        }
1766
1767
        if (regKey) {
1768
            keyClass = data.name === '' ? '' : kbcss.keyPrefix + data.name;
1769
        } else {
1770
            // Action keys will have the 'ui-keyboard-actionkey' class
1771
            keyClass = kbcss.keyAction + ' ' + kbcss.keyPrefix + keys.action;
1772
        }
1773
        // '\u2190'.length = 1 because the unicode is converted, so if more than one character,
1774
        // add the wide class
1775
        keyClass += (keys.name.length > 2 ? ' ' + kbcss.keyWide : '') + ' ' + o.css.buttonDefault;
1776
1777
        data.html = '<span class="' + kbcss.keyText + '">' +
1778
            // this prevents HTML from being added to the key
1779
            keys.name.replace(/[\u00A0-\u9999]/gim, function (i) {
1780
                return '&#' + i.charCodeAt(0) + ';';
1781
            }) +
1782
            '</span>';
1783
1784
        data.$key = base.keyBtn
1785
            .clone()
1786
            .attr({
1787
                'data-value': regKey ? keys.name : keys.action, // value
1788
                'data-name': keys.action,
1789
                'data-pos': base.temp[1] + ',' + base.temp[2],
1790
                'data-action': keys.action,
1791
                'data-html': data.html
1792
            })
1793
            // add 'ui-keyboard-' + data.name for all keys
1794
            //  (e.g. 'Bksp' will have 'ui-keyboard-bskp' class)
1795
            // any non-alphanumeric characters will be replaced with
1796
            //  their decimal unicode value
1797
            //  (e.g. '~' is a regular key, class = 'ui-keyboard-126'
1798
            //  (126 is the unicode decimal value - same as &#126;)
1799
            //  See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
1800
            .addClass(keyClass)
1801
            .html(data.html)
1802
            .appendTo(base.temp[0]);
1803
1804
        if (keys.map) {
1805
            data.$key.attr('data-mapped', keys.map);
1806
        }
1807
        if (keys.title || txt.title) {
1808
            data.$key.attr({
1809
                'data-title': txt.title || keys.title, // used to allow adding content to title
1810
                'title': txt.title || keys.title
1811
            });
1812
        }
1813
1814
        if (typeof o.buildKey === 'function') {
1815
            data = o.buildKey(base, data);
1816
            // copy html back to attributes
1817
            tmp = data.$key.html();
1818
            data.$key.attr('data-html', tmp);
1819
        }
1820
        return data.$key;
1821
    };
1822
1823
    base.customHash = function (layout) {
1824
        /*jshint bitwise:false */
1825
        var i, array, hash, character, len,
1826
            arrays = [],
1827
            merged = [];
1828
        // pass layout to allow for testing
1829
        layout = typeof layout === 'undefined' ? o.customLayout : layout;
1830
        // get all layout arrays
1831
        for (array in layout) {
1832
            if (layout.hasOwnProperty(array)) {
1833
                arrays.push(layout[array]);
1834
            }
1835
        }
1836
        // flatten array
1837
        merged = merged.concat.apply(merged, arrays).join(' ');
1838
        // produce hash name - http://stackoverflow.com/a/7616484/145346
1839
        hash = 0;
1840
        len = merged.length;
1841
        if (len === 0) {
1842
            return hash;
1843
        }
1844
        for (i = 0; i < len; i++) {
1845
            character = merged.charCodeAt(i);
1846
            hash = ((hash << 5) - hash) + character;
1847
            hash = hash & hash; // Convert to 32bit integer
1848
        }
1849
        return hash;
1850
    };
1851
1852
    base.buildKeyboard = function (name, internal) {
1853
        // o.display is empty when this is called from the scramble extension (when alwaysOpen:true)
1854
        if ($.isEmptyObject(o.display)) {
1855
            // set keyboard language
1856
            base.updateLanguage();
1857
        }
1858
        var row, $row, currentSet,
1859
            kbcss = $keyboard.css,
1860
            sets = 0,
1861
            layout = $keyboard.builtLayouts[name || base.layout || o.layout] = {
1862
                mappedKeys: {},
1863
                acceptedKeys: []
1864
            },
1865
            acceptedKeys = layout.acceptedKeys = o.restrictInclude ?
1866
                ('' + o.restrictInclude).split(/\s+/) || [] :
1867
                [],
1868
            // using $layout temporarily to hold keyboard popup classnames
1869
            $layout = kbcss.keyboard + ' ' + o.css.popup + ' ' + o.css.container +
1870
                (o.alwaysOpen || o.userClosed ? ' ' + kbcss.alwaysOpen : ''),
1871
1872
            container = $('<div />')
1873
                .addClass($layout)
1874
                .attr({
1875
                    'role': 'textbox'
1876
                })
1877
                .hide();
1878
        // verify layout or setup custom keyboard
1879
        if ((internal && o.layout === 'custom') || !$keyboard.layouts.hasOwnProperty(o.layout)) {
1880
            o.layout = 'custom';
1881
            $layout = $keyboard.layouts.custom = o.customLayout || {
1882
                'normal': ['{cancel}']
1883
            };
1884
        } else {
1885
            $layout = $keyboard.layouts[internal ? o.layout : name || base.layout || o.layout];
1886
        }
1887
1888
        // Main keyboard building loop
1889
        $.each($layout, function (set, keySet) {
1890
            // skip layout name & lang settings
1891
            if (set !== '' && !/^(name|lang|rtl)$/i.test(set)) {
1892
                // keep backwards compatibility for change from default to normal naming
1893
                if (set === 'default') {
1894
                    set = 'normal';
1895
                }
1896
                sets++;
1897
                $row = $('<div />')
1898
                    .attr('name', set) // added for typing extension
1899
                    .addClass(kbcss.keySet + ' ' + kbcss.keySet + '-' + set)
1900
                    .appendTo(container)
1901
                    .toggle(set === 'normal');
1902
1903
                for (row = 0; row < keySet.length; row++) {
1904
                    // remove extra spaces before spliting (regex probably could be improved)
1905
                    currentSet = $.trim(keySet[row]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g, '{$1:$2}');
1906
                    base.buildRow($row, row, currentSet.split(/\s+/), acceptedKeys);
1907
                    $row.find('.' + kbcss.keyButton + ',.' + kbcss.keySpacer)
1908
                        .filter(':last')
1909
                        .after('<br class="' + kbcss.endRow + '"/>');
1910
                }
1911
            }
1912
        });
1913
1914
        if (sets > 1) {
1915
            base.sets = true;
1916
        }
1917
        layout.hasMappedKeys = !($.isEmptyObject(layout.mappedKeys));
1918
        layout.$keyboard = container;
1919
        return container;
1920
    };
1921
1922
    base.buildRow = function ($row, row, keys, acceptedKeys) {
1923
        var t, txt, key, isAction, action, margin,
1924
            kbcss = $keyboard.css;
1925
        for (key = 0; key < keys.length; key++) {
1926
            // used by addKey function
1927
            base.temp = [$row, row, key];
1928
            isAction = false;
1929
1930
            // ignore empty keys
1931
            if (keys[key].length === 0) {
1932
                continue;
1933
            }
1934
1935
            // process here if it's an action key
1936
            if (/^\{\S+\}$/.test(keys[key])) {
1937
                action = keys[key].match(/^\{(\S+)\}$/)[1];
1938
                // add active class if there are double exclamation points in the name
1939
                if (/\!\!/.test(action)) {
1940
                    action = action.replace('!!', '');
1941
                    isAction = true;
1942
                }
1943
1944
                // add empty space
1945
                if (/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i.test(action)) {
1946
                    // not perfect globalization, but allows you to use {sp:1,1em}, {sp:1.2em} or {sp:15px}
1947
                    margin = parseFloat(action
1948
                        .replace(/,/, '.')
1949
                        .match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
1950
                    );
1951
                    $('<span class="' + kbcss.keyText + '"></span>')
1952
                        // previously {sp:1} would add 1em margin to each side of a 0 width span
1953
                        // now Firefox doesn't seem to render 0px dimensions, so now we set the
1954
                        // 1em margin x 2 for the width
1955
                        .width((action.match(/px/i) ? margin + 'px' : (margin * 2) + 'em'))
1956
                        .addClass(kbcss.keySpacer)
1957
                        .appendTo($row);
1958
                }
1959
1960
                // add empty button
1961
                if (/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(action)) {
1962
                    margin = (/:/.test(action)) ? parseFloat(action
1963
                        .replace(/,/, '.')
1964
                        .match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
1965
                    ) : '';
1966
                    base
1967
                        .addKey('', ' ', true)
1968
                        .addClass(o.css.buttonDisabled + ' ' + o.css.buttonEmpty)
1969
                        .attr('aria-disabled', true)
1970
                        .width(margin ? (action.match('px') ? margin + 'px' : (margin * 2) + 'em') : '');
1971
                    continue;
1972
                }
1973
1974
                // meta keys
1975
                if (/^meta\d+\:?(\w+)?/i.test(action)) {
1976
                    base
1977
                        .addKey(action.split(':')[0], action)
1978
                        .addClass(kbcss.keyHasActive);
1979
                    continue;
1980
                }
1981
1982
                // switch needed for action keys with multiple names/shortcuts or
1983
                // default will catch all others
1984
                txt = action.split(':');
1985
                switch (txt[0].toLowerCase()) {
1986
1987
                case 'a':
1988
                case 'accept':
1989
                    base
1990
                        .addKey('accept', action)
1991
                        .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
1992
                    break;
1993
1994
                case 'alt':
1995
                case 'altgr':
1996
                    base
1997
                        .addKey('alt', action)
1998
                        .addClass(kbcss.keyHasActive);
1999
                    break;
2000
2001
                case 'b':
2002
                case 'bksp':
2003
                    base.addKey('bksp', action);
2004
                    break;
2005
2006
                case 'c':
2007
                case 'cancel':
2008
                    base
2009
                        .addKey('cancel', action)
2010
                        .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
2011
                    break;
2012
2013
                    // toggle combo/diacritic key
2014
                    /*jshint -W083 */
2015
                case 'combo':
2016
                    base
2017
                        .addKey('combo', action)
2018
                        .addClass(kbcss.keyHasActive)
2019
                        .attr('title', function (indx, title) {
2020
                            // add combo key state to title
2021
                            return title + ' ' + o.display[o.useCombos ? 'active' : 'disabled'];
2022
                        })
2023
                        .toggleClass(o.css.buttonActive, o.useCombos);
2024
                    break;
2025
2026
                    // Decimal - unique decimal point (num pad layout)
2027
                case 'dec':
2028
                    acceptedKeys.push((base.decimal) ? '.' : ',');
2029
                    base.addKey('dec', action);
2030
                    break;
2031
2032
                case 'e':
2033
                case 'enter':
2034
                    base
2035
                        .addKey('enter', action)
2036
                        .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
2037
                    break;
2038
2039
                case 'lock':
2040
                    base
2041
                        .addKey('lock', action)
2042
                        .addClass(kbcss.keyHasActive);
2043
                    break;
2044
2045
                case 's':
2046
                case 'shift':
2047
                    base
2048
                        .addKey('shift', action)
2049
                        .addClass(kbcss.keyHasActive);
2050
                    break;
2051
2052
                    // Change sign (for num pad layout)
2053
                case 'sign':
2054
                    acceptedKeys.push('-');
2055
                    base.addKey('sign', action);
2056
                    break;
2057
2058
                case 'space':
2059
                    acceptedKeys.push(' ');
2060
                    base.addKey('space', action);
2061
                    break;
2062
2063
                case 't':
2064
                case 'tab':
2065
                    base.addKey('tab', action);
2066
                    break;
2067
2068
                default:
2069
                    if ($keyboard.keyaction.hasOwnProperty(txt[0])) {
2070
                        base
2071
                            .addKey(txt[0], action)
2072
                            .toggleClass(o.css.buttonAction + ' ' + kbcss.keyAction, isAction);
2073
                    }
2074
2075
                }
2076
2077
            } else {
2078
2079
                // regular button (not an action key)
2080
                t = keys[key];
2081
                base.addKey(t, t, true);
2082
            }
2083
        }
2084
    };
2085
2086
    base.removeBindings = function (namespace) {
2087
        $(document).unbind(namespace);
2088
        if (base.el.ownerDocument !== document) {
2089
            $(base.el.ownerDocument).unbind(namespace);
2090
        }
2091
        $(window).unbind(namespace);
2092
        base.$el.unbind(namespace);
2093
    };
2094
2095
    base.removeKeyboard = function () {
2096
        base.$allKeys = null;
2097
        base.$decBtn = null;
2098
        // base.$preview === base.$el when o.usePreview is false - fixes #442
2099
        if (o.usePreview) {
2100
            base.$preview.removeData('keyboard');
2101
        }
2102
        base.preview = null;
2103
        base.$preview = null;
2104
        base.$previewCopy = null;
2105
        base.$keyboard.removeData('keyboard');
2106
        base.$keyboard.remove();
2107
        base.$keyboard = [];
2108
        base.isOpen = false;
2109
        base.isCurrent(false);
2110
    };
2111
2112
    base.destroy = function (callback) {
2113
        var index,
2114
            kbcss = $keyboard.css,
2115
            len = base.extensionNamespace.length,
2116
            tmp = [
2117
                kbcss.input,
2118
                kbcss.locked,
2119
                kbcss.placeholder,
2120
                kbcss.noKeyboard,
2121
                kbcss.alwaysOpen,
2122
                o.css.input,
2123
                kbcss.isCurrent
2124
            ].join(' ');
2125
        clearTimeout(base.timer);
2126
        clearTimeout(base.timer2);
2127
        if (base.$keyboard.length) {
2128
            base.removeKeyboard();
2129
        }
2130
        base.removeBindings(base.namespace);
2131
        base.removeBindings(base.namespace + 'callbacks');
2132
        for (index = 0; index < len; index++) {
2133
            base.removeBindings(base.extensionNamespace[index]);
2134
        }
2135
        base.el.active = false;
2136
2137
        base.$el
2138
            .removeClass(tmp)
2139
            .removeAttr('aria-haspopup')
2140
            .removeAttr('role')
2141
            .removeData('keyboard');
2142
        base = null;
2143
2144
        if (typeof callback === 'function') {
2145
            callback();
2146
        }
2147
    };
2148
2149
    // Run initializer
2150
    base.init();
2151
2152
    }; // end $.keyboard definition
2153
2154
    // event.which & ASCII values
2155
    $keyboard.keyCodes = {
2156
        backSpace: 8,
2157
        tab: 9,
2158
        enter: 13,
2159
        capsLock: 20,
2160
        escape: 27,
2161
        space: 32,
2162
        pageUp: 33,
2163
        pageDown: 34,
2164
        end: 35,
2165
        home: 36,
2166
        left: 37,
2167
        up: 38,
2168
        right: 39,
2169
        down: 40,
2170
        insert: 45,
2171
        delete: 46,
2172
        // event.which keyCodes (uppercase letters)
2173
        A: 65,
2174
        Z: 90,
2175
        V: 86,
2176
        C: 67,
2177
        X: 88,
2178
2179
        // ASCII lowercase a & z
2180
        a: 97,
2181
        z: 122
2182
    };
2183
2184
    $keyboard.css = {
2185
        // keyboard id suffix
2186
        idSuffix: '_keyboard',
2187
        // element class names
2188
        input: 'ui-keyboard-input',
2189
        inputClone: 'ui-keyboard-preview-clone',
2190
        wrapper: 'ui-keyboard-preview-wrapper',
2191
        preview: 'ui-keyboard-preview',
2192
        keyboard: 'ui-keyboard',
2193
        keySet: 'ui-keyboard-keyset',
2194
        keyButton: 'ui-keyboard-button',
2195
        keyWide: 'ui-keyboard-widekey',
2196
        keyPrefix: 'ui-keyboard-',
2197
        keyText: 'ui-keyboard-text', // span with button text
2198
        keyHasActive: 'ui-keyboard-hasactivestate',
2199
        keyAction: 'ui-keyboard-actionkey',
2200
        keySpacer: 'ui-keyboard-spacer', // empty keys
2201
        keyToggle: 'ui-keyboard-toggle',
2202
        keyDisabled: 'ui-keyboard-disabled',
2203
        // states
2204
        locked: 'ui-keyboard-lockedinput',
2205
        alwaysOpen: 'ui-keyboard-always-open',
2206
        noKeyboard: 'ui-keyboard-nokeyboard',
2207
        placeholder: 'ui-keyboard-placeholder',
2208
        hasFocus: 'ui-keyboard-has-focus',
2209
        isCurrent: 'ui-keyboard-input-current',
2210
        // validation & autoaccept
2211
        inputValid: 'ui-keyboard-valid-input',
2212
        inputInvalid: 'ui-keyboard-invalid-input',
2213
        inputAutoAccepted: 'ui-keyboard-autoaccepted',
2214
        endRow: 'ui-keyboard-button-endrow' // class added to <br>
2215
    };
2216
2217
    $keyboard.events = {
2218
        // keyboard events
2219
        kbChange: 'keyboardChange',
2220
        kbBeforeClose: 'beforeClose',
2221
        kbBeforeVisible: 'beforeVisible',
2222
        kbVisible: 'visible',
2223
        kbInit: 'initialized',
2224
        kbInactive: 'inactive',
2225
        kbHidden: 'hidden',
2226
        kbRepeater: 'repeater',
2227
        kbKeysetChange: 'keysetChange',
2228
        // input events
2229
        inputAccepted: 'accepted',
2230
        inputCanceled: 'canceled',
2231
        inputChange: 'change',
2232
        inputRestricted: 'restricted'
2233
    };
2234
2235
    // Action key function list
2236
    $keyboard.keyaction = {
2237
        accept: function (base) {
2238
            base.close(true); // same as base.accept();
2239
            return false; // return false prevents further processing
2240
        },
2241
        alt: function (base) {
2242
            base.altActive = !base.altActive;
2243
            base.showSet();
2244
        },
2245
        bksp: function (base) {
2246
            // the script looks for the '\b' string and initiates a backspace
2247
            base.insertText('\b');
2248
        },
2249
        cancel: function (base) {
2250
            base.close();
2251
            return false; // return false prevents further processing
2252
        },
2253
        clear: function (base) {
2254
            base.$preview.val('');
2255
            if (base.$decBtn.length) {
2256
                base.checkDecimal();
2257
            }
2258
        },
2259
        combo: function (base) {
2260
            var o = base.options,
2261
                c = !o.useCombos,
2262
                $combo = base.$keyboard.find('.' + $keyboard.css.keyPrefix + 'combo');
2263
            o.useCombos = c;
2264
            $combo
2265
                .toggleClass(o.css.buttonActive, c)
2266
                // update combo key state
2267
                .attr('title', $combo.attr('data-title') + ' (' + o.display[c ? 'active' : 'disabled'] + ')');
2268
            if (c) {
2269
                base.checkCombos();
2270
            }
2271
            return false;
2272
        },
2273
        dec: function (base) {
2274
            base.insertText((base.decimal) ? '.' : ',');
2275
        },
2276
        del: function (base) {
2277
            // the script looks for the '{d}' string and initiates a delete
2278
            base.insertText('{d}');
2279
        },
2280
        // resets to base keyset (deprecated because "default" is a reserved word)
2281
        'default': function (base) {
2282
            base.shiftActive = base.altActive = base.metaActive = false;
2283
            base.showSet();
2284
        },
2285
        // el is the pressed key (button) object; it is null when the real keyboard enter is pressed
2286
        enter: function (base, el, e) {
2287
            var tag = base.el.nodeName,
2288
                o = base.options;
2289
            // shift+enter in textareas
2290
            if (e.shiftKey) {
2291
                // textarea & input - enterMod + shift + enter = accept, then go to prev;
2292
                //  base.switchInput(goToNext, autoAccept)
2293
                // textarea & input - shift + enter = accept (no navigation)
2294
                return (o.enterNavigation) ? base.switchInput(!e[o.enterMod], true) : base.close(true);
2295
            }
2296
            // input only - enterMod + enter to navigate
2297
            if (o.enterNavigation && (tag !== 'TEXTAREA' || e[o.enterMod])) {
2298
                return base.switchInput(!e[o.enterMod], o.autoAccept ? 'true' : false);
2299
            }
2300
            // pressing virtual enter button inside of a textarea - add a carriage return
2301
            // e.target is span when clicking on text and button at other times
2302
            if (tag === 'TEXTAREA' && $(e.target).closest('button').length) {
2303
                // IE8 fix (space + \n) - fixes #71 thanks Blookie!
2304
                base.insertText(($keyboard.msie ? ' ' : '') + '\n');
2305
            }
2306
        },
2307
        // caps lock key
2308
        lock: function (base) {
2309
            base.last.keyset[0] = base.shiftActive = base.capsLock = !base.capsLock;
2310
            base.showSet();
2311
        },
2312
        left: function (base) {
2313
            var p = $keyboard.caret(base.$preview);
2314
            if (p.start - 1 >= 0) {
2315
                // move both start and end of caret (prevents text selection) & save caret position
2316
                base.last.start = base.last.end = p.start - 1;
2317
                $keyboard.caret(base.$preview, base.last);
2318
                base.setScroll();
2319
            }
2320
        },
2321
        meta: function (base, el) {
2322
            var $el = $(el);
2323
            base.metaActive = !$el.hasClass(base.options.css.buttonActive);
2324
            base.showSet($el.attr('data-name'));
2325
        },
2326
        next: function (base) {
2327
            base.switchInput(true, base.options.autoAccept);
2328
            return false;
2329
        },
2330
        // same as 'default' - resets to base keyset
2331
        normal: function (base) {
2332
            base.shiftActive = base.altActive = base.metaActive = false;
2333
            base.showSet();
2334
        },
2335
        prev: function (base) {
2336
            base.switchInput(false, base.options.autoAccept);
2337
            return false;
2338
        },
2339
        right: function (base) {
2340
            var p = $keyboard.caret(base.$preview);
2341
            if (p.start + 1 <= base.$preview.val().length) {
2342
                // move both start and end of caret (prevents text selection) && save caret position
2343
                base.last.start = base.last.end = p.start + 1;
2344
                $keyboard.caret(base.$preview, base.last);
2345
                base.setScroll();
2346
            }
2347
        },
2348
        shift: function (base) {
2349
            base.last.keyset[0] = base.shiftActive = !base.shiftActive;
2350
            base.showSet();
2351
        },
2352
        sign: function (base) {
2353
            if (/^\-?\d*\.?\d*$/.test(base.$preview.val())) {
2354
                base.$preview.val((base.$preview.val() * -1));
2355
            }
2356
        },
2357
        space: function (base) {
2358
            base.insertText(' ');
2359
        },
2360
        tab: function (base) {
2361
            var tag = base.el.nodeName,
2362
                o = base.options;
2363
            if (tag === 'INPUT') {
2364
                if (o.tabNavigation) {
2365
                    return base.switchInput(!base.shiftActive, true);
2366
                } else {
2367
                    // ignore tab key in input
2368
                    return false;
2369
                }
2370
            }
2371
            base.insertText('\t');
2372
        },
2373
        toggle: function (base) {
2374
            base.enabled = !base.enabled;
2375
            base.toggle();
2376
        },
2377
        // *** Special action keys: NBSP & zero-width characters ***
2378
        // Non-breaking space
2379
        NBSP: '\u00a0',
2380
        // zero width space
2381
        ZWSP: '\u200b',
2382
        // Zero width non-joiner
2383
        ZWNJ: '\u200c',
2384
        // Zero width joiner
2385
        ZWJ: '\u200d',
2386
        // Left-to-right Mark
2387
        LRM: '\u200e',
2388
        // Right-to-left Mark
2389
        RLM: '\u200f'
2390
    };
2391
2392
    // Default keyboard layouts
2393
    $keyboard.builtLayouts = {};
2394
    $keyboard.layouts = {
2395
        'alpha': {
2396
            'normal': [
2397
                '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2398
                '{tab} a b c d e f g h i j [ ] \\',
2399
                'k l m n o p q r s ; \' {enter}',
2400
                '{shift} t u v w x y z , . / {shift}',
2401
                '{accept} {space} {cancel}'
2402
            ],
2403
            'shift': [
2404
                '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2405
                '{tab} A B C D E F G H I J { } |',
2406
                'K L M N O P Q R S : " {enter}',
2407
                '{shift} T U V W X Y Z < > ? {shift}',
2408
                '{accept} {space} {cancel}'
2409
            ]
2410
        },
2411
        'qwerty': {
2412
            'normal': [
2413
                '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2414
                '{tab} q w e r t y u i o p [ ] \\',
2415
                'a s d f g h j k l ; \' {enter}',
2416
                '{shift} z x c v b n m , . / {shift}',
2417
                '{accept} {space} {cancel}'
2418
            ],
2419
            'shift': [
2420
                '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2421
                '{tab} Q W E R T Y U I O P { } |',
2422
                'A S D F G H J K L : " {enter}',
2423
                '{shift} Z X C V B N M < > ? {shift}',
2424
                '{accept} {space} {cancel}'
2425
            ]
2426
        },
2427
        'international': {
2428
            'normal': [
2429
                '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2430
                '{tab} q w e r t y u i o p [ ] \\',
2431
                'a s d f g h j k l ; \' {enter}',
2432
                '{shift} z x c v b n m , . / {shift}',
2433
                '{accept} {alt} {space} {alt} {cancel}'
2434
            ],
2435
            'shift': [
2436
                '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2437
                '{tab} Q W E R T Y U I O P { } |',
2438
                'A S D F G H J K L : " {enter}',
2439
                '{shift} Z X C V B N M < > ? {shift}',
2440
                '{accept} {alt} {space} {alt} {cancel}'
2441
            ],
2442
            'alt': [
2443
                '~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}',
2444
                '{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac',
2445
                '\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}',
2446
                '{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}',
2447
                '{accept} {alt} {space} {alt} {cancel}'
2448
            ],
2449
            'alt-shift': [
2450
                '~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}',
2451
                '{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6',
2452
                '\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}',
2453
                '{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}',
2454
                '{accept} {alt} {space} {alt} {cancel}'
2455
            ]
2456
        },
2457
        'colemak': {
2458
            'normal': [
2459
                '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2460
                '{tab} q w f p g j l u y ; [ ] \\',
2461
                '{bksp} a r s t d h n e i o \' {enter}',
2462
                '{shift} z x c v b k m , . / {shift}',
2463
                '{accept} {space} {cancel}'
2464
            ],
2465
            'shift': [
2466
                '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
2467
                '{tab} Q W F P G J L U Y : { } |',
2468
                '{bksp} A R S T D H N E I O " {enter}',
2469
                '{shift} Z X C V B K M < > ? {shift}',
2470
                '{accept} {space} {cancel}'
2471
            ]
2472
        },
2473
        'dvorak': {
2474
            'normal': [
2475
                '` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}',
2476
                '{tab} \' , . p y f g c r l / = \\',
2477
                'a o e u i d h t n s - {enter}',
2478
                '{shift} ; q j k x b m w v z {shift}',
2479
                '{accept} {space} {cancel}'
2480
            ],
2481
            'shift': [
2482
                '~ ! @ # $ % ^ & * ( ) { } {bksp}',
2483
                '{tab} " < > P Y F G C R L ? + |',
2484
                'A O E U I D H T N S _ {enter}',
2485
                '{shift} : Q J K X B M W V Z {shift}',
2486
                '{accept} {space} {cancel}'
2487
            ]
2488
        },
2489
        'num': {
2490
            'normal': [
2491
                '= ( ) {b}',
2492
                '{clear} / * -',
2493
                '7 8 9 +',
2494
                '4 5 6 {sign}',
2495
                '1 2 3 %',
2496
                '0 {dec} {a} {c}'
2497
            ]
2498
        }
2499
    };
2500
2501
    $keyboard.language = {
2502
        en: {
2503
            display: {
2504
                // check mark - same action as accept
2505
                'a': '\u2714:Accept (Shift+Enter)',
2506
                'accept': 'Accept:Accept (Shift+Enter)',
2507
                // other alternatives \u2311
2508
                'alt': 'Alt:\u2325 AltGr',
2509
                // Left arrow (same as &larr;)
2510
                'b': '\u232b:Backspace',
2511
                'bksp': 'Bksp:Backspace',
2512
                // big X, close - same action as cancel
2513
                'c': '\u2716:Cancel (Esc)',
2514
                'cancel': 'Cancel:Cancel (Esc)',
2515
                // clear num pad
2516
                'clear': 'C:Clear',
2517
                'combo': '\u00f6:Toggle Combo Keys',
2518
                // decimal point for num pad (optional), change '.' to ',' for European format
2519
                'dec': '.:Decimal',
2520
                // down, then left arrow - enter symbol
2521
                'e': '\u23ce:Enter',
2522
                'empty': '\u00a0',
2523
                'enter': 'Enter:Enter \u23ce',
2524
                // left arrow (move caret)
2525
                'left': '\u2190',
2526
                // caps lock
2527
                'lock': 'Lock:\u21ea Caps Lock',
2528
                'next': 'Next \u21e8',
2529
                'prev': '\u21e6 Prev',
2530
                // right arrow (move caret)
2531
                'right': '\u2192',
2532
                // thick hollow up arrow
2533
                's': '\u21e7:Shift',
2534
                'shift': 'Shift:Shift',
2535
                // +/- sign for num pad
2536
                'sign': '\u00b1:Change Sign',
2537
                'space': '\u00a0:Space',
2538
                // right arrow to bar (used since this virtual keyboard works with one directional tabs)
2539
                't': '\u21e5:Tab',
2540
                // \u21b9 is the true tab symbol (left & right arrows)
2541
                'tab': '\u21e5 Tab:Tab',
2542
                // replaced by an image
2543
                'toggle': ' ',
2544
2545
                // added to titles of keys
2546
                // accept key status when acceptValid:true
2547
                'valid': 'valid',
2548
                'invalid': 'invalid',
2549
                // combo key states
2550
                'active': 'active',
2551
                'disabled': 'disabled'
2552
            },
2553
2554
            // Message added to the key title while hovering, if the mousewheel plugin exists
2555
            wheelMessage: 'Use mousewheel to see other keys',
2556
2557
            comboRegex: /([`\'~\^\"ao])([a-z])/mig,
2558
            combos: {
2559
                // grave
2560
                '`': { a: '\u00e0', A: '\u00c0', e: '\u00e8', E: '\u00c8', i: '\u00ec', I: '\u00cc', o: '\u00f2',
2561
                        O: '\u00d2', u: '\u00f9', U: '\u00d9', y: '\u1ef3', Y: '\u1ef2' },
2562
                // acute & cedilla
2563
                "'": { a: '\u00e1', A: '\u00c1', e: '\u00e9', E: '\u00c9', i: '\u00ed', I: '\u00cd', o: '\u00f3',
2564
                        O: '\u00d3', u: '\u00fa', U: '\u00da', y: '\u00fd', Y: '\u00dd' },
2565
                // umlaut/trema
2566
                '"': { a: '\u00e4', A: '\u00c4', e: '\u00eb', E: '\u00cb', i: '\u00ef', I: '\u00cf', o: '\u00f6',
2567
                        O: '\u00d6', u: '\u00fc', U: '\u00dc', y: '\u00ff', Y: '\u0178' },
2568
                // circumflex
2569
                '^': { a: '\u00e2', A: '\u00c2', e: '\u00ea', E: '\u00ca', i: '\u00ee', I: '\u00ce', o: '\u00f4',
2570
                        O: '\u00d4', u: '\u00fb', U: '\u00db', y: '\u0177', Y: '\u0176' },
2571
                // tilde
2572
                '~': { a: '\u00e3', A: '\u00c3', e: '\u1ebd', E: '\u1ebc', i: '\u0129', I: '\u0128', o: '\u00f5',
2573
                        O: '\u00d5', u: '\u0169', U: '\u0168', y: '\u1ef9', Y: '\u1ef8', n: '\u00f1', N: '\u00d1' }
2574
            }
2575
        }
2576
    };
2577
2578
    $keyboard.defaultOptions = {
2579
        // set this to ISO 639-1 language code to override language set by the layout
2580
        // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
2581
        // language defaults to 'en' if not found
2582
        language: null,
2583
        rtl: false,
2584
2585
        // *** choose layout & positioning ***
2586
        layout: 'qwerty',
2587
        customLayout: null,
2588
2589
        position: {
2590
            // optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
2591
            of: null,
2592
            my: 'center top',
2593
            at: 'center top',
2594
            // used when 'usePreview' is false (centers the keyboard at the bottom of the input/textarea)
2595
            at2: 'center bottom'
2596
        },
2597
2598
        // allow jQuery position utility to reposition the keyboard on window resize
2599
        reposition: true,
2600
2601
        // preview added above keyboard if true, original input/textarea used if false
2602
        usePreview: true,
2603
2604
        // if true, the keyboard will always be visible
2605
        alwaysOpen: false,
2606
2607
        // give the preview initial focus when the keyboard becomes visible
2608
        initialFocus: true,
2609
2610
        // avoid changing the focus (hardware keyboard probably won't work)
2611
        noFocus: false,
2612
2613
        // if true, keyboard will remain open even if the input loses focus, but closes on escape
2614
        // or when another keyboard opens.
2615
        stayOpen: false,
2616
2617
        // Prevents the keyboard from closing when the user clicks or presses outside the keyboard
2618
        // the `autoAccept` option must also be set to true when this option is true or changes are lost
2619
        userClosed: false,
2620
2621
        // if true, keyboard will not close if you press escape.
2622
        ignoreEsc: false,
2623
2624
        // if true, keyboard will only closed on click event instead of mousedown and touchstart
2625
        closeByClickEvent: false,
2626
2627
        css: {
2628
            // input & preview
2629
            input: 'ui-widget-content ui-corner-all',
2630
            // keyboard container
2631
            container: 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
2632
            // keyboard container extra class (same as container, but separate)
2633
            popup: '',
2634
            // default state
2635
            buttonDefault: 'ui-state-default ui-corner-all',
2636
            // hovered button
2637
            buttonHover: 'ui-state-hover',
2638
            // Action keys (e.g. Accept, Cancel, Tab, etc); this replaces 'actionClass' option
2639
            buttonAction: 'ui-state-active',
2640
            // Active keys (e.g. shift down, meta keyset active, combo keys active)
2641
            buttonActive: 'ui-state-active',
2642
            // used when disabling the decimal button {dec} when a decimal exists in the input area
2643
            buttonDisabled: 'ui-state-disabled',
2644
            buttonEmpty: 'ui-keyboard-empty'
2645
        },
2646
2647
        // *** Useability ***
2648
        // Auto-accept content when clicking outside the keyboard (popup will close)
2649
        autoAccept: false,
2650
        // Auto-accept content even if the user presses escape (only works if `autoAccept` is `true`)
2651
        autoAcceptOnEsc: false,
2652
2653
        // Prevents direct input in the preview window when true
2654
        lockInput: false,
2655
2656
        // Prevent keys not in the displayed keyboard from being typed in
2657
        restrictInput: false,
2658
        // Additional allowed characters while restrictInput is true
2659
        restrictInclude: '', // e.g. 'a b foo \ud83d\ude38'
2660
2661
        // Check input against validate function, if valid the accept button gets a class name of
2662
        // 'ui-keyboard-valid-input'. If invalid, the accept button gets a class name of
2663
        // 'ui-keyboard-invalid-input'
2664
        acceptValid: false,
2665
        // Auto-accept when input is valid; requires `acceptValid` set `true` & validate callback
2666
        autoAcceptOnValid: false,
2667
2668
        // if acceptValid is true & the validate function returns a false, this option will cancel
2669
        // a keyboard close only after the accept button is pressed
2670
        cancelClose: true,
2671
2672
        // tab to go to next, shift-tab for previous (default behavior)
2673
        tabNavigation: false,
2674
2675
        // enter for next input; shift+enter accepts content & goes to next
2676
        // shift + 'enterMod' + enter ('enterMod' is the alt as set below) will accept content and go
2677
        // to previous in a textarea
2678
        enterNavigation: false,
2679
        // mod key options: 'ctrlKey', 'shiftKey', 'altKey', 'metaKey' (MAC only)
2680
        enterMod: 'altKey', // alt-enter to go to previous; shift-alt-enter to accept & go to previous
2681
2682
        // if true, the next button will stop on the last keyboard input/textarea; prev button stops at first
2683
        // if false, the next button will wrap to target the first input/textarea; prev will go to the last
2684
        stopAtEnd: true,
2685
2686
        // Set this to append the keyboard after the input/textarea (appended to the input/textarea parent).
2687
        // This option works best when the input container doesn't have a set width & when the 'tabNavigation'
2688
        // option is true.
2689
        appendLocally: false,
2690
        // When appendLocally is false, the keyboard will be appended to this object
2691
        appendTo: 'body',
2692
2693
        // If false, the shift key will remain active until the next key is (mouse) clicked on; if true it will
2694
        // stay active until pressed again
2695
        stickyShift: true,
2696
2697
        // Prevent pasting content into the area
2698
        preventPaste: false,
2699
2700
        // caret placed at the end of any text when keyboard becomes visible
2701
        caretToEnd: false,
2702
2703
        // caret stays this many pixels from the edge of the input while scrolling left/right;
2704
        // use "c" or "center" to center the caret while scrolling
2705
        scrollAdjustment: 10,
2706
2707
        // Set the max number of characters allowed in the input, setting it to false disables this option
2708
        maxLength: false,
2709
        // allow inserting characters @ caret when maxLength is set
2710
        maxInsert: true,
2711
2712
        // Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will
2713
        // start repeating
2714
        repeatDelay: 500,
2715
2716
        // Mouse repeat rate - after the repeatDelay, this is the rate (characters per second) at which the
2717
        // key is repeated Added to simulate holding down a real keyboard key and having it repeat. I haven't
2718
        // calculated the upper limit of this rate, but it is limited to how fast the javascript can process
2719
        // the keys. And for me, in Firefox, it's around 20.
2720
        repeatRate: 20,
2721
2722
        // resets the keyboard to the default keyset when visible
2723
        resetDefault: true,
2724
2725
        // Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
2726
        openOn: 'focus',
2727
2728
        // Event (namepaced) for when the character is added to the input (clicking on the keyboard)
2729
        keyBinding: 'mousedown touchstart',
2730
2731
        // enable/disable mousewheel functionality
2732
        // enabling still depends on the mousewheel plugin
2733
        useWheel: true,
2734
2735
        // combos (emulate dead keys : http://en.wikipedia.org/wiki/Keyboard_layout#US-International)
2736
        // if user inputs `a the script converts it to à, ^o becomes ô, etc.
2737
        useCombos: true,
2738
2739
        /*
2740
            // *** Methods ***
2741
            // commenting these out to reduce the size of the minified version
2742
            // Callbacks - attach a function to any of these callbacks as desired
2743
            initialized   : function(e, keyboard, el) {},
2744
            beforeVisible : function(e, keyboard, el) {},
2745
            visible       : function(e, keyboard, el) {},
2746
            beforeInsert  : function(e, keyboard, el, textToAdd) { return textToAdd; },
2747
            change        : function(e, keyboard, el) {},
2748
            beforeClose   : function(e, keyboard, el, accepted) {},
2749
            accepted      : function(e, keyboard, el) {},
2750
            canceled      : function(e, keyboard, el) {},
2751
            restricted    : function(e, keyboard, el) {},
2752
            hidden        : function(e, keyboard, el) {},
2753
            // called instead of base.switchInput
2754
            switchInput   : function(keyboard, goToNext, isAccepted) {},
2755
            // used if you want to create a custom layout or modify the built-in keyboard
2756
            create        : function(keyboard) { return keyboard.buildKeyboard(); },
2757

2758
            // build key callback
2759
            buildKey : function( keyboard, data ) {
2760
                / *
2761
                data = {
2762
                // READ ONLY
2763
                isAction : [boolean] true if key is an action key
2764
                name     : [string]  key class name suffix ( prefix = 'ui-keyboard-' );
2765
                                     may include decimal ascii value of character
2766
                value    : [string]  text inserted (non-action keys)
2767
                title    : [string]  title attribute of key
2768
                action   : [string]  keyaction name
2769
                html     : [string]  HTML of the key; it includes a <span> wrapping the text
2770
                // use to modify key HTML
2771
                $key     : [object]  jQuery selector of key which is already appended to keyboard
2772
                }
2773
                * /
2774
                return data;
2775
            },
2776
        */
2777
2778
        // this callback is called, if the acceptValid is true, and just before the 'beforeClose' to check
2779
        // the value if the value is valid, return true and the keyboard will continue as it should
2780
        // (close if not always open, etc). If the value is not valid, return false and clear the keyboard
2781
        // value ( like this "keyboard.$preview.val('');" ), if desired. The validate function is called after
2782
        // each input, the 'isClosing' value will be false; when the accept button is clicked,
2783
        // 'isClosing' is true
2784
        validate: function (keyboard, value, isClosing) {
2785
            return true;
2786
        }
2787
2788
    };
2789
2790
    // for checking combos
2791
    $keyboard.comboRegex = /([`\'~\^\"ao])([a-z])/mig;
2792
2793
    // store current keyboard element; used by base.isCurrent()
2794
    $keyboard.currentKeyboard = '';
2795
2796
    $('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]>' +
2797
            '<script>jQuery("body").addClass("ie");</script><![endif]-->')
2798
        .appendTo('body')
2799
        .remove();
2800
    $keyboard.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
2801
    $keyboard.allie = $('body').hasClass('ie');
2802
2803
    $keyboard.watermark = (typeof (document.createElement('input').placeholder) !== 'undefined');
2804
2805
    $keyboard.checkCaretSupport = function () {
2806
        if (typeof $keyboard.checkCaret !== 'boolean') {
2807
            // Check if caret position is saved when input is hidden or loses focus
2808
            // (*cough* all versions of IE and I think Opera has/had an issue as well
2809
            var $temp = $('<div style="height:0px;width:0px;overflow:hidden;position:fixed;top:0;left:-100px;">' +
2810
                '<input type="text" value="testing"/></div>').prependTo('body'); // stop page scrolling
2811
            $keyboard.caret($temp.find('input'), 3, 3);
2812
            // Also save caret position of the input if it is locked
2813
            $keyboard.checkCaret = $keyboard.caret($temp.find('input').hide().show()).start !== 3;
2814
            $temp.remove();
2815
        }
2816
        return $keyboard.checkCaret;
2817
    };
2818
2819
    $keyboard.caret = function ($el, param1, param2) {
2820
        if (!$el || !$el.length || $el.is(':hidden') || $el.css('visibility') === 'hidden') {
2821
            return {};
2822
        }
2823
        var start, end, txt, pos,
2824
            kb = $el.data('keyboard'),
2825
            noFocus = kb && kb.options.noFocus;
2826
        if (!noFocus) {
2827
            $el.focus();
2828
        }
2829
        // set caret position
2830
        if (typeof param1 !== 'undefined') {
2831
            // allow setting caret using ( $el, { start: x, end: y } )
2832
            if (typeof param1 === 'object' && 'start' in param1 && 'end' in param1) {
2833
                start = param1.start;
2834
                end = param1.end;
2835
            } else if (typeof param2 === 'undefined') {
2836
                param2 = param1; // set caret using start position
2837
            }
2838
            // set caret using ( $el, start, end );
2839
            if (typeof param1 === 'number' && typeof param2 === 'number') {
2840
                start = param1;
2841
                end = param2;
2842
            } else if (param1 === 'start') {
2843
                start = end = 0;
2844
            } else if (typeof param1 === 'string') {
2845
                // unknown string setting, move caret to end
2846
                start = end = $el.val().length;
2847
            }
2848
2849
            // *** SET CARET POSITION ***
2850
            // modify the line below to adapt to other caret plugins
2851
            return $el.caret(start, end, noFocus);
2852
        }
2853
        // *** GET CARET POSITION ***
2854
        // modify the line below to adapt to other caret plugins
2855
        pos = $el.caret();
2856
        start = pos.start;
2857
        end = pos.end;
2858
2859
        // *** utilities ***
2860
        txt = ($el[0].value || $el.text() || '');
2861
        return {
2862
            start: start,
2863
            end: end,
2864
            // return selected text
2865
            text: txt.substring(start, end),
2866
            // return a replace selected string method
2867
            replaceStr: function (str) {
2868
                return txt.substring(0, start) + str + txt.substring(end, txt.length);
2869
            }
2870
        };
2871
    };
2872
2873
    $.fn.keyboard = function (options) {
2874
        return this.each(function () {
2875
            if (!$(this).data('keyboard')) {
2876
                /*jshint nonew:false */
2877
                (new $.keyboard(this, options));
2878
            }
2879
        });
2880
    };
2881
2882
    $.fn.getkeyboard = function () {
2883
        return this.data('keyboard');
2884
    };
2885
2886
    /* Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
2887
     * Licensed under the MIT License:
2888
     * http://www.opensource.org/licenses/mit-license.php
2889
     * Highly modified from the original
2890
     */
2891
2892
    $.fn.caret = function (start, end, noFocus) {
2893
        if (typeof this[0] === 'undefined' || this.is(':hidden') || this.css('visibility') === 'hidden') {
2894
            return this;
2895
        }
2896
        var selRange, range, stored_range, txt, val,
2897
            selection = document.selection,
2898
            $el = this,
2899
            el = $el[0],
2900
            sTop = el.scrollTop,
2901
            ss = false,
2902
            supportCaret = true;
2903
        try {
2904
            ss = 'selectionStart' in el;
2905
        } catch (err) {
2906
            supportCaret = false;
2907
        }
2908
        if (supportCaret && typeof start !== 'undefined') {
2909
            if (!/(email|number)/i.test(el.type)) {
2910
                if (ss) {
2911
                    el.selectionStart = start;
2912
                    el.selectionEnd = end;
2913
                } else {
2914
                    selRange = el.createTextRange();
2915
                    selRange.collapse(true);
2916
                    selRange.moveStart('character', start);
2917
                    selRange.moveEnd('character', end - start);
2918
                    selRange.select();
2919
                }
2920
            }
2921
            // must be visible or IE8 crashes; IE9 in compatibility mode works fine - issue #56
2922
            if (!noFocus && ($el.is(':visible') || $el.css('visibility') !== 'hidden')) {
2923
                el.focus();
2924
            }
2925
            el.scrollTop = sTop;
2926
            return this;
2927
        } else {
2928
            if (/(email|number)/i.test(el.type)) {
2929
                // fix suggested by raduanastase (https://github.com/Mottie/Keyboard/issues/105#issuecomment-40456535)
2930
                start = end = $el.val().length;
2931
            } else if (ss) {
2932
                start = el.selectionStart;
2933
                end = el.selectionEnd;
2934
            } else if (selection) {
2935
                if (el.nodeName === 'TEXTAREA') {
2936
                    val = $el.val();
2937
                    range = selection.createRange();
2938
                    stored_range = range.duplicate();
2939
                    stored_range.moveToElementText(el);
2940
                    stored_range.setEndPoint('EndToEnd', range);
2941
                    // thanks to the awesome comments in the rangy plugin
2942
                    start = stored_range.text.replace(/\r/g, '\n').length;
2943
                    end = start + range.text.replace(/\r/g, '\n').length;
2944
                } else {
2945
                    val = $el.val().replace(/\r/g, '\n');
2946
                    range = selection.createRange().duplicate();
2947
                    range.moveEnd('character', val.length);
2948
                    start = (range.text === '' ? val.length : val.lastIndexOf(range.text));
2949
                    range = selection.createRange().duplicate();
2950
                    range.moveStart('character', -val.length);
2951
                    end = range.text.length;
2952
                }
2953
            } else {
2954
                // caret positioning not supported
2955
                start = end = (el.value || '').length;
2956
            }
2957
            txt = (el.value || '');
2958
            return {
2959
                start: start,
2960
                end: end,
2961
                text: txt.substring(start, end),
2962
                replace: function (str) {
2963
                    return txt.substring(0, start) + str + txt.substring(end, txt.length);
2964
                }
2965
            };
2966
        }
2967
    };
2968
2969
    return $keyboard;
2970
2971
}));
2972
2973
2974
function getKeyboardLayout(element) {
2975
    var elementType = $(element).attr('type');
2976
    var layout = 'qwerty';
2977
    var customLayout = '';
2978
    var position = { my : 'center top', at : 'center bottom' };
2979
2980
    switch(elementType) {
2981
        case "number":
2982
            layout = 'num';
2983
            break;
2984
        case "time":
2985
            layout = 'num';
2986
            break;
2987
        case "date":
2988
            layout = 'num';
2989
            break;
2990
        case "tel":
2991
            layout = 'num';
2992
            break;
2993
        case "email":
2994
            layout = "custom";
2995
            customLayout = {
2996
                    'normal': [
2997
                        '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
2998
                        '{tab} q w e r t y u i o p [ ] \\',
2999
                        'a s d f g h j k l ; \' {enter}',
3000
                        '{shift} z x c v b n m , . / {shift}',
3001
                        '{accept} {space} @ .com .de .info .org'
3002
                    ],
3003
                    'shift': [
3004
                        '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
3005
                        '{tab} Q W E R T Y U I O P { } |',
3006
                        'A S D F G H J K L : " {enter}',
3007
                        '{shift} Z X C V B N M < > ? {shift}',
3008
                        '{accept} {space} {left} {right}'
3009
                    ]
3010
                };
3011
            break;
3012
        case "url":
3013
            layout = "custom";
3014
            customLayout = {
3015
                    'normal': [
3016
                        '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
3017
                        '{tab} q w e r t y u i o p [ ] \\',
3018
                        'a s d f g h j k l ; \' {enter}',
3019
                        '{shift} z x c v b n m , . / {shift}',
3020
                        '{accept} {space} www. .com .de .info .org'
3021
                    ],
3022
                    'shift': [
3023
                        '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
3024
                        '{tab} Q W E R T Y U I O P { } |',
3025
                        'A S D F G H J K L : " {enter}',
3026
                        '{shift} Z X C V B N M < > ? {shift}',
3027
                        '{accept} {space} {left} {right}'
3028
                    ]
3029
                };
3030
            break;
3031
        default:
3032
            layout = 'qwerty';
3033
    }
3034
3035
3036
    $(element).keyboard({
3037
        language: 'de',
3038
        layout: layout,
3039
        customLayout: customLayout,
3040
        position: position,
3041
        reposition: false,
3042
        keyBinding: 'mousedown touchstart click',
3043
    });
3044
}