]>
git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/jquery.keyboard.js
1 /*! jQuery UI Virtual Keyboard v1.26.22 *//*
2 Author: Jeremy Satterfield
3 Maintained: Rob Garrison (Mottie on github)
4 Licensed under the MIT License
6 An on-screen virtual keyboard embedded within the browser window which
7 will popup when a specified entry field is focused. The user can then
8 type and preview their input before Accepting or Canceling.
10 This plugin adds default class names to match jQuery UI theme styling.
11 Bootstrap & custom themes may also be applied - See
12 https://github.com/Mottie/Keyboard#themes
16 Caret plugin (included)
18 jQuery UI (position utility only) & CSS theme
22 Please refer to https://github.com/Mottie/Keyboard/wiki
24 -----------------------------------------
25 Caret code modified from jquery.caret.1.02.js
26 Licensed under the MIT License:
27 http://www.opensource.org/licenses/mit-license.php
28 -----------------------------------------
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'));
42 var $keyboard
= $.keyboard = function (el
, options
) {
45 base
.version
= '1.26.22';
47 // Access to jQuery and DOM versions of element
51 // Add a reverse reference to the DOM object
52 base
.$el
.data('keyboard', base
);
54 base
.init = function () {
55 base
.initialized
= false;
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;
65 base
.options
= o
= $.extend(true, {}, $keyboard
.defaultOptions
, options
);
67 o
.position
= position
;
68 options
.position
= position
;
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'];
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
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;
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
= [
114 // keyboard enabled; set to false on destroy
117 base
.checkCaret
= (o
.lockInput
|| $keyboard
.checkCaretSupport());
127 keyset: [false, false, false], // [shift, alt, meta]
132 // used when building the keyboard - [keyset element, row, index]
133 base
.temp
= ['', 0, 0];
138 kbevents
.kbBeforeVisible
,
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
]);
152 // Close with esc key & clicking outside
158 if (base
.el
.ownerDocument
!== document
) {
159 tmp
= tmp
.add(base
.el
.ownerDocument
);
162 var bindings
= 'keyup checkkeyboard mousedown touchstart ';
163 if (o
.closeByClickEvent
) {
164 bindings
+= 'click ';
166 // debounce bindings... see #542
167 tmp
.bind(bindings
.split(' ').join(base
.namespace + ' '), function(e
) {
168 clearTimeout(base
.timer3
);
169 base
.timer3
= setTimeout(function() {
174 // Display keyboard on focus
176 .addClass(kbcss
.input
+ ' ' + o
.css
.input
)
178 'aria-haspopup': 'true',
182 // set lockInput if the element is readonly; or make the element readonly if lockInput is set
183 if (o
.lockInput
|| base
.el
.readOnly
) {
186 .addClass(kbcss
.locked
)
188 'readonly': 'readonly'
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
);
200 // Add placeholder if not supported by the browser
201 if (!base
.watermark
&& base
.$el
.val() === '' && base
.inPlaceholder
!== '' &&
202 base
.$el
.attr('placeholder') !== '') {
204 .addClass(kbcss
.placeholder
) // css watermark style (darker text)
205 .val(base
.inPlaceholder
);
208 base
.$el
.trigger(kbevents
.kbInit
, [base
, base
.el
]);
210 // initialized with keyboard open
214 base
.initialized
= true;
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
225 .toggleClass($keyboard
.css
.keyDisabled
, locked
)
226 .find('.' + $keyboard
.css
.keyButton
)
228 .prop('disabled', locked
)
229 .attr('aria-disabled', locked
);
230 $toggle
.toggleClass($keyboard
.css
.keyDisabled
, locked
);
232 if (locked
&& base
.typing_options
) {
233 base
.typing_options
.text
= '';
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);
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
);
253 base
.$el
.addClass(kbcss
.isCurrent
);
254 base
.$keyboard
.addClass(kbcss
.hasFocus
);
255 base
.isCurrent(true);
259 base
.isCurrent = function (set) {
260 var cur
= $keyboard
.currentKeyboard
|| false;
262 cur
= $keyboard
.currentKeyboard
= base
.el
;
263 } else if (set === false && cur
=== base
.el
) {
264 cur
= $keyboard
.currentKeyboard
= '';
266 return cur
=== base
.el
;
269 base
.hasKeyboard = function () {
270 return base
.$keyboard
&& base
.$keyboard
.length
> 0;
273 base
.isVisible = function () {
274 return base
.hasKeyboard() ? base
.$keyboard
.is(':visible') : false;
277 base
.focusOn = function () {
278 if (!base
&& base
.el
.active
) {
279 // keyboard was destroyed
282 if (!base
.isVisible()) {
283 clearTimeout(base
.timer
);
286 // keyboard already open, make it the current keyboard
291 // add redraw method to make API more clear
292 base
.redraw = function (layout
) {
294 // allow updating the layout by calling redraw
295 base
.options
.layout
= layout
;
297 // update keyboard after a layout change
298 if (base
.$keyboard
.length
) {
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
);
304 base
.removeKeyboard();
305 base
.shiftActive
= base
.altActive
= base
.metaActive
= false;
307 base
.isOpen
= o
.alwaysOpen
;
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);
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
);
330 base
.$el
.removeClass(kbcss
.noKeyboard
);
333 // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
335 base
.$el
.unbind($.trim((o
.openOn
+ ' ').split(/\s+/).join(base
.namespace + ' ')));
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]))) {
345 if (!base
.watermark
&& base
.el
.value
=== base
.inPlaceholder
) {
347 .removeClass(kbcss
.placeholder
)
350 // save starting content, in case we cancel
351 base
.originalContent
= base
.$el
.val();
352 base
.$preview
.val(base
.originalContent
);
354 // disable/enable accept button
359 if (o
.resetDefault
) {
360 base
.shiftActive
= base
.altActive
= base
.metaActive
= false;
364 // beforeVisible event
365 if (!base
.isVisible()) {
366 base
.$el
.trigger($keyboard
.events
.kbBeforeVisible
, [base
, base
.el
]);
371 ( !o
.initialFocus
&& base
.$el
.hasClass($keyboard
.css
.initialFocus
) )
375 // update keyboard - enabled or disabled?
379 base
.$keyboard
.show();
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();
388 base
.$preview
.width(base
.width
);
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;
402 base
.saveCaret(base
.originalContent
.length
, base
.originalContent
.length
);
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
;
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
;
418 if (alreadyOpen
|| redraw
) {
419 // restore caret position (userClosed)
420 $keyboard
.caret(base
.$preview
, base
.last
);
424 // opening keyboard flag; delay allows switching between keyboards without immediately closing
426 base
.timer2
= setTimeout(function () {
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
);
437 if (o
.initialFocus
|| base
.$el
.hasClass($keyboard
.css
.initialFocus
)) {
438 $keyboard
.caret(base
.$preview
, base
.last
);
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
450 // return base to allow chaining in typing extension
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
;
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];
466 // set keyboard language
467 o
.display
= $.extend(true, {},
469 kblang
[lang
] && kblang
[lang
].display
|| {},
470 base
.settings
.display
472 o
.combos
= $.extend(true, {},
474 kblang
[lang
] && kblang
[lang
].combos
|| {},
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;
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
);
486 .toggleClass('rtl', o
.rtl
)
487 .css('direction', o
.rtl
? 'rtl' : '');
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
)) {
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();
501 base
.layout
= o
.layout
=== 'custom' ? o
.layoutHash : o
.layout
;
502 base
.last
.layout
= base
.layout
;
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);
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
);
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);
531 base
.$keyboard
.appendTo(o
.appendLocally
? base
.$el
.parent() : o
.appendTo
|| 'body');
535 // reposition keyboard on window resize
536 if (o
.reposition
&& $.ui
&& $.ui
.position
&& o
.appendTo
== 'body') {
537 $(window
).bind('resize' + base
.namespace, function () {
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
) {
550 // get single target position
552 // OR target stored in element data (multiple targets)
553 base
.$el
.data('keyboardPosition') ||
554 // OR default @ element
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
);
566 base
.makePreview = function () {
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];
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';
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
);
593 // build preview container and append preview display
595 .addClass(kbcss
.wrapper
)
596 .append(base
.$preview
)
597 .prependTo(base
.$keyboard
);
599 base
.$preview
= base
.$el
;
600 base
.preview
= base
.el
;
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
);
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
;
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
) {
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
));
628 if (!base
.$previewCopy
) {
630 base
.$previewCopy
= base
.$preview
.clone()
631 .removeAttr('id') // fixes #334
633 position: 'absolute',
638 .addClass($keyboard
.css
.inputClone
);
639 // prevent submitting content on form submission
640 base
.$previewCopy
[0].disabled
= true;
642 // make input zero-width because we need an accurate scrollWidth
643 base
.$previewCopy
.css({
644 'white-space': 'pre',
649 // add clone inside of preview wrapper
650 base
.$preview
.after(base
.$previewCopy
);
652 // just slap that thing in there somewhere
653 base
.$keyboard
.prepend(base
.$previewCopy
);
658 // need the textarea scrollHeight, so set the clone textarea height to be the line height
660 .height(base
.lineHeight
)
662 // set scrollTop for Textarea
663 base
.preview
.scrollTop
= base
.lineHeight
*
664 (Math
.floor(base
.$previewCopy
[0].scrollHeight
/ base
.lineHeight
) - 1);
666 // add non-breaking spaces
667 base
.$previewCopy
.val(value
.replace(/\s/g, '\xa0'));
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;
673 // set initial state as moving right
674 if (typeof base
.last
.scrollWidth
=== 'undefined') {
675 base
.last
.scrollWidth
= scrollWidth
;
676 base
.last
.direction
= true;
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
;
684 // set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
685 // hug right while scrolling right...
687 if (scrollWidth
< clientWidth
) {
688 base
.preview
.scrollLeft
= 0;
690 base
.preview
.scrollLeft
= scrollWidth
- clientWidth
;
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
;
699 base
.preview
.scrollLeft
= 0;
703 base
.last
.scrollWidth
= scrollWidth
;
704 base
.last
.direction
= direction
;
709 base
.bindFocus = function () {
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 () {
717 // remove focus from element (needed for IE since blur doesn't seem to work)
718 if ($(':focus')[0] === base
.el
) {
725 base
.bindKeyboard = function () {
727 keyCodes
= $keyboard
.keyCodes
,
728 layout
= $keyboard
.builtLayouts
[base
.layout
];
730 .unbind(base
.namespace)
731 .bind('click' + base
.namespace + ' touchstart' + base
.namespace, function () {
732 if (o
.alwaysOpen
&& !base
.isCurrent()) {
735 // update last caret position after user click, use at least 150ms or it doesn't work in IE
736 base
.timer2
= setTimeout(function () {
743 .bind('keypress' + base
.namespace, function (e
) {
747 if (!base
.isCurrent()) {
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;
758 base
.last
.$key
= []; // not a virtual keyboard key
759 if (base
.checkCaret
) {
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;
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
)) {
782 if ($.inArray(str
, layout
.acceptedKeys
) === -1) {
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
]);
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
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
);
805 if (typeof o
.beforeInsert
=== 'function') {
806 base
.insertText(base
.last
.key
);
809 base
.checkMaxLength();
812 .bind('keyup' + base
.namespace, function (e
) {
813 if (!base
.isCurrent()) { return; }
814 base
.last
.virtual
= false;
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
);
834 // Escape will hide the keyboard
835 case keyCodes
.escape:
837 base
.close(o
.autoAccept
&& o
.autoAcceptOnEsc
? 'true' : false);
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()) {
851 base
.checkMaxLength();
853 base
.last
.preVal
= '' + base
.last
.val
;
854 base
.last
.val
= base
.$preview
.val();
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
]);
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
);
870 if (o
.acceptValid
&& o
.autoAcceptOnValid
) {
871 if ($.isFunction(o
.validate
) && o
.validate(base
, base
.$preview
.val())) {
872 base
.$preview
.blur();
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()) {
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
894 base
.last
.virtual
= false;
897 case keyCodes
.backSpace:
898 $keyboard
.keyaction
.bksp(base
, null, e
);
903 $keyboard
.keyaction
.enter(base
, null, e
);
907 case keyCodes
.capsLock:
908 base
.shiftActive
= base
.capsLock
= !base
.capsLock
;
913 // prevent ctrl-v/cmd-v
914 if (e
.ctrlKey
|| e
.metaKey
) {
915 if (o
.preventPaste
) {
919 base
.checkCombos(); // check pasted content
924 .bind('mouseup touchend '.split(' ').join(base
.namespace + ' '), function () {
925 base
.last
.virtual
= true;
929 // prevent keyboard event bubbling
930 base
.$keyboard
.bind('mousedown click touchstart '.split(' ').join(base
.namespace + ' '), function (e
) {
932 if (!base
.isCurrent()) {
934 $(document
).trigger('checkkeyboard' + base
.namespace);
936 if (!o
.noFocus
&& base
.$preview
) {
937 base
.$preview
.focus();
941 // If preventing paste, block context menu (right click)
942 if (o
.preventPaste
) {
943 base
.$preview
.bind('contextmenu' + base
.namespace, function (e
) {
946 base
.$el
.bind('contextmenu' + base
.namespace, function (e
) {
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()) {
963 base
.$preview
.focus();
965 $keyboard
.caret(base
.$preview
, base
.last
);
967 if (!base
.isCurrent()) {
975 if (o
.useWheel
&& base
.wheel
) {
976 $keys
= base
.getLayers($this);
977 txt
= ($keys
.length
? $keys
.map(function () {
978 return $(this).attr('data-value') || '';
980 .get() : '') || [$this.text()];
981 last
.wheel_
$Keys
= $keys
;
982 last
.wheelLayers
= txt
;
983 last
.wheelIndex
= $.inArray($this.attr('data-value'), txt
);
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') ?
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;
1005 .attr('title', function (i
, t
) {
1006 return (t
=== o
.wheelMessage
) ? '' : t
;
1008 .html($this.attr('data-html')); // restore original button text
1012 // keyBinding = 'mousedown touchstart' by default
1013 .bind(o
.keyBinding
.split(' ').join(base
.namespace + ' ') + base
.namespace + ' ' +
1014 $keyboard
.events
.kbRepeater
, function (e
) {
1016 // prevent errors when external triggers attempt to 'type' - see issue #158
1017 if (!base
.$keyboard
.is(':visible')) {
1024 // prevent mousedown & touchstart from both firing events at the same time - see #184
1025 timer
= new Date().getTime();
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
;
1033 action
= $key
.attr('data-action');
1034 if (timer
- (last
.eventTime
|| 0) < o
.preventDoubleEventTime
) {
1037 last
.eventTime
= timer
;
1039 last
.virtual
= true;
1041 base
.$preview
.focus();
1044 last
.key
= $key
.attr('data-value');
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
);
1050 if (/^meta/.test(action
)) {
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) {
1061 action
= null; // prevent inserting action name
1063 // stop processing if keyboard closed and keyaction did not return false - see #536
1064 if (!base
.hasKeyboard()) {
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'));
1075 // set caret if caret moved by action function; also, attempt to fix issue #131
1076 $keyboard
.caret(base
.$preview
, last
);
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();
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
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;
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
) {
1110 } else if (/(mouseleave|touchend|touchcancel)/i.test(e
.type
)) {
1111 $this.removeClass(o
.css
.buttonHover
); // needed for touch devices
1113 if (!o
.noFocus
&& base
.isCurrent() && base
.isVisible()) {
1114 base
.$preview
.focus();
1116 if (base
.checkCaret
) {
1117 $keyboard
.caret(base
.$preview
, base
.last
);
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();
1130 // prevent form submits when keyboard is bound locally - issue #64
1131 .bind('click' + base
.namespace, function () {
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
;
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) {
1154 base
.last
.wheelIndex
= n
;
1155 $(this).html(txt
[n
]);
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) {
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
);
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
);
1184 if (typeof txt
=== 'undefined' || txt
=== false) {
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
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
;
1201 if (pos
.start
> len
) {
1202 pos
.end
= pos
.start
= len
;
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') {
1213 if (txt
=== '{d}') {
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
);
1224 base
.$preview
.val(val
);
1225 base
.saveCaret(t
, t
); // save caret in case of bksp
1227 // see #506.. allow chaining of insertText
1232 base
.checkMaxLength = function () {
1233 if (!base
.$preview
) { return; }
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
);
1240 // prevent inserting new characters when maxed #289
1242 val
= base
.last
.val
;
1243 caret
= start
- 1; // move caret back one
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
);
1250 if (base
.$decBtn
.length
) {
1251 base
.checkDecimal();
1257 // mousedown repeater
1258 base
.repeatKey = function (key
) {
1259 key
.trigger($keyboard
.events
.kbRepeater
);
1260 if (base
.mouseRepeat
[0]) {
1261 base
.repeater
= setTimeout(function () {
1263 base
.repeatKey(key
);
1265 }, base
.repeatTime
);
1269 base
.getKeySet = function () {
1271 if (base
.altActive
) {
1274 if (base
.shiftActive
) {
1277 if (base
.metaActive
) {
1278 // base.metaActive contains the string name of the
1279 // current meta keyset
1280 sets
.push(base
.metaActive
);
1282 return sets
.length
? sets
.join('+') : 'normal';
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]);
1296 base
.metaActive
= false;
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
,
1313 toShow
= (base
.shiftActive
? 1 : 0) + (base
.altActive
? 2 : 0);
1314 if (!base
.shiftActive
) {
1315 base
.capsLock
= false;
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", "");
1323 if (base
.altActive
) {
1324 name
= (name
|| "").replace("-alt", "");
1326 // the name attribute contains the meta set name 'meta99'
1327 key
= (/^meta/i.test(name
)) ? name : '';
1328 // save active meta keyset name
1330 key
= (base
.metaActive
=== true) ? '' : base
.metaActive
;
1332 base
.metaActive
= key
;
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;
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;
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];
1354 .find(prefix
+ 'alt,' + prefix
+ 'shift,.' + kbcss
.keyAction
+ '[class*=meta]')
1355 .removeClass(active
)
1357 .find(prefix
+ 'alt')
1358 .toggleClass(active
, base
.altActive
)
1360 .find(prefix
+ 'shift')
1361 .toggleClass(active
, base
.shiftActive
)
1363 .find(prefix
+ 'lock')
1364 .toggleClass(active
, base
.capsLock
)
1366 .find('.' + kbcss
.keySet
)
1369 .find('.' + (kbcss
.keyAction
+ prefix
+ key
).replace("--", "-"))
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);
1380 base
.last
.keyset
= [base
.shiftActive
, base
.altActive
, base
.metaActive
];
1381 base
.$el
.trigger($keyboard
.events
.kbKeysetChange
, [base
, base
.el
]);
1387 // check for key combos (dead keys)
1388 base
.checkCombos = function () {
1389 // return val for close function
1391 base
.isVisible() || (
1392 base
.hasKeyboard() &&
1393 base
.$keyboard
.hasClass( $keyboard
.css
.hasFocus
)
1396 return ( base
.$preview
|| base
.$el
).val();
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
1406 // check valid on empty string - see #429
1407 if (o
.acceptValid
) {
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
;
1419 if (pos
.start
> len
) {
1420 pos
.end
= pos
.start
= len
;
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') {
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
;
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
);
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
;
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
));
1453 val
= base
.$preview
.val();
1457 // check input restrictions - in case content was pasted
1458 if (o
.restrictInput
&& val
!== '') {
1459 t
= layout
.acceptedKeys
.length
;
1461 r
= layout
.acceptedKeysRegex
;
1463 t2
= $.map(layout
.acceptedKeys
, function (v
) {
1464 // escape any special characters
1465 return v
.replace(base
.escapeRegex
, '\\$&');
1467 r
= layout
.acceptedKeysRegex
= new RegExp('(' + t2
.join('|') + ')', 'g');
1470 // only save matching keys
1475 // no valid characters
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
1489 base
.checkMaxLength();
1491 if (o
.acceptValid
) {
1495 return val
; // return text, used for keyboard closing section
1498 // Toggle accept button classes, if validating
1499 base
.checkValid = function () {
1500 var kbcss
= $keyboard
.css
,
1501 $accept
= base
.$keyboard
.find('.' + kbcss
.keyPrefix
+ 'accept'),
1503 if ($.isFunction(o
.validate
)) {
1504 valid
= o
.validate(base
, base
.$preview
.val(), false);
1506 // toggle accept button classes; defined in the css
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'] + ')');
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
))) {
1521 'disabled': 'disabled',
1522 'aria-disabled': 'true'
1524 .removeClass(o
.css
.buttonHover
)
1525 .addClass(o
.css
.buttonDisabled
);
1528 .removeAttr('disabled')
1530 'aria-disabled': 'false'
1532 .addClass(o
.css
.buttonDefault
)
1533 .removeClass(o
.css
.buttonDisabled
);
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 () {
1545 .find('.' + kbcss
.keyText
)
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
);
1558 // base.$keyboard may be an empty array - see #275 (apod42)
1559 if (base
.$keyboard
.length
) {
1560 base
.$keyboard
.hide();
1564 all
= $('button, input, select, textarea, a')
1567 indx
= all
.index(base
.$el
) + (goToNext
? 1 : -1);
1568 if (base
.$keyboard
.length
) {
1569 base
.$keyboard
.show();
1571 if (indx
> all
.length
- 1) {
1572 stopped
= o
.stopAtEnd
;
1573 indx
= 0; // go to first input
1576 stopped
= o
.stopAtEnd
;
1577 indx
= all
.length
- 1; // stop or go to last
1580 isAccepted
= base
.close(isAccepted
);
1584 kb
= all
.eq(indx
).data('keyboard');
1585 if (kb
&& kb
.options
.openOn
.length
) {
1588 all
.eq(indx
).focus();
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
;
1607 if (o
.cancelClose
) {
1611 base
.isCurrent(false);
1612 base
.isOpen
= o
.alwaysOpen
|| o
.userClosed
;
1613 // update value for always open keyboards
1614 base
.$preview
.val(val
);
1616 .removeClass(kbcss
.isCurrent
+ ' ' + kbcss
.inputAutoAccepted
)
1617 // add 'ui-keyboard-autoaccepted' to inputs - see issue #66
1618 .addClass((accepted
|| false) ? accepted
=== true ? '' : kbcss
.inputAutoAccepted : '')
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)]);
1627 // save caret after updating value (fixes userClosed issue with changing focus)
1628 $keyboard
.caret(base
.$preview
, base
.last
);
1631 .trigger(((accepted
|| false) ? kbevents
.inputAccepted : kbevents
.inputCanceled
), [base
, base
.el
])
1632 .trigger((o
.alwaysOpen
) ? kbevents
.kbInactive : kbevents
.kbHidden
, [base
, base
.el
])
1635 // base is undefined if keyboard was destroyed - fixes #358
1637 // add close event time
1638 base
.last
.eventTime
= new Date().getTime();
1639 if (!(o
.alwaysOpen
|| o
.userClosed
&& accepted
=== 'true') && base
.$keyboard
.length
) {
1641 base
.removeKeyboard();
1642 // rebind input focus - delayed to fix IE issue #72
1643 base
.timer
= setTimeout(function () {
1649 if (!base
.watermark
&& base
.el
.value
=== '' && base
.inPlaceholder
!== '') {
1651 .addClass(kbcss
.placeholder
)
1652 .val(base
.inPlaceholder
);
1659 base
.accept = function () {
1660 return base
.close(true);
1663 base
.checkClose = function (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
1676 !kb
.$el
.hasClass(kbcss
.isCurrent
) &&
1677 kb
.options
.openOn
&&
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) :
1691 // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or
1692 // single stay open keyboard
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
)) {
1702 // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
1703 if ($keyboard
.allie
) {
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);
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);
1721 // Build default button
1722 base
.keyBtn
= $('<button />')
1726 'aria-disabled': 'false',
1729 .addClass($keyboard
.css
.keyButton
);
1731 // convert key names into a class name
1732 base
.processName = function (name
) {
1734 process
= (name
|| '').replace(/[^a-z0-9-_]/gi, ''),
1735 len
= process
.length
,
1737 if (len
> 1 && name
=== process
) {
1738 // return name if basic text
1741 // return character code sequence
1744 for (index
= 0; index
< len
; 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)
1752 return newName
.join('');
1758 base
.processKeys = function (name
) {
1760 parts
= name
.split(':'),
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
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)'
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
) {
1785 data
.title
= parts
.length
> 1 ? parts
.slice(1).join(':') : '';
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);
1794 // 'x(:):test' => parts = [ 'x(', ')', 'title' ] need to slice 2
1796 parts
= parts
.slice(2);
1798 data
.title
= parts
.length
? parts
.join(':') : '';
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] === '') {
1809 parts
= parts
.slice(1);
1813 data
.title
= parts
.length
> 1 ? parts
.slice(1).join(':') : '';
1817 // corner case of '::;' reduced to ':;', split as ['', ';']
1818 if (name
!== '' && parts
[0] === '') {
1820 parts
= parts
.slice(1);
1822 data
.name
= parts
[0];
1824 data
.title
= parts
.length
> 1 ? parts
.slice(1).join(':') : '';
1826 data
.title
= $.trim(data
.title
).replace(/_
/g
, ' ');
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
,
1838 txt
= base
.processKeys(regKey
? keyName : action
),
1839 kbcss
= $keyboard
.css
;
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
;
1847 // when regKey is true, keyName is the same as action
1849 keys
.action
= txt
.name
;
1852 data
.name
= base
.processName(txt
.name
);
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
);
1862 keyClass
= data
.name
=== '' ? '' : kbcss
.keyPrefix
+ data
.name
;
1864 // Action keys will have the 'ui-keyboard-actionkey' class
1865 keyClass
= kbcss
.keyAction
+ ' ' + kbcss
.keyPrefix
+ keys
.action
;
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
;
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) + ';';
1878 data
.$key
= base
.keyBtn
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
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 ~)
1893 // See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
1896 .appendTo(base
.temp
[0]);
1899 data
.$key
.attr('data-mapped', keys
.map
);
1901 if (keys
.title
|| txt
.title
) {
1903 'data-title': txt
.title
|| keys
.title
, // used to allow adding content to title
1904 'title': txt
.title
|| keys
.title
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
);
1917 base
.customHash = function (layout
) {
1918 /*jshint bitwise:false */
1919 var i
, array
, hash
, character
, len
,
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
]);
1931 merged
= merged
.concat
.apply(merged
, arrays
).join(' ');
1932 // produce hash name - http://stackoverflow.com/a/7616484/145346
1934 len
= merged
.length
;
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
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();
1952 var row
, $row
, currentSet
,
1953 kbcss
= $keyboard
.css
,
1955 layout
= $keyboard
.builtLayouts
[name
|| base
.layout
|| o
.layout
] = {
1959 acceptedKeys
= layout
.acceptedKeys
= o
.restrictInclude
?
1960 ('' + o
.restrictInclude
).split(/\s+/) || [] :
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 : ''),
1966 container
= $('<div />')
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}']
1979 $layout
= $keyboard
.layouts
[internal ? o
.layout : name
|| base
.layout
|| o
.layout
];
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') {
1992 .attr('name', set) // added for typing extension
1993 .addClass(kbcss
.keySet
+ ' ' + kbcss
.keySet
+ '-' + set)
1994 .appendTo(container
)
1995 .toggle(set === 'normal');
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
)
2003 .after('<br class="' + kbcss
.endRow
+ '"/>');
2011 layout
.hasMappedKeys
= !($.isEmptyObject(layout
.mappedKeys
));
2012 layout
.$keyboard
= container
;
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
];
2024 // ignore empty keys
2025 if (keys
[key
].length
=== 0) {
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('!!', '');
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
2043 .match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
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
)
2055 if (/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(action
)) {
2056 margin
= (/:/.test(action
)) ? parseFloat(action
2058 .match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
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') : '');
2069 if (/^meta[\w-]+\:?(\w+)?/i.test(action
)) {
2071 .addKey(action
.split(':')[0], action
)
2072 .addClass(kbcss
.keyHasActive
);
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()) {
2084 .addKey('accept', action
)
2085 .addClass(o
.css
.buttonAction
+ ' ' + kbcss
.keyAction
);
2091 .addKey('alt', action
)
2092 .addClass(kbcss
.keyHasActive
);
2097 base
.addKey('bksp', action
);
2103 .addKey('cancel', action
)
2104 .addClass(o
.css
.buttonAction
+ ' ' + kbcss
.keyAction
);
2107 // toggle combo/diacritic key
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'];
2117 .toggleClass(o
.css
.buttonActive
, o
.useCombos
);
2120 // Decimal - unique decimal point (num pad layout)
2122 acceptedKeys
.push((base
.decimal) ? '.' : ',');
2123 base
.addKey('dec', action
);
2129 .addKey('enter', action
)
2130 .addClass(o
.css
.buttonAction
+ ' ' + kbcss
.keyAction
);
2135 .addKey('lock', action
)
2136 .addClass(kbcss
.keyHasActive
);
2142 .addKey('shift', action
)
2143 .addClass(kbcss
.keyHasActive
);
2146 // Change sign (for num pad layout)
2148 acceptedKeys
.push('-');
2149 base
.addKey('sign', action
);
2153 acceptedKeys
.push(' ');
2154 base
.addKey('space', action
);
2159 base
.addKey('tab', action
);
2163 if ($keyboard
.keyaction
.hasOwnProperty(txt
[0])) {
2165 .addKey(txt
[0], action
)
2166 .toggleClass(o
.css
.buttonAction
+ ' ' + kbcss
.keyAction
, isAction
);
2173 // regular button (not an action key)
2175 base
.addKey(t
, t
, true);
2180 base
.removeBindings = function (namespace) {
2181 $(document
).unbind(namespace);
2182 if (base
.el
.ownerDocument
!== document
) {
2183 $(base
.el
.ownerDocument
).unbind(namespace);
2185 $(window
).unbind(namespace);
2186 base
.$el
.unbind(namespace);
2189 base
.removeKeyboard = function () {
2192 // base.$preview === base.$el when o.usePreview is false - fixes #442
2194 base
.$preview
.removeData('keyboard');
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);
2206 base
.destroy = function (callback
) {
2208 kbcss
= $keyboard
.css
,
2209 len
= base
.extensionNamespace
.length
,
2219 clearTimeout(base
.timer
);
2220 clearTimeout(base
.timer2
);
2221 if (base
.$keyboard
.length
) {
2222 base
.removeKeyboard();
2224 base
.removeBindings(base
.namespace);
2225 base
.removeBindings(base
.namespace + 'callbacks');
2226 for (index
= 0; index
< len
; index
++) {
2227 base
.removeBindings(base
.extensionNamespace
[index
]);
2229 base
.el
.active
= false;
2233 .removeAttr('aria-haspopup')
2235 .removeData('keyboard');
2238 if (typeof callback
=== 'function') {
2246 }; // end $.keyboard definition
2248 // event.which & ASCII values
2249 $keyboard
.keyCodes
= {
2266 // event.which keyCodes (uppercase letters)
2273 // ASCII lowercase a & z
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',
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>
2313 $keyboard
.events
= {
2315 kbChange: 'keyboardChange',
2316 kbBeforeClose: 'beforeClose',
2317 kbBeforeVisible: 'beforeVisible',
2318 kbVisible: 'visible',
2319 kbInit: 'initialized',
2320 kbInactive: 'inactive',
2322 kbRepeater: 'repeater',
2323 kbKeysetChange: 'keysetChange',
2325 inputAccepted: 'accepted',
2326 inputCanceled: 'canceled',
2327 inputChange: 'change',
2328 inputRestricted: 'restricted'
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
2337 alt: function (base
) {
2338 base
.altActive
= !base
.altActive
;
2341 bksp: function (base
) {
2342 // the script looks for the '\b' string and initiates a backspace
2343 base
.insertText('\b');
2345 cancel: function (base
) {
2347 return false; // return false prevents further processing
2349 clear: function (base
) {
2350 base
.$preview
.val('');
2351 if (base
.$decBtn
.length
) {
2352 base
.checkDecimal();
2355 combo: function (base
) {
2356 var o
= base
.options
,
2358 $combo
= base
.$keyboard
.find('.' + $keyboard
.css
.keyPrefix
+ 'combo');
2361 .toggleClass(o
.css
.buttonActive
, c
)
2362 // update combo key state
2363 .attr('title', $combo
.attr('data-title') + ' (' + o
.display
[c
? 'active' : 'disabled'] + ')');
2369 dec: function (base
) {
2370 base
.insertText((base
.decimal) ? '.' : ',');
2372 del: function (base
) {
2373 // the script looks for the '{d}' string and initiates a delete
2374 base
.insertText('{d}');
2376 // resets to base keyset (deprecated because "default" is a reserved word)
2377 'default': function (base
) {
2378 base
.shiftActive
= base
.altActive
= base
.metaActive
= false;
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
,
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);
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);
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');
2404 lock: function (base
) {
2405 base
.last
.keyset
[0] = base
.shiftActive
= base
.capsLock
= !base
.capsLock
;
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
);
2417 meta: function (base
, el
) {
2419 base
.metaActive
= !$el
.hasClass(base
.options
.css
.buttonActive
);
2420 base
.showSet($el
.attr('data-name'));
2422 next: function (base
) {
2423 base
.switchInput(true, base
.options
.autoAccept
);
2426 // same as 'default' - resets to base keyset
2427 normal: function (base
) {
2428 base
.shiftActive
= base
.altActive
= base
.metaActive
= false;
2431 prev: function (base
) {
2432 base
.switchInput(false, base
.options
.autoAccept
);
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
);
2444 shift: function (base
) {
2445 base
.last
.keyset
[0] = base
.shiftActive
= !base
.shiftActive
;
2448 sign: function (base
) {
2449 if (/^\-?\d*\.?\d*$/.test(base
.$preview
.val())) {
2450 base
.$preview
.val((base
.$preview
.val() * -1));
2453 space: function (base
) {
2454 base
.insertText(' ');
2456 tab: function (base
) {
2457 var tag
= base
.el
.nodeName
,
2459 if (tag
=== 'INPUT') {
2460 if (o
.tabNavigation
) {
2461 return base
.switchInput(!base
.shiftActive
, true);
2463 // ignore tab key in input
2467 base
.insertText('\t');
2469 toggle: function (base
) {
2470 base
.enabled
= !base
.enabled
;
2473 // *** Special action keys: NBSP & zero-width characters ***
2474 // Non-breaking space
2478 // Zero width non-joiner
2480 // Zero width joiner
2482 // Left-to-right Mark
2484 // Right-to-left Mark
2488 // Default keyboard layouts
2489 $keyboard
.builtLayouts
= {};
2490 $keyboard
.layouts
= {
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}'
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}'
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}'
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}'
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}'
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}'
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}'
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}'
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}'
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}'
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}'
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}'
2597 $keyboard
.language
= {
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 ←)
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)',
2613 'combo': '\u00f6:Toggle Combo Keys',
2614 // decimal point for num pad (optional), change '.' to ',' for European format
2616 // down, then left arrow - enter symbol
2617 'e': '\u23ce:Enter',
2619 'enter': 'Enter:Enter \u23ce',
2620 // left arrow (move caret)
2623 'lock': 'Lock:\u21ea Caps Lock',
2624 'next': 'Next \u21e8',
2625 'prev': '\u21e6 Prev',
2626 // right arrow (move caret)
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)
2636 // \u21b9 is the true tab symbol (left & right arrows)
2637 'tab': '\u21e5 Tab:Tab',
2638 // replaced by an image
2641 // added to titles of keys
2642 // accept key status when acceptValid:true
2644 'invalid': 'invalid',
2647 'disabled': 'disabled'
2650 // Message added to the key title while hovering, if the mousewheel plugin exists
2651 wheelMessage: 'Use mousewheel to see other keys',
2653 comboRegex: /([`\'~\^\"ao])([a-z])/mig,
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' },
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' },
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' },
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' },
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' }
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
2681 // *** choose layout & positioning ***
2686 // optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
2690 // used when 'usePreview' is false (centers the keyboard at the bottom of the input/textarea)
2691 at2: 'center bottom'
2694 // allow jQuery position utility to reposition the keyboard on window resize
2697 // preview added above keyboard if true, original input/textarea used if false
2700 // if true, the keyboard will always be visible
2703 // give the preview initial focus when the keyboard becomes visible
2706 // avoid changing the focus (hardware keyboard probably won't work)
2709 // if true, keyboard will remain open even if the input loses focus, but closes on escape
2710 // or when another keyboard opens.
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
2717 // if true, keyboard will not close if you press escape.
2720 // if true, keyboard will only closed on click event instead of mousedown and touchstart
2721 closeByClickEvent: false,
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)
2731 buttonDefault: 'ui-state-default ui-corner-all',
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'
2743 // *** Useability ***
2744 // Auto-accept content when clicking outside the keyboard (popup will close)
2746 // Auto-accept content even if the user presses escape (only works if `autoAccept` is `true`)
2747 autoAcceptOnEsc: false,
2749 // Prevents direct input in the preview window when true
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'
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'
2761 // Auto-accept when input is valid; requires `acceptValid` set `true` & validate callback
2762 autoAcceptOnValid: false,
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
2768 // tab to go to next, shift-tab for previous (default behavior)
2769 tabNavigation: false,
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
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
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'
2785 appendLocally: false,
2786 // When appendLocally is false, the keyboard will be appended to this object
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
2793 // Prevent pasting content into the area
2794 preventPaste: false,
2796 // caret placed at the end of any text when keyboard becomes visible
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,
2803 // Set the max number of characters allowed in the input, setting it to false disables this option
2805 // allow inserting characters @ caret when maxLength is set
2808 // Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will
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.
2818 // resets the keyboard to the default keyset when visible
2821 // Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
2824 // Event (namepaced) for when the character is added to the input (clicking on the keyboard)
2825 keyBinding: 'mousedown touchstart',
2827 // enable/disable mousewheel functionality
2828 // enabling still depends on the mousewheel plugin
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.
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(); },
2854 // build key callback
2855 buildKey : function( keyboard, data ) {
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
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
) {
2886 // for checking combos
2887 $keyboard
.comboRegex
= /([`\'~\^\"ao])([a-z])/mig;
2889 // store current keyboard element; used by base.isCurrent()
2890 $keyboard
.currentKeyboard
= '';
2892 $('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]>' +
2893 '<script>jQuery("body").addClass("ie");</script><![endif]-->')
2896 $keyboard
.msie
= $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
2897 $keyboard
.allie
= $('body').hasClass('ie');
2899 $keyboard
.watermark
= (typeof (document
.createElement('input').placeholder
) !== 'undefined');
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;
2912 return $keyboard
.checkCaret
;
2915 $keyboard
.caret = function ($el
, param1
, param2
) {
2916 if (!$el
|| !$el
.length
|| $el
.is(':hidden') || $el
.css('visibility') === 'hidden') {
2919 var start
, end
, txt
, pos
,
2920 kb
= $el
.data('keyboard'),
2921 noFocus
= kb
&& kb
.options
.noFocus
;
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
;
2931 } else if (typeof param2
=== 'undefined') {
2932 param2
= param1
; // set caret using start position
2934 // set caret using ( $el, start, end );
2935 if (typeof param1
=== 'number' && typeof param2
=== 'number') {
2938 } else if (param1
=== 'start') {
2940 } else if (typeof param1
=== 'string') {
2941 // unknown string setting, move caret to end
2942 start
= end
= $el
.val().length
;
2945 // *** SET CARET POSITION ***
2946 // modify the line below to adapt to other caret plugins
2947 return $el
.caret(start
, end
, noFocus
);
2949 // *** GET CARET POSITION ***
2950 // modify the line below to adapt to other caret plugins
2955 // *** utilities ***
2956 txt
= ($el
[0].value
|| $el
.text() || '');
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
);
2969 $.fn
.keyboard = function (options
) {
2970 return this.each(function () {
2971 if (!$(this).data('keyboard')) {
2972 /*jshint nonew:false */
2973 (new $.keyboard(this, options
));
2978 $.fn
.getkeyboard = function () {
2979 return this.data('keyboard');
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
2988 $.fn
.caret = function (start
, end
, noFocus
) {
2989 if (typeof this[0] === 'undefined' || this.is(':hidden') || this.css('visibility') === 'hidden') {
2992 var selRange
, range
, stored_range
, txt
, val
,
2993 selection
= document
.selection
,
2996 sTop
= el
.scrollTop
,
2998 supportCaret
= true;
3000 ss
= 'selectionStart' in el
;
3002 supportCaret
= false;
3004 if (supportCaret
&& typeof start
!== 'undefined') {
3005 if (!/(email|number)/i.test(el
.type
)) {
3007 el
.selectionStart
= start
;
3008 el
.selectionEnd
= end
;
3010 selRange
= el
.createTextRange();
3011 selRange
.collapse(true);
3012 selRange
.moveStart('character', start
);
3013 selRange
.moveEnd('character', end
- start
);
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')) {
3021 el
.scrollTop
= sTop
;
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
;
3028 start
= el
.selectionStart
;
3029 end
= el
.selectionEnd
;
3030 } else if (selection
) {
3031 if (el
.nodeName
=== 'TEXTAREA') {
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
;
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
;
3050 // caret positioning not supported
3051 start
= end
= (el
.value
|| '').length
;
3053 txt
= (el
.value
|| '');
3057 text: txt
.substring(start
, end
),
3058 replace: function (str
) {
3059 return txt
.substring(0, start
) + str
+ txt
.substring(end
, txt
.length
);