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