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