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