blinker / firefox.plugin / data / scripts / text / keyboard.js @ master
History | View | Annotate | Download (124.321 KB)
1 | 76dd22bd | KevinTaron | /*! jQuery UI Virtual Keyboard v1.26.4 *//* |
---|---|---|---|
2 | Author: Jeremy Satterfield
|
||
3 | Maintained: Rob Garrison (Mottie on github)
|
||
4 | Licensed under the MIT License
|
||
5 | |||
6 | An on-screen virtual keyboard embedded within the browser window which
|
||
7 | will popup when a specified entry field is focused. The user can then
|
||
8 | type and preview their input before Accepting or Canceling.
|
||
9 | |||
10 | This plugin adds default class names to match jQuery UI theme styling.
|
||
11 | Bootstrap & custom themes may also be applied - See
|
||
12 | https://github.com/Mottie/Keyboard#themes
|
||
13 | |||
14 | Requires:
|
||
15 | jQuery v1.4.3+
|
||
16 | Caret plugin (included)
|
||
17 | Optional:
|
||
18 | jQuery UI (position utility only) & CSS theme
|
||
19 | jQuery mousewheel
|
||
20 | |||
21 | Setup/Usage:
|
||
22 | Please refer to https://github.com/Mottie/Keyboard/wiki
|
||
23 | |||
24 | -----------------------------------------
|
||
25 | Caret code modified from jquery.caret.1.02.js
|
||
26 | Licensed under the MIT License:
|
||
27 | http://www.opensource.org/licenses/mit-license.php
|
||
28 | -----------------------------------------
|
||
29 | */
|
||
30 | /*jshint browser:true, jquery:true, unused:false */
|
||
31 | /*global require:false, define:false, module:false */
|
||
32 | ;(function (factory) {
|
||
33 | if (typeof define === 'function' && define.amd) { |
||
34 | define(['jquery'], factory);
|
||
35 | } else if (typeof module === 'object' && typeof module.exports === 'object') { |
||
36 | module.exports = factory(require('jquery'));
|
||
37 | } else {
|
||
38 | factory(jQuery); |
||
39 | } |
||
40 | }(function ($) { |
||
41 | 'use strict';
|
||
42 | var $keyboard = $.keyboard = function (el, options) { |
||
43 | var o, base = this; |
||
44 | |||
45 | base.version = '1.26.4';
|
||
46 | |||
47 | // Access to jQuery and DOM versions of element
|
||
48 | base.$el = $(el); |
||
49 | base.el = el; |
||
50 | |||
51 | // Add a reverse reference to the DOM object
|
||
52 | base.$el.data('keyboard', base); |
||
53 | |||
54 | base.init = function () { |
||
55 | var k, position, tmp,
|
||
56 | kbcss = $keyboard.css,
|
||
57 | kbevents = $keyboard.events;
|
||
58 | base.settings = options || {}; |
||
59 | // shallow copy position to prevent performance issues; see #357
|
||
60 | if (options && options.position) {
|
||
61 | position = $.extend({}, options.position);
|
||
62 | options.position = null;
|
||
63 | } |
||
64 | base.options = o = $.extend(true, {}, $keyboard.defaultOptions, options); |
||
65 | if (position) {
|
||
66 | o.position = position; |
||
67 | options.position = position; |
||
68 | } |
||
69 | |||
70 | // keyboard is active (not destroyed);
|
||
71 | base.el.active = true;
|
||
72 | // unique keyboard namespace
|
||
73 | base.namespace = '.keyboard' + Math.random().toString(16).slice(2); |
||
74 | // extension namespaces added here (to unbind listeners on base.$el upon destroy)
|
||
75 | base.extensionNamespace = []; |
||
76 | // Shift and Alt key toggles, sets is true if a layout has more than one keyset
|
||
77 | // used for mousewheel message
|
||
78 | base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
|
||
79 | // Class names of the basic key set - meta keysets are handled by the keyname
|
||
80 | base.rows = ['', '-shift', '-alt', '-alt-shift']; |
||
81 | |||
82 | base.inPlaceholder = base.$el.attr('placeholder') || ''; |
||
83 | // html 5 placeholder/watermark
|
||
84 | base.watermark = $keyboard.watermark && base.inPlaceholder !== ''; |
||
85 | // convert mouse repeater rate (characters per second) into a time in milliseconds.
|
||
86 | base.repeatTime = 1000 / (o.repeatRate || 20); |
||
87 | // delay in ms to prevent mousedown & touchstart from both firing events at the same time
|
||
88 | o.preventDoubleEventTime = o.preventDoubleEventTime || 100;
|
||
89 | // flag indication that a keyboard is open
|
||
90 | base.isOpen = false;
|
||
91 | // is mousewheel plugin loaded?
|
||
92 | base.wheel = $.isFunction($.fn.mousewheel); |
||
93 | // special character in regex that need to be escaped
|
||
94 | base.escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
|
||
95 | |||
96 | // keyCode of keys always allowed to be typed
|
||
97 | k = $keyboard.keyCodes;
|
||
98 | // base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
|
||
99 | base.alwaysAllowed = [ |
||
100 | k.capsLock, |
||
101 | k.pageUp, |
||
102 | k.pageDown, |
||
103 | k.end, |
||
104 | k.home, |
||
105 | k.left, |
||
106 | k.up, |
||
107 | k.right, |
||
108 | k.down, |
||
109 | k.insert, |
||
110 | k.delete
|
||
111 | ]; |
||
112 | base.$keyboard = [];
|
||
113 | // keyboard enabled; set to false on destroy
|
||
114 | base.enabled = true;
|
||
115 | // make a copy of the original keyboard position
|
||
116 | if (!$.isEmptyObject(o.position)) { |
||
117 | o.position.orig_at = o.position.at; |
||
118 | } |
||
119 | |||
120 | base.checkCaret = (o.lockInput || $keyboard.checkCaretSupport());
|
||
121 | |||
122 | base.last = { |
||
123 | start: 0, |
||
124 | end: 0, |
||
125 | key: '', |
||
126 | val: '', |
||
127 | preVal: '', |
||
128 | layout: '', |
||
129 | virtual: true, |
||
130 | keyset: [false, false, false], // [shift, alt, meta] |
||
131 | wheel_$Keys: null, |
||
132 | wheelIndex: 0, |
||
133 | wheelLayers: []
|
||
134 | }; |
||
135 | // used when building the keyboard - [keyset element, row, index]
|
||
136 | base.temp = ['', 0, 0]; |
||
137 | |||
138 | // Callbacks
|
||
139 | $.each([
|
||
140 | kbevents.kbInit, |
||
141 | kbevents.kbBeforeVisible, |
||
142 | kbevents.kbVisible, |
||
143 | kbevents.kbHidden, |
||
144 | kbevents.inputCanceled, |
||
145 | kbevents.inputAccepted, |
||
146 | kbevents.kbBeforeClose, |
||
147 | kbevents.inputRestricted |
||
148 | ], function (i, callback) {
|
||
149 | if ($.isFunction(o[callback])) { |
||
150 | // bind callback functions within options to triggered events
|
||
151 | base.$el.bind(callback + base.namespace + 'callbacks', o[callback]); |
||
152 | } |
||
153 | }); |
||
154 | |||
155 | // Close with esc key & clicking outside
|
||
156 | if (o.alwaysOpen) {
|
||
157 | o.stayOpen = true;
|
||
158 | } |
||
159 | |||
160 | tmp = $(document);
|
||
161 | if (base.el.ownerDocument !== document) {
|
||
162 | tmp = tmp.add(base.el.ownerDocument); |
||
163 | } |
||
164 | |||
165 | var bindings = 'keyup checkkeyboard mousedown touchstart '; |
||
166 | if (o.closeByClickEvent) {
|
||
167 | bindings += 'click ';
|
||
168 | } |
||
169 | tmp.bind(bindings.split(' ').join(base.namespace + ' '), base.checkClose); |
||
170 | |||
171 | // Display keyboard on focus
|
||
172 | base.$el
|
||
173 | .addClass(kbcss.input + ' ' + o.css.input)
|
||
174 | .attr({ |
||
175 | 'aria-haspopup': 'true', |
||
176 | 'role': 'textbox' |
||
177 | }); |
||
178 | |||
179 | // set lockInput if the element is readonly; or make the element readonly if lockInput is set
|
||
180 | if (o.lockInput || base.el.readOnly) {
|
||
181 | o.lockInput = true;
|
||
182 | base.$el
|
||
183 | .addClass(kbcss.locked) |
||
184 | .attr({ |
||
185 | 'readonly': 'readonly' |
||
186 | }); |
||
187 | } |
||
188 | // add disabled/readonly class - dynamically updated on reveal
|
||
189 | if (base.$el.is(':disabled') || (base.$el.attr('readonly') && |
||
190 | !base.$el.hasClass(kbcss.locked))) {
|
||
191 | base.$el.addClass(kbcss.noKeyboard);
|
||
192 | } |
||
193 | |||
194 | if (o.openOn) {
|
||
195 | base.bindFocus(); |
||
196 | } |
||
197 | |||
198 | // Add placeholder if not supported by the browser
|
||
199 | if (!base.watermark && base.$el.val() === '' && base.inPlaceholder !== '' && |
||
200 | base.$el.attr('placeholder') !== '') { |
||
201 | base.$el
|
||
202 | .addClass(kbcss.placeholder) // css watermark style (darker text)
|
||
203 | .val(base.inPlaceholder); |
||
204 | } |
||
205 | |||
206 | base.$el.trigger(kbevents.kbInit, [base, base.el]);
|
||
207 | |||
208 | // initialized with keyboard open
|
||
209 | if (o.alwaysOpen) {
|
||
210 | base.reveal(); |
||
211 | } |
||
212 | |||
213 | }; |
||
214 | |||
215 | base.toggle = function () { |
||
216 | var $toggle = base.$keyboard.find('.' + $keyboard.css.keyToggle), |
||
217 | locked = !base.enabled; |
||
218 | // prevent physical keyboard from working
|
||
219 | base.$preview.prop('readonly', locked || base.options.lockInput); |
||
220 | // disable all buttons
|
||
221 | base.$keyboard
|
||
222 | .toggleClass($keyboard.css.keyDisabled, locked)
|
||
223 | .find('.' + $keyboard.css.keyButton) |
||
224 | .not($toggle)
|
||
225 | .prop('disabled', locked)
|
||
226 | .attr('aria-disabled', locked);
|
||
227 | $toggle.toggleClass($keyboard.css.keyDisabled, locked); |
||
228 | // stop auto typing
|
||
229 | if (locked && base.typing_options) {
|
||
230 | base.typing_options.text = '';
|
||
231 | } |
||
232 | }; |
||
233 | |||
234 | base.setCurrent = function () { |
||
235 | var kbcss = $keyboard.css, |
||
236 | // close any "isCurrent" keyboard (just in case they are always open)
|
||
237 | $current = $('.' + kbcss.isCurrent), |
||
238 | kb = $current.data('keyboard'); |
||
239 | // close keyboard, if not self
|
||
240 | if (!$.isEmptyObject(kb) && kb.el !== base.el) { |
||
241 | kb.close(kb.options.autoAccept ? 'true' : false); |
||
242 | } |
||
243 | $current.removeClass(kbcss.isCurrent);
|
||
244 | // ui-keyboard-has-focus is applied in case multiple keyboards have
|
||
245 | // alwaysOpen = true and are stacked
|
||
246 | $('.' + kbcss.hasFocus).removeClass(kbcss.hasFocus); |
||
247 | |||
248 | base.$el.addClass(kbcss.isCurrent);
|
||
249 | base.$keyboard.addClass(kbcss.hasFocus);
|
||
250 | base.isCurrent(true);
|
||
251 | base.isOpen = true;
|
||
252 | }; |
||
253 | |||
254 | base.isCurrent = function (set) { |
||
255 | var cur = $keyboard.currentKeyboard || false; |
||
256 | if (set) {
|
||
257 | cur = $keyboard.currentKeyboard = base.el;
|
||
258 | } else if (set === false && cur === base.el) { |
||
259 | cur = $keyboard.currentKeyboard = ''; |
||
260 | } |
||
261 | return cur === base.el;
|
||
262 | }; |
||
263 | |||
264 | base.isVisible = function () { |
||
265 | return base.$keyboard && base.$keyboard.length ? base.$keyboard.is(':visible') : false; |
||
266 | }; |
||
267 | |||
268 | base.focusOn = function () { |
||
269 | if (!base && base.el.active) {
|
||
270 | // keyboard was destroyed
|
||
271 | return;
|
||
272 | } |
||
273 | if (!base.isVisible()) {
|
||
274 | clearTimeout(base.timer); |
||
275 | base.reveal(); |
||
276 | } |
||
277 | }; |
||
278 | |||
279 | // add redraw method to make API more clear
|
||
280 | base.redraw = function () { |
||
281 | // update keyboard after a layout change
|
||
282 | if (base.$keyboard.length) { |
||
283 | |||
284 | base.last.preVal = '' + base.last.val;
|
||
285 | base.last.val = base.$preview && base.$preview.val() || base.$el.val(); |
||
286 | base.$el.val( base.last.val );
|
||
287 | |||
288 | base.removeKeyboard(); |
||
289 | base.shiftActive = base.altActive = base.metaActive = false;
|
||
290 | } |
||
291 | base.isOpen = o.alwaysOpen; |
||
292 | base.reveal(true);
|
||
293 | }; |
||
294 | |||
295 | base.reveal = function (redraw) { |
||
296 | var alreadyOpen = base.isOpen,
|
||
297 | kbcss = $keyboard.css;
|
||
298 | base.opening = !alreadyOpen; |
||
299 | // remove all 'extra' keyboards by calling close function
|
||
300 | $('.' + kbcss.keyboard).not('.' + kbcss.alwaysOpen).each(function(){ |
||
301 | var kb = $(this).data('keyboard'); |
||
302 | if (!$.isEmptyObject(kb)) { |
||
303 | kb.close(kb.options.autoAccept && kb.options.autoAcceptOnEsc ? 'true' : false); |
||
304 | } |
||
305 | }); |
||
306 | |||
307 | // Don't open if disabled
|
||
308 | if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass(kbcss.locked))) { |
||
309 | base.$el.addClass(kbcss.noKeyboard);
|
||
310 | return;
|
||
311 | } else {
|
||
312 | base.$el.removeClass(kbcss.noKeyboard);
|
||
313 | } |
||
314 | |||
315 | // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
|
||
316 | if (o.openOn) {
|
||
317 | base.$el.unbind($.trim((o.openOn + ' ').split(/\s+/).join(base.namespace + ' '))); |
||
318 | } |
||
319 | |||
320 | // build keyboard if it doesn't exist; or attach keyboard if it was removed, but not cleared
|
||
321 | if (!base.$keyboard || base.$keyboard && |
||
322 | (!base.$keyboard.length || $.contains(document.body, base.$keyboard[0]))) { |
||
323 | base.startup(); |
||
324 | } |
||
325 | |||
326 | // clear watermark
|
||
327 | if (!base.watermark && base.el.value === base.inPlaceholder) {
|
||
328 | base.$el
|
||
329 | .removeClass(kbcss.placeholder) |
||
330 | .val('');
|
||
331 | } |
||
332 | // save starting content, in case we cancel
|
||
333 | base.originalContent = base.$el.val();
|
||
334 | base.$preview.val(base.originalContent);
|
||
335 | |||
336 | // disable/enable accept button
|
||
337 | if (o.acceptValid) {
|
||
338 | base.checkValid(); |
||
339 | } |
||
340 | |||
341 | if (o.resetDefault) {
|
||
342 | base.shiftActive = base.altActive = base.metaActive = false;
|
||
343 | } |
||
344 | base.showSet(); |
||
345 | |||
346 | // beforeVisible event
|
||
347 | if (!base.isVisible()) {
|
||
348 | base.$el.trigger($keyboard.events.kbBeforeVisible, [base, base.el]); |
||
349 | } |
||
350 | base.setCurrent(); |
||
351 | // update keyboard - enabled or disabled?
|
||
352 | base.toggle(); |
||
353 | |||
354 | // show keyboard
|
||
355 | base.$keyboard.show();
|
||
356 | |||
357 | // adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
|
||
358 | if (o.usePreview && $keyboard.msie) { |
||
359 | if (typeof base.width === 'undefined') { |
||
360 | base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing |
||
361 | base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row |
||
362 | base.$preview.show();
|
||
363 | } |
||
364 | base.$preview.width(base.width);
|
||
365 | } |
||
366 | |||
367 | base.position = $.isEmptyObject(o.position) ? false : o.position; |
||
368 | |||
369 | // position after keyboard is visible (required for UI position utility) and appropriately sized
|
||
370 | if ($.ui && $.ui.position && base.position) { |
||
371 | // get single target position || target stored in element data (multiple targets) || default @ element
|
||
372 | base.position.of = base.position.of || base.$el.data('keyboardPosition') || base.$el; |
||
373 | base.position.collision = base.position.collision || 'flipfit flipfit';
|
||
374 | o.position.at = o.usePreview ? o.position.orig_at : o.position.at2; |
||
375 | base.$keyboard.position(base.position);
|
||
376 | } |
||
377 | |||
378 | base.checkDecimal(); |
||
379 | |||
380 | // get preview area line height
|
||
381 | // add roughly 4px to get line height from font height, works well for font-sizes from 14-36px
|
||
382 | // needed for textareas
|
||
383 | base.lineHeight = parseInt(base.$preview.css('lineHeight'), 10) || |
||
384 | parseInt(base.$preview.css('font-size'), 10) + 4; |
||
385 | |||
386 | if (o.caretToEnd) {
|
||
387 | base.saveCaret(base.originalContent.length, base.originalContent.length); |
||
388 | } |
||
389 | |||
390 | // IE caret haxx0rs
|
||
391 | if ($keyboard.allie) { |
||
392 | // sometimes end = 0 while start is > 0
|
||
393 | if (base.last.end === 0 && base.last.start > 0) { |
||
394 | base.last.end = base.last.start; |
||
395 | } |
||
396 | // IE will have start -1, end of 0 when not focused (see demo: https://jsfiddle.net/Mottie/fgryQ/3/)
|
||
397 | if (base.last.start < 0) { |
||
398 | // ensure caret is at the end of the text (needed for IE)
|
||
399 | base.last.start = base.last.end = base.originalContent.length; |
||
400 | } |
||
401 | } |
||
402 | |||
403 | if (alreadyOpen || redraw) {
|
||
404 | // restore caret position (userClosed)
|
||
405 | $keyboard.caret(base.$preview, base.last); |
||
406 | return base;
|
||
407 | } |
||
408 | |||
409 | // opening keyboard flag; delay allows switching between keyboards without immediately closing
|
||
410 | // the keyboard
|
||
411 | base.timer2 = setTimeout(function () {
|
||
412 | var undef;
|
||
413 | base.opening = false;
|
||
414 | // Number inputs don't support selectionStart and selectionEnd
|
||
415 | // Number/email inputs don't support selectionStart and selectionEnd
|
||
416 | if (!/(number|email)/i.test(base.el.type) && !o.caretToEnd) { |
||
417 | // caret position is always 0,0 in webkit; and nothing is focused at this point... odd
|
||
418 | // save caret position in the input to transfer it to the preview
|
||
419 | // inside delay to get correct caret position
|
||
420 | base.saveCaret(undef, undef, base.$el);
|
||
421 | } |
||
422 | if (o.initialFocus) {
|
||
423 | $keyboard.caret(base.$preview, base.last); |
||
424 | } |
||
425 | // save event time for keyboards with stayOpen: true
|
||
426 | base.last.eventTime = new Date().getTime();
|
||
427 | base.$el.trigger($keyboard.events.kbVisible, [base, base.el]); |
||
428 | base.timer = setTimeout(function () {
|
||
429 | // get updated caret information after visible event - fixes #331
|
||
430 | if (base) { // Check if base exists, this is a case when destroy is called, before timers fire |
||
431 | base.saveCaret(); |
||
432 | } |
||
433 | }, 200);
|
||
434 | }, 10);
|
||
435 | // return base to allow chaining in typing extension
|
||
436 | return base;
|
||
437 | }; |
||
438 | |||
439 | base.updateLanguage = function () { |
||
440 | // change language if layout is named something like 'french-azerty-1'
|
||
441 | var layouts = $keyboard.layouts, |
||
442 | lang = o.language || layouts[o.layout] && layouts[o.layout].lang && |
||
443 | layouts[o.layout].lang || [o.language || 'en'],
|
||
444 | kblang = $keyboard.language;
|
||
445 | |||
446 | // some languages include a dash, e.g. 'en-gb' or 'fr-ca'
|
||
447 | // allow o.language to be a string or array...
|
||
448 | // array is for future expansion where a layout can be set for multiple languages
|
||
449 | lang = ($.isArray(lang) ? lang[0] : lang).split('-')[0]; |
||
450 | |||
451 | // set keyboard language
|
||
452 | o.display = $.extend(true, {}, |
||
453 | kblang.en.display, |
||
454 | kblang[lang] && kblang[lang].display || {}, |
||
455 | base.settings.display |
||
456 | ); |
||
457 | o.combos = $.extend(true, {}, |
||
458 | kblang.en.combos, |
||
459 | kblang[lang] && kblang[lang].combos || {}, |
||
460 | base.settings.combos |
||
461 | ); |
||
462 | o.wheelMessage = kblang[lang] && kblang[lang].wheelMessage || kblang.en.wheelMessage; |
||
463 | // rtl can be in the layout or in the language definition; defaults to false
|
||
464 | o.rtl = layouts[o.layout] && layouts[o.layout].rtl || kblang[lang] && kblang[lang].rtl || false;
|
||
465 | |||
466 | // save default regex (in case loading another layout changes it)
|
||
467 | base.regex = kblang[lang] && kblang[lang].comboRegex || $keyboard.comboRegex;
|
||
468 | // determine if US '.' or European ',' system being used
|
||
469 | base.decimal = /^\./.test(o.display.dec);
|
||
470 | base.$el
|
||
471 | .toggleClass('rtl', o.rtl)
|
||
472 | .css('direction', o.rtl ? 'rtl' : ''); |
||
473 | }; |
||
474 | |||
475 | base.startup = function () { |
||
476 | var kbcss = $keyboard.css; |
||
477 | // ensure base.$preview is defined; but don't overwrite it if keyboard is always visible
|
||
478 | if (!((o.alwaysOpen || o.userClosed) && base.$preview)) { |
||
479 | base.makePreview(); |
||
480 | } |
||
481 | if (!(base.$keyboard && base.$keyboard.length)) { |
||
482 | // custom layout - create a unique layout name based on the hash
|
||
483 | if (o.layout === 'custom') { |
||
484 | o.layoutHash = 'custom' + base.customHash();
|
||
485 | } |
||
486 | base.layout = o.layout === 'custom' ? o.layoutHash : o.layout;
|
||
487 | base.last.layout = base.layout; |
||
488 | |||
489 | base.updateLanguage(); |
||
490 | if (typeof $keyboard.builtLayouts[base.layout] === 'undefined') { |
||
491 | if ($.isFunction(o.create)) { |
||
492 | // create must call buildKeyboard() function; or create it's own keyboard
|
||
493 | base.$keyboard = o.create(base);
|
||
494 | } else if (!base.$keyboard.length) { |
||
495 | base.buildKeyboard(base.layout, true);
|
||
496 | } |
||
497 | } |
||
498 | base.$keyboard = $keyboard.builtLayouts[base.layout].$keyboard.clone(); |
||
499 | base.$keyboard.data('keyboard', base); |
||
500 | if ((base.el.id || '') !== '') { |
||
501 | // add ID to keyboard for styling purposes
|
||
502 | base.$keyboard.attr('id', base.el.id + $keyboard.css.idSuffix); |
||
503 | } |
||
504 | |||
505 | base.makePreview(); |
||
506 | // build preview display
|
||
507 | if (o.usePreview) {
|
||
508 | // restore original positioning (in case usePreview option is altered)
|
||
509 | if (!$.isEmptyObject(o.position)) { |
||
510 | o.position.at = o.position.orig_at; |
||
511 | } |
||
512 | } else {
|
||
513 | // No preview display, use element and reposition the keyboard under it.
|
||
514 | if (!$.isEmptyObject(o.position)) { |
||
515 | o.position.at = o.position.at2; |
||
516 | } |
||
517 | } |
||
518 | |||
519 | } |
||
520 | |||
521 | base.$decBtn = base.$keyboard.find('.' + kbcss.keyPrefix + 'dec'); |
||
522 | // add enter to allowed keys; fixes #190
|
||
523 | if (o.enterNavigation || base.el.nodeName === 'TEXTAREA') { |
||
524 | base.alwaysAllowed.push(13);
|
||
525 | } |
||
526 | |||
527 | base.bindKeyboard(); |
||
528 | |||
529 | base.$keyboard.appendTo(o.appendLocally ? base.$el.parent() : o.appendTo || 'body'); |
||
530 | |||
531 | base.bindKeys(); |
||
532 | |||
533 | // adjust with window resize; don't check base.position
|
||
534 | // here in case it is changed dynamically
|
||
535 | if (o.reposition && $.ui && $.ui.position && o.appendTo == 'body') { |
||
536 | $(window).bind('resize' + base.namespace, function () { |
||
537 | if (base.position && base.isVisible()) {
|
||
538 | base.$keyboard.position(base.position);
|
||
539 | } |
||
540 | }); |
||
541 | } |
||
542 | |||
543 | }; |
||
544 | |||
545 | base.makePreview = function () { |
||
546 | if (o.usePreview) {
|
||
547 | var indx, attrs, attr, removedAttr,
|
||
548 | kbcss = $keyboard.css;
|
||
549 | base.$preview = base.$el.clone(false) |
||
550 | .data('keyboard', base)
|
||
551 | .removeClass(kbcss.placeholder + ' ' + kbcss.input)
|
||
552 | .addClass(kbcss.preview + ' ' + o.css.input)
|
||
553 | .attr('tabindex', '-1') |
||
554 | .show(); // for hidden inputs
|
||
555 | base.preview = base.$preview[0]; |
||
556 | |||
557 | // Switch the number input field to text so the caret positioning will work again
|
||
558 | if (base.preview.type === 'number') { |
||
559 | base.preview.type = 'text';
|
||
560 | } |
||
561 | |||
562 | // remove extraneous attributes.
|
||
563 | removedAttr = /^(data-|id|aria-haspopup)/i;
|
||
564 | attrs = base.$preview.get(0).attributes; |
||
565 | for (indx = attrs.length - 1; indx >= 0; indx--) { |
||
566 | attr = attrs[indx] && attrs[indx].name; |
||
567 | if (removedAttr.test(attr)) {
|
||
568 | // remove data-attributes - see #351
|
||
569 | base.preview.removeAttribute(attr); |
||
570 | } |
||
571 | } |
||
572 | // build preview container and append preview display
|
||
573 | $('<div />') |
||
574 | .addClass(kbcss.wrapper) |
||
575 | .append(base.$preview)
|
||
576 | .prependTo(base.$keyboard);
|
||
577 | } else {
|
||
578 | base.$preview = base.$el; |
||
579 | base.preview = base.el; |
||
580 | } |
||
581 | }; |
||
582 | |||
583 | base.saveCaret = function (start, end, $el) { |
||
584 | var p = $keyboard.caret($el || base.$preview, start, end); |
||
585 | base.last.start = typeof start === 'undefined' ? p.start : start; |
||
586 | base.last.end = typeof end === 'undefined' ? p.end : end; |
||
587 | }; |
||
588 | |||
589 | base.setScroll = function () { |
||
590 | // Set scroll so caret & current text is in view
|
||
591 | // needed for virtual keyboard typing, NOT manual typing - fixes #23
|
||
592 | if (base.last.virtual) {
|
||
593 | |||
594 | var scrollWidth, clientWidth, adjustment, direction,
|
||
595 | isTextarea = base.preview.nodeName === 'TEXTAREA',
|
||
596 | value = base.last.val.substring(0, Math.max(base.last.start, base.last.end));
|
||
597 | |||
598 | if (!base.$previewCopy) { |
||
599 | // clone preview
|
||
600 | base.$previewCopy = base.$preview.clone() |
||
601 | .removeAttr('id') // fixes #334 |
||
602 | .css({ |
||
603 | position: 'absolute', |
||
604 | left: 0, |
||
605 | zIndex: -10, |
||
606 | visibility: 'hidden' |
||
607 | }) |
||
608 | .addClass($keyboard.css.inputClone);
|
||
609 | if (!isTextarea) {
|
||
610 | // make input zero-width because we need an accurate scrollWidth
|
||
611 | base.$previewCopy.css({
|
||
612 | 'white-space': 'pre', |
||
613 | 'width': 0 |
||
614 | }); |
||
615 | } |
||
616 | if (o.usePreview) {
|
||
617 | // add clone inside of preview wrapper
|
||
618 | base.$preview.after(base.$previewCopy); |
||
619 | } else {
|
||
620 | // just slap that thing in there somewhere
|
||
621 | base.$keyboard.prepend(base.$previewCopy); |
||
622 | } |
||
623 | } |
||
624 | |||
625 | if (isTextarea) {
|
||
626 | // need the textarea scrollHeight, so set the clone textarea height to be the line height
|
||
627 | base.$previewCopy
|
||
628 | .height(base.lineHeight) |
||
629 | .val(value); |
||
630 | // set scrollTop for Textarea
|
||
631 | base.preview.scrollTop = base.lineHeight * |
||
632 | (Math.floor(base.$previewCopy[0].scrollHeight / base.lineHeight) - 1); |
||
633 | } else {
|
||
634 | // add non-breaking spaces
|
||
635 | base.$previewCopy.val(value.replace(/\s/g, '\xa0')); |
||
636 | |||
637 | // if scrollAdjustment option is set to "c" or "center" then center the caret
|
||
638 | adjustment = /c/i.test(o.scrollAdjustment) ? base.preview.clientWidth / 2 : o.scrollAdjustment; |
||
639 | scrollWidth = base.$previewCopy[0].scrollWidth - 1; |
||
640 | |||
641 | // set initial state as moving right
|
||
642 | if (typeof base.last.scrollWidth === 'undefined') { |
||
643 | base.last.scrollWidth = scrollWidth; |
||
644 | base.last.direction = true;
|
||
645 | } |
||
646 | // if direction = true; we're scrolling to the right
|
||
647 | direction = base.last.scrollWidth === scrollWidth ? |
||
648 | base.last.direction : |
||
649 | base.last.scrollWidth < scrollWidth; |
||
650 | clientWidth = base.preview.clientWidth - adjustment; |
||
651 | |||
652 | // set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
|
||
653 | // hug right while scrolling right...
|
||
654 | if (direction) {
|
||
655 | if (scrollWidth < clientWidth) {
|
||
656 | base.preview.scrollLeft = 0;
|
||
657 | } else {
|
||
658 | base.preview.scrollLeft = scrollWidth - clientWidth; |
||
659 | } |
||
660 | } else {
|
||
661 | // hug left while scrolling left...
|
||
662 | if (scrollWidth >= base.preview.scrollWidth - clientWidth) {
|
||
663 | base.preview.scrollLeft = base.preview.scrollWidth - adjustment; |
||
664 | } else if (scrollWidth - adjustment > 0) { |
||
665 | base.preview.scrollLeft = scrollWidth - adjustment; |
||
666 | } else {
|
||
667 | base.preview.scrollLeft = 0;
|
||
668 | } |
||
669 | } |
||
670 | |||
671 | base.last.scrollWidth = scrollWidth; |
||
672 | base.last.direction = direction; |
||
673 | } |
||
674 | } |
||
675 | }; |
||
676 | |||
677 | base.bindFocus = function () { |
||
678 | if (o.openOn) {
|
||
679 | // make sure keyboard isn't destroyed
|
||
680 | // Check if base exists, this is a case when destroy is called, before timers have fired
|
||
681 | if (base && base.el.active) {
|
||
682 | base.$el.bind(o.openOn + base.namespace, function () { |
||
683 | base.focusOn(); |
||
684 | }); |
||
685 | // remove focus from element (needed for IE since blur doesn't seem to work)
|
||
686 | if ($(':focus')[0] === base.el) { |
||
687 | base.$el.blur();
|
||
688 | } |
||
689 | } |
||
690 | } |
||
691 | }; |
||
692 | |||
693 | base.bindKeyboard = function () { |
||
694 | var evt,
|
||
695 | keyCodes = $keyboard.keyCodes,
|
||
696 | layout = $keyboard.builtLayouts[base.layout];
|
||
697 | base.$preview
|
||
698 | .unbind(base.namespace) |
||
699 | .bind('click' + base.namespace + ' touchstart' + base.namespace, function () { |
||
700 | if (o.alwaysOpen && !base.isCurrent()) {
|
||
701 | base.reveal(); |
||
702 | } |
||
703 | // update last caret position after user click, use at least 150ms or it doesn't work in IE
|
||
704 | base.timer2 = setTimeout(function () {
|
||
705 | if (base){
|
||
706 | base.saveCaret(); |
||
707 | } |
||
708 | }, 150);
|
||
709 | |||
710 | }) |
||
711 | .bind('keypress' + base.namespace, function (e) { |
||
712 | if (o.lockInput || !base.isCurrent()) {
|
||
713 | return false; |
||
714 | } |
||
715 | var k = e.charCode || e.which,
|
||
716 | // capsLock can only be checked while typing a-z
|
||
717 | k1 = k >= keyCodes.A && k <= keyCodes.Z, |
||
718 | k2 = k >= keyCodes.a && k <= keyCodes.z, |
||
719 | str = base.last.key = String.fromCharCode(k); |
||
720 | base.last.virtual = false;
|
||
721 | base.last.event = e; |
||
722 | base.last.$key = []; // not a virtual keyboard key |
||
723 | if (base.checkCaret) {
|
||
724 | base.saveCaret(); |
||
725 | } |
||
726 | |||
727 | // update capsLock
|
||
728 | if (k !== keyCodes.capsLock && (k1 || k2)) {
|
||
729 | base.capsLock = (k1 && !e.shiftKey) || (k2 && e.shiftKey); |
||
730 | // if shifted keyset not visible, then show it
|
||
731 | if (base.capsLock && !base.shiftActive) {
|
||
732 | base.shiftActive = true;
|
||
733 | base.showSet(); |
||
734 | } |
||
735 | } |
||
736 | |||
737 | // restrict input - keyCode in keypress special keys:
|
||
738 | // see http://www.asquare.net/javascript/tests/KeyCode.html
|
||
739 | if (o.restrictInput) {
|
||
740 | // allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
|
||
741 | if ((e.which === keyCodes.backSpace || e.which === 0) && |
||
742 | $.inArray(e.keyCode, base.alwaysAllowed)) {
|
||
743 | return;
|
||
744 | } |
||
745 | // quick key check
|
||
746 | if ($.inArray(str, layout.acceptedKeys) === -1) { |
||
747 | e.preventDefault(); |
||
748 | // copy event object in case e.preventDefault() breaks when changing the type
|
||
749 | evt = $.extend({}, e);
|
||
750 | evt.type = $keyboard.events.inputRestricted;
|
||
751 | base.$el.trigger(evt, [base, base.el]);
|
||
752 | } |
||
753 | } else if ((e.ctrlKey || e.metaKey) && |
||
754 | (e.which === keyCodes.A || e.which === keyCodes.C || e.which === keyCodes.V || |
||
755 | (e.which >= keyCodes.X && e.which <= keyCodes.Z))) { |
||
756 | // Allow select all (ctrl-a), copy (ctrl-c), paste (ctrl-v) & cut (ctrl-x) &
|
||
757 | // redo (ctrl-y)& undo (ctrl-z); meta key for mac
|
||
758 | return;
|
||
759 | } |
||
760 | // Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
|
||
761 | // Set up a key in the layout as follows: 'm(a):label'; m = key to map, (a) = actual keyboard key
|
||
762 | // to map to (optional), ':label' = title/tooltip (optional)
|
||
763 | // example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
|
||
764 | if (layout.hasMappedKeys && layout.mappedKeys.hasOwnProperty(str)) {
|
||
765 | base.last.key = layout.mappedKeys[str]; |
||
766 | base.insertText(base.last.key); |
||
767 | e.preventDefault(); |
||
768 | } |
||
769 | if (typeof o.beforeInsert === 'function') { |
||
770 | base.insertText(base.last.key); |
||
771 | e.preventDefault(); |
||
772 | } |
||
773 | base.checkMaxLength(); |
||
774 | |||
775 | }) |
||
776 | .bind('keyup' + base.namespace, function (e) { |
||
777 | if (!base.isCurrent()) { return; } |
||
778 | base.last.virtual = false;
|
||
779 | switch (e.which) {
|
||
780 | // Insert tab key
|
||
781 | case keyCodes.tab:
|
||
782 | // Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab
|
||
783 | // to the keyboard preview area on keyup. Sadly it still happens if you don't release the tab
|
||
784 | // key immediately because keydown event auto-repeats
|
||
785 | if (base.tab && o.tabNavigation && !o.lockInput) {
|
||
786 | base.shiftActive = e.shiftKey; |
||
787 | // when switching inputs, the tab keyaction returns false
|
||
788 | var notSwitching = $keyboard.keyaction.tab(base); |
||
789 | base.tab = false;
|
||
790 | if (!notSwitching) {
|
||
791 | return false; |
||
792 | } |
||
793 | } else {
|
||
794 | e.preventDefault(); |
||
795 | } |
||
796 | break;
|
||
797 | |||
798 | // Escape will hide the keyboard
|
||
799 | case keyCodes.escape:
|
||
800 | if (!o.ignoreEsc) {
|
||
801 | base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false); |
||
802 | } |
||
803 | return false; |
||
804 | } |
||
805 | |||
806 | // throttle the check combo function because fast typers will have an incorrectly positioned caret
|
||
807 | clearTimeout(base.throttled); |
||
808 | base.throttled = setTimeout(function () {
|
||
809 | // fix error in OSX? see issue #102
|
||
810 | if (base && base.isVisible()) {
|
||
811 | base.checkCombos(); |
||
812 | } |
||
813 | }, 100);
|
||
814 | |||
815 | base.checkMaxLength(); |
||
816 | |||
817 | base.last.preVal = '' + base.last.val;
|
||
818 | base.last.val = base.$preview.val();
|
||
819 | e.type = $keyboard.events.kbChange;
|
||
820 | // base.last.key may be empty string (shift, enter, tab, etc) when keyboard is first visible
|
||
821 | // use e.key instead, if browser supports it
|
||
822 | e.action = base.last.key; |
||
823 | base.$el.trigger(e, [base, base.el]);
|
||
824 | |||
825 | // change callback is no longer bound to the input element as the callback could be
|
||
826 | // called during an external change event with all the necessary parameters (issue #157)
|
||
827 | if ($.isFunction(o.change)) { |
||
828 | e.type = $keyboard.events.inputChange;
|
||
829 | o.change(e, base, base.el); |
||
830 | return false; |
||
831 | } |
||
832 | if (o.acceptValid && o.autoAcceptOnValid) {
|
||
833 | if ($.isFunction(o.validate) && o.validate(base, base.$preview.val())) { |
||
834 | base.$preview.blur();
|
||
835 | base.accept(); |
||
836 | } |
||
837 | } |
||
838 | }) |
||
839 | .bind('keydown' + base.namespace, function (e) { |
||
840 | // ensure alwaysOpen keyboards are made active
|
||
841 | if (o.alwaysOpen && !base.isCurrent()) {
|
||
842 | base.reveal(); |
||
843 | } |
||
844 | // prevent tab key from leaving the preview window
|
||
845 | if (e.which === keyCodes.tab) {
|
||
846 | // allow tab to pass through - tab to next input/shift-tab for prev
|
||
847 | base.tab = true;
|
||
848 | return false; |
||
849 | } |
||
850 | |||
851 | if (o.lockInput) {
|
||
852 | return false; |
||
853 | } |
||
854 | |||
855 | base.last.virtual = false;
|
||
856 | switch (e.which) {
|
||
857 | |||
858 | case keyCodes.backSpace:
|
||
859 | $keyboard.keyaction.bksp(base, null, e); |
||
860 | e.preventDefault(); |
||
861 | break;
|
||
862 | |||
863 | case keyCodes.enter:
|
||
864 | $keyboard.keyaction.enter(base, null, e); |
||
865 | break;
|
||
866 | |||
867 | // Show capsLock
|
||
868 | case keyCodes.capsLock:
|
||
869 | base.shiftActive = base.capsLock = !base.capsLock; |
||
870 | base.showSet(); |
||
871 | break;
|
||
872 | |||
873 | case keyCodes.V:
|
||
874 | // prevent ctrl-v/cmd-v
|
||
875 | if (e.ctrlKey || e.metaKey) {
|
||
876 | if (o.preventPaste) {
|
||
877 | e.preventDefault(); |
||
878 | return;
|
||
879 | } |
||
880 | base.checkCombos(); // check pasted content
|
||
881 | } |
||
882 | break;
|
||
883 | } |
||
884 | }) |
||
885 | .bind('mouseup touchend '.split(' ').join(base.namespace + ' '), function () { |
||
886 | base.last.virtual = true;
|
||
887 | base.saveCaret(); |
||
888 | }); |
||
889 | |||
890 | // prevent keyboard event bubbling
|
||
891 | base.$keyboard.bind('mousedown click touchstart '.split(' ').join(base.namespace + ' '), function (e) { |
||
892 | e.stopPropagation(); |
||
893 | if (!base.isCurrent()) {
|
||
894 | base.reveal(); |
||
895 | $(document).trigger('checkkeyboard' + base.namespace); |
||
896 | } |
||
897 | if (!o.noFocus && base.$preview) { |
||
898 | base.$preview.focus();
|
||
899 | } |
||
900 | }); |
||
901 | |||
902 | // If preventing paste, block context menu (right click)
|
||
903 | if (o.preventPaste) {
|
||
904 | base.$preview.bind('contextmenu' + base.namespace, function (e) { |
||
905 | e.preventDefault(); |
||
906 | }); |
||
907 | base.$el.bind('contextmenu' + base.namespace, function (e) { |
||
908 | e.preventDefault(); |
||
909 | }); |
||
910 | } |
||
911 | |||
912 | }; |
||
913 | |||
914 | base.bindKeys = function () { |
||
915 | var kbcss = $keyboard.css; |
||
916 | base.$allKeys = base.$keyboard.find('button.' + kbcss.keyButton) |
||
917 | .unbind(base.namespace + ' ' + base.namespace + 'kb') |
||
918 | // Change hover class and tooltip - moved this touchstart before option.keyBinding touchstart
|
||
919 | // to prevent mousewheel lag/duplication - Fixes #379 & #411
|
||
920 | .bind('mouseenter mouseleave touchstart '.split(' ').join(base.namespace + ' '), function (e) { |
||
921 | if ((o.alwaysOpen || o.userClosed) && e.type !== 'mouseleave' && !base.isCurrent()) { |
||
922 | base.reveal(); |
||
923 | base.$preview.focus();
|
||
924 | $keyboard.caret(base.$preview, base.last); |
||
925 | } |
||
926 | if (!base.isCurrent()) {
|
||
927 | return;
|
||
928 | } |
||
929 | var $keys, txt, |
||
930 | last = base.last, |
||
931 | $this = $(this), |
||
932 | type = e.type; |
||
933 | |||
934 | if (o.useWheel && base.wheel) {
|
||
935 | $keys = base.getLayers($this); |
||
936 | txt = ($keys.length ? $keys.map(function () { |
||
937 | return $(this).attr('data-value') || ''; |
||
938 | }) |
||
939 | .get() : '') || [$this.text()]; |
||
940 | last.wheel_$Keys = $keys; |
||
941 | last.wheelLayers = txt; |
||
942 | last.wheelIndex = $.inArray($this.attr('data-value'), txt); |
||
943 | } |
||
944 | |||
945 | if ((type === 'mouseenter' || type === 'touchstart') && base.el.type !== 'password' && |
||
946 | !$this.hasClass(o.css.buttonDisabled)) {
|
||
947 | $this.addClass(o.css.buttonHover);
|
||
948 | if (o.useWheel && base.wheel) {
|
||
949 | $this.attr('title', function (i, t) { |
||
950 | // show mouse wheel message
|
||
951 | return (base.wheel && t === '' && base.sets && txt.length > 1 && type !== 'touchstart') ? |
||
952 | o.wheelMessage : t; |
||
953 | }); |
||
954 | } |
||
955 | } |
||
956 | if (type === 'mouseleave') { |
||
957 | // needed or IE flickers really bad
|
||
958 | $this.removeClass((base.el.type === 'password') ? '' : o.css.buttonHover); |
||
959 | if (o.useWheel && base.wheel) {
|
||
960 | last.wheelIndex = 0;
|
||
961 | last.wheelLayers = []; |
||
962 | last.wheel_$Keys = null; |
||
963 | $this
|
||
964 | .attr('title', function (i, t) { |
||
965 | return (t === o.wheelMessage) ? '' : t; |
||
966 | }) |
||
967 | .html($this.attr('data-html')); // restore original button text |
||
968 | } |
||
969 | } |
||
970 | }) |
||
971 | // keyBinding = 'mousedown touchstart' by default
|
||
972 | .bind(o.keyBinding.split(' ').join(base.namespace + ' ') + base.namespace + ' ' + |
||
973 | $keyboard.events.kbRepeater, function (e) { |
||
974 | e.preventDefault(); |
||
975 | // prevent errors when external triggers attempt to 'type' - see issue #158
|
||
976 | if (!base.$keyboard.is(':visible')) { |
||
977 | return false; |
||
978 | } |
||
979 | var action, $keys, |
||
980 | last = base.last, |
||
981 | key = this,
|
||
982 | $key = $(key), |
||
983 | // prevent mousedown & touchstart from both firing events at the same time - see #184
|
||
984 | timer = new Date().getTime();
|
||
985 | |||
986 | if (o.useWheel && base.wheel) {
|
||
987 | // get keys from other layers/keysets (shift, alt, meta, etc) that line up by data-position
|
||
988 | $keys = last.wheel_$Keys; |
||
989 | // target mousewheel selected key
|
||
990 | $key = $keys && last.wheelIndex > -1 ? $keys.eq(last.wheelIndex) : $key; |
||
991 | } |
||
992 | action = $key.attr('data-action'); |
||
993 | if (timer - (last.eventTime || 0) < o.preventDoubleEventTime) { |
||
994 | return;
|
||
995 | } |
||
996 | last.eventTime = timer; |
||
997 | last.event = e; |
||
998 | last.virtual = true;
|
||
999 | if (!o.noFocus) {
|
||
1000 | base.$preview.focus();
|
||
1001 | } |
||
1002 | last.$key = $key; |
||
1003 | last.key = $key.attr('data-value'); |
||
1004 | // Start caret in IE when not focused (happens with each virtual keyboard button click
|
||
1005 | if (base.checkCaret) {
|
||
1006 | $keyboard.caret(base.$preview, last); |
||
1007 | } |
||
1008 | if (action.match('meta')) { |
||
1009 | action = 'meta';
|
||
1010 | } |
||
1011 | // keyaction is added as a string, override original action & text
|
||
1012 | if (action === last.key && typeof $keyboard.keyaction[action] === 'string') { |
||
1013 | last.key = action = $keyboard.keyaction[action];
|
||
1014 | } else if (action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[action])) { |
||
1015 | // stop processing if action returns false (close & cancel)
|
||
1016 | if ($keyboard.keyaction[action](base, this, e) === false) { |
||
1017 | return false; |
||
1018 | } |
||
1019 | action = null; // prevent inserting action name |
||
1020 | } |
||
1021 | if (typeof action !== 'undefined' && action !== null) { |
||
1022 | last.key = $(this).hasClass(kbcss.keyAction) ? action : last.key; |
||
1023 | base.insertText(last.key); |
||
1024 | if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
|
||
1025 | base.shiftActive = false;
|
||
1026 | base.showSet($key.attr('data-name')); |
||
1027 | } |
||
1028 | } |
||
1029 | // set caret if caret moved by action function; also, attempt to fix issue #131
|
||
1030 | $keyboard.caret(base.$preview, last); |
||
1031 | base.checkCombos(); |
||
1032 | e.type = $keyboard.events.kbChange;
|
||
1033 | e.action = last.key; |
||
1034 | base.$el.trigger(e, [base, base.el]);
|
||
1035 | last.preVal = '' + last.val;
|
||
1036 | last.val = base.$preview.val();
|
||
1037 | |||
1038 | if ($.isFunction(o.change)) { |
||
1039 | e.type = $keyboard.events.inputChange;
|
||
1040 | o.change(e, base, base.el); |
||
1041 | // return false to prevent reopening keyboard if base.accept() was called
|
||
1042 | return false; |
||
1043 | } |
||
1044 | |||
1045 | }) |
||
1046 | // using 'kb' namespace for mouse repeat functionality to keep it separate
|
||
1047 | // I need to trigger a 'repeater.keyboard' to make it work
|
||
1048 | .bind('mouseup' + base.namespace + ' ' + 'mouseleave touchend touchmove touchcancel '.split(' ') |
||
1049 | .join(base.namespace + 'kb '), function (e) { |
||
1050 | base.last.virtual = true;
|
||
1051 | var offset,
|
||
1052 | $this = $(this); |
||
1053 | if (e.type === 'touchmove') { |
||
1054 | // if moving within the same key, don't stop repeating
|
||
1055 | offset = $this.offset();
|
||
1056 | offset.right = offset.left + $this.outerWidth();
|
||
1057 | offset.bottom = offset.top + $this.outerHeight();
|
||
1058 | if (e.originalEvent.touches[0].pageX >= offset.left && |
||
1059 | e.originalEvent.touches[0].pageX < offset.right &&
|
||
1060 | e.originalEvent.touches[0].pageY >= offset.top &&
|
||
1061 | e.originalEvent.touches[0].pageY < offset.bottom) {
|
||
1062 | return true; |
||
1063 | } |
||
1064 | } else if (/(mouseleave|touchend|touchcancel)/i.test(e.type)) { |
||
1065 | $this.removeClass(o.css.buttonHover); // needed for touch devices |
||
1066 | } else {
|
||
1067 | if (!o.noFocus && base.isCurrent() && base.isVisible()) {
|
||
1068 | base.$preview.focus();
|
||
1069 | } |
||
1070 | if (base.checkCaret) {
|
||
1071 | $keyboard.caret(base.$preview, base.last); |
||
1072 | } |
||
1073 | } |
||
1074 | base.mouseRepeat = [false, '']; |
||
1075 | clearTimeout(base.repeater); // make sure key repeat stops!
|
||
1076 | if (o.acceptValid && o.autoAcceptOnValid) {
|
||
1077 | if ($.isFunction(o.validate) && o.validate(base, base.$preview.val())) { |
||
1078 | base.$preview.blur();
|
||
1079 | base.accept(); |
||
1080 | } |
||
1081 | } |
||
1082 | return false; |
||
1083 | }) |
||
1084 | // prevent form submits when keyboard is bound locally - issue #64
|
||
1085 | .bind('click' + base.namespace, function () { |
||
1086 | return false; |
||
1087 | }) |
||
1088 | // no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
|
||
1089 | .not('.' + kbcss.keyAction)
|
||
1090 | // Allow mousewheel to scroll through other keysets of the same (non-action) key
|
||
1091 | .bind('mousewheel' + base.namespace, function (e, delta) { |
||
1092 | if (o.useWheel && base.wheel) {
|
||
1093 | // deltaY used by newer versions of mousewheel plugin
|
||
1094 | delta = delta || e.deltaY; |
||
1095 | var n,
|
||
1096 | txt = base.last.wheelLayers || []; |
||
1097 | if (txt.length > 1) { |
||
1098 | n = base.last.wheelIndex + (delta > 0 ? -1 : 1); |
||
1099 | if (n > txt.length - 1) { |
||
1100 | n = 0;
|
||
1101 | } |
||
1102 | if (n < 0) { |
||
1103 | n = txt.length - 1;
|
||
1104 | } |
||
1105 | } else {
|
||
1106 | n = 0;
|
||
1107 | } |
||
1108 | base.last.wheelIndex = n; |
||
1109 | $(this).html(txt[n]); |
||
1110 | return false; |
||
1111 | } |
||
1112 | }) |
||
1113 | // mouse repeated action key exceptions
|
||
1114 | .add('.' + kbcss.keyPrefix + ('tab bksp space enter'.split(' ') |
||
1115 | .join(',.' + kbcss.keyPrefix)), base.$keyboard) |
||
1116 | .bind('mousedown touchstart '.split(' ').join(base.namespace + 'kb '), function () { |
||
1117 | if (o.repeatRate !== 0) { |
||
1118 | var key = $(this); |
||
1119 | // save the key, make sure we are repeating the right one (fast typers)
|
||
1120 | base.mouseRepeat = [true, key];
|
||
1121 | setTimeout(function () {
|
||
1122 | // don't repeat keys if it is disabled - see #431
|
||
1123 | if (base && base.mouseRepeat[0] && base.mouseRepeat[1] === key && !key[0].disabled) { |
||
1124 | base.repeatKey(key); |
||
1125 | } |
||
1126 | }, o.repeatDelay); |
||
1127 | } |
||
1128 | return false; |
||
1129 | }); |
||
1130 | }; |
||
1131 | |||
1132 | // Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
|
||
1133 | base.insertText = function (txt) { |
||
1134 | if (typeof o.beforeInsert === 'function') { |
||
1135 | txt = o.beforeInsert(base.last.event, base, base.el, txt); |
||
1136 | } |
||
1137 | if (typeof txt === 'undefined' || txt === false) { |
||
1138 | base.last.key = '';
|
||
1139 | return;
|
||
1140 | } |
||
1141 | var bksp, t,
|
||
1142 | isBksp = txt === '\b',
|
||
1143 | // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
|
||
1144 | val = base.$preview.val(),
|
||
1145 | pos = $keyboard.caret(base.$preview), |
||
1146 | len = val.length; // save original content length
|
||
1147 | |||
1148 | // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
|
||
1149 | // is still difficult
|
||
1150 | // in IE, pos.end can be zero after input loses focus
|
||
1151 | if (pos.end < pos.start) {
|
||
1152 | pos.end = pos.start; |
||
1153 | } |
||
1154 | if (pos.start > len) {
|
||
1155 | pos.end = pos.start = len; |
||
1156 | } |
||
1157 | |||
1158 | if (base.preview.nodeName === 'TEXTAREA') { |
||
1159 | // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
|
||
1160 | if ($keyboard.msie && val.substr(pos.start, 1) === '\n') { |
||
1161 | pos.start += 1;
|
||
1162 | pos.end += 1;
|
||
1163 | } |
||
1164 | } |
||
1165 | |||
1166 | if (txt === '{d}') { |
||
1167 | txt = '';
|
||
1168 | t = pos.start; |
||
1169 | pos.end += 1;
|
||
1170 | } |
||
1171 | |||
1172 | bksp = isBksp && pos.start === pos.end; |
||
1173 | txt = isBksp ? '' : txt;
|
||
1174 | val = val.substr(0, pos.start - (bksp ? 1 : 0)) + txt + val.substr(pos.end); |
||
1175 | t = pos.start + (bksp ? -1 : txt.length);
|
||
1176 | |||
1177 | base.$preview.val(val);
|
||
1178 | base.saveCaret(t, t); // save caret in case of bksp
|
||
1179 | base.setScroll(); |
||
1180 | }; |
||
1181 | |||
1182 | // check max length
|
||
1183 | base.checkMaxLength = function () { |
||
1184 | if (!base.isCurrent()) { return; } |
||
1185 | var start, caret,
|
||
1186 | val = base.$preview.val();
|
||
1187 | if (o.maxLength !== false && val.length > o.maxLength) { |
||
1188 | start = $keyboard.caret(base.$preview).start; |
||
1189 | caret = Math.min(start, o.maxLength); |
||
1190 | |||
1191 | // prevent inserting new characters when maxed #289
|
||
1192 | if (!o.maxInsert) {
|
||
1193 | val = base.last.val; |
||
1194 | caret = start - 1; // move caret back one |
||
1195 | } |
||
1196 | |||
1197 | base.$preview.val(val.substring(0, o.maxLength)); |
||
1198 | // restore caret on change, otherwise it ends up at the end.
|
||
1199 | base.saveCaret(caret, caret); |
||
1200 | } |
||
1201 | if (base.$decBtn.length) { |
||
1202 | base.checkDecimal(); |
||
1203 | } |
||
1204 | }; |
||
1205 | |||
1206 | // mousedown repeater
|
||
1207 | base.repeatKey = function (key) { |
||
1208 | key.trigger($keyboard.events.kbRepeater);
|
||
1209 | if (base.mouseRepeat[0]) { |
||
1210 | base.repeater = setTimeout(function () {
|
||
1211 | if (base){
|
||
1212 | base.repeatKey(key); |
||
1213 | } |
||
1214 | }, base.repeatTime); |
||
1215 | } |
||
1216 | }; |
||
1217 | |||
1218 | // make it easier to switch keysets via API
|
||
1219 | // showKeySet('shift+alt+meta1')
|
||
1220 | base.showKeySet = function (str) { |
||
1221 | if (typeof str === 'string') { |
||
1222 | base.last.keyset = [base.shiftActive, base.altActive, base.metaActive]; |
||
1223 | base.shiftActive = /shift/i.test(str);
|
||
1224 | base.altActive = /alt/i.test(str);
|
||
1225 | if (/meta/.test(str)) { |
||
1226 | base.metaActive = true;
|
||
1227 | base.showSet(str.match(/meta\d+/i)[0]); |
||
1228 | } else {
|
||
1229 | base.metaActive = false;
|
||
1230 | base.showSet(); |
||
1231 | } |
||
1232 | } else {
|
||
1233 | base.showSet(str); |
||
1234 | } |
||
1235 | }; |
||
1236 | |||
1237 | base.showSet = function (name) { |
||
1238 | o = base.options; // refresh options
|
||
1239 | var kbcss = $keyboard.css, |
||
1240 | prefix = '.' + kbcss.keyPrefix,
|
||
1241 | active = o.css.buttonActive, |
||
1242 | key = '',
|
||
1243 | toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0); |
||
1244 | if (!base.shiftActive) {
|
||
1245 | base.capsLock = false;
|
||
1246 | } |
||
1247 | // check meta key set
|
||
1248 | if (base.metaActive) {
|
||
1249 | // the name attribute contains the meta set # 'meta99'
|
||
1250 | key = (/meta/i.test(name)) ? name : ''; |
||
1251 | // save active meta keyset name
|
||
1252 | if (key === '') { |
||
1253 | key = (base.metaActive === true) ? '' : base.metaActive; |
||
1254 | } else {
|
||
1255 | base.metaActive = key; |
||
1256 | } |
||
1257 | // if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
|
||
1258 | if ((!o.stickyShift && base.last.keyset[2] !== base.metaActive) || |
||
1259 | ((base.shiftActive || base.altActive) && |
||
1260 | !base.$keyboard.find('.' + kbcss.keySet + '-' + key + base.rows[toShow]).length)) { |
||
1261 | base.shiftActive = base.altActive = false;
|
||
1262 | } |
||
1263 | } else if (!o.stickyShift && base.last.keyset[2] !== base.metaActive && base.shiftActive) { |
||
1264 | // switching from meta key set back to default, reset shift & alt if using stickyShift
|
||
1265 | base.shiftActive = base.altActive = false;
|
||
1266 | } |
||
1267 | toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0); |
||
1268 | key = (toShow === 0 && !base.metaActive) ? '-normal' : (key === '') ? '' : '-' + key; |
||
1269 | if (!base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow]).length) { |
||
1270 | // keyset doesn't exist, so restore last keyset settings
|
||
1271 | base.shiftActive = base.last.keyset[0];
|
||
1272 | base.altActive = base.last.keyset[1];
|
||
1273 | base.metaActive = base.last.keyset[2];
|
||
1274 | return;
|
||
1275 | } |
||
1276 | base.$keyboard
|
||
1277 | .find(prefix + 'alt,' + prefix + 'shift,.' + kbcss.keyAction + '[class*=meta]') |
||
1278 | .removeClass(active) |
||
1279 | .end() |
||
1280 | .find(prefix + 'alt')
|
||
1281 | .toggleClass(active, base.altActive) |
||
1282 | .end() |
||
1283 | .find(prefix + 'shift')
|
||
1284 | .toggleClass(active, base.shiftActive) |
||
1285 | .end() |
||
1286 | .find(prefix + 'lock')
|
||
1287 | .toggleClass(active, base.capsLock) |
||
1288 | .end() |
||
1289 | .find('.' + kbcss.keySet)
|
||
1290 | .hide() |
||
1291 | .end() |
||
1292 | .find('.' + kbcss.keyAction + prefix + key)
|
||
1293 | .addClass(active); |
||
1294 | |||
1295 | // show keyset using inline-block ( extender layout will then line up )
|
||
1296 | base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow])[0].style.display = 'inline-block'; |
||
1297 | |||
1298 | if (base.metaActive) {
|
||
1299 | base.$keyboard.find(prefix + base.metaActive)
|
||
1300 | // base.metaActive contains the string "meta#" or false
|
||
1301 | // without the !== false, jQuery UI tries to transition the classes
|
||
1302 | .toggleClass(active, base.metaActive !== false);
|
||
1303 | } |
||
1304 | base.last.keyset = [base.shiftActive, base.altActive, base.metaActive]; |
||
1305 | base.$el.trigger($keyboard.events.kbKeysetChange, [base, base.el]); |
||
1306 | }; |
||
1307 | |||
1308 | // check for key combos (dead keys)
|
||
1309 | base.checkCombos = function () { |
||
1310 | // return val for close function
|
||
1311 | if (!(base.isVisible() || base.$keyboard.hasClass($keyboard.css.hasFocus))) { |
||
1312 | return base.$preview.val(); |
||
1313 | } |
||
1314 | var r, t, t2,
|
||
1315 | // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
|
||
1316 | val = base.$preview.val(),
|
||
1317 | pos = $keyboard.caret(base.$preview), |
||
1318 | layout = $keyboard.builtLayouts[base.layout],
|
||
1319 | len = val.length; // save original content length
|
||
1320 | // return if val is empty; fixes #352
|
||
1321 | if (val === '') { |
||
1322 | // check valid on empty string - see #429
|
||
1323 | if (o.acceptValid) {
|
||
1324 | base.checkValid(); |
||
1325 | } |
||
1326 | return val;
|
||
1327 | } |
||
1328 | |||
1329 | // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
|
||
1330 | // is still difficult
|
||
1331 | // in IE, pos.end can be zero after input loses focus
|
||
1332 | if (pos.end < pos.start) {
|
||
1333 | pos.end = pos.start; |
||
1334 | } |
||
1335 | if (pos.start > len) {
|
||
1336 | pos.end = pos.start = len; |
||
1337 | } |
||
1338 | // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
|
||
1339 | if ($keyboard.msie && val.substr(pos.start, 1) === '\n') { |
||
1340 | pos.start += 1;
|
||
1341 | pos.end += 1;
|
||
1342 | } |
||
1343 | |||
1344 | if (o.useCombos) {
|
||
1345 | // keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
|
||
1346 | // thanks to KennyTM: http://stackoverflow.com/q/4275077
|
||
1347 | // original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
|
||
1348 | if ($keyboard.msie) { |
||
1349 | // old IE may not have the caret positioned correctly, so just check the whole thing
|
||
1350 | val = val.replace(base.regex, function (s, accent, letter) {
|
||
1351 | return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
|
||
1352 | }); |
||
1353 | // prevent combo replace error, in case the keyboard closes - see issue #116
|
||
1354 | } else if (base.$preview.length) { |
||
1355 | // Modern browsers - check for combos from last two characters left of the caret
|
||
1356 | t = pos.start - (pos.start - 2 >= 0 ? 2 : 0); |
||
1357 | // target last two characters
|
||
1358 | $keyboard.caret(base.$preview, t, pos.end); |
||
1359 | // do combo replace
|
||
1360 | t2 = ($keyboard.caret(base.$preview).text || '').replace(base.regex, function (s, accent, letter) { |
||
1361 | return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
|
||
1362 | }); |
||
1363 | // add combo back
|
||
1364 | base.$preview.val($keyboard.caret(base.$preview).replaceStr(t2)); |
||
1365 | val = base.$preview.val();
|
||
1366 | } |
||
1367 | } |
||
1368 | |||
1369 | // check input restrictions - in case content was pasted
|
||
1370 | if (o.restrictInput && val !== '') { |
||
1371 | t = layout.acceptedKeys.length; |
||
1372 | |||
1373 | r = layout.acceptedKeysRegex; |
||
1374 | if (!r) {
|
||
1375 | t2 = $.map(layout.acceptedKeys, function (v) { |
||
1376 | // escape any special characters
|
||
1377 | return v.replace(base.escapeRegex, '\\$&'); |
||
1378 | }); |
||
1379 | r = layout.acceptedKeysRegex = new RegExp('(' + t2.join('|') + ')', 'g'); |
||
1380 | } |
||
1381 | |||
1382 | // only save matching keys
|
||
1383 | t2 = val.match(r); |
||
1384 | if (t2) {
|
||
1385 | val = t2.join('');
|
||
1386 | } else {
|
||
1387 | // no valid characters
|
||
1388 | val = '';
|
||
1389 | len = 0;
|
||
1390 | } |
||
1391 | } |
||
1392 | |||
1393 | // save changes, then reposition caret
|
||
1394 | pos.start += val.length - len; |
||
1395 | pos.end += val.length - len; |
||
1396 | base.$preview.val(val);
|
||
1397 | base.saveCaret(pos.start, pos.end); |
||
1398 | // set scroll to keep caret in view
|
||
1399 | base.setScroll(); |
||
1400 | |||
1401 | base.checkMaxLength(); |
||
1402 | |||
1403 | if (o.acceptValid) {
|
||
1404 | base.checkValid(); |
||
1405 | } |
||
1406 | |||
1407 | return val; // return text, used for keyboard closing section |
||
1408 | }; |
||
1409 | |||
1410 | // Toggle accept button classes, if validating
|
||
1411 | base.checkValid = function () { |
||
1412 | var kbcss = $keyboard.css, |
||
1413 | $accept = base.$keyboard.find('.' + kbcss.keyPrefix + 'accept'), |
||
1414 | valid = true;
|
||
1415 | if ($.isFunction(o.validate)) { |
||
1416 | valid = o.validate(base, base.$preview.val(), false); |
||
1417 | } |
||
1418 | // toggle accept button classes; defined in the css
|
||
1419 | $accept
|
||
1420 | .toggleClass(kbcss.inputInvalid, !valid) |
||
1421 | .toggleClass(kbcss.inputValid, valid) |
||
1422 | // update title to indicate that the entry is valid or invalid
|
||
1423 | .attr('title', $accept.attr('data-title') + ' (' + o.display[valid ? 'valid' : 'invalid'] + ')'); |
||
1424 | }; |
||
1425 | |||
1426 | // Decimal button for num pad - only allow one (not used by default)
|
||
1427 | base.checkDecimal = function () { |
||
1428 | // Check US '.' or European ',' format
|
||
1429 | if ((base.decimal && /\./g.test(base.preview.value)) || |
||
1430 | (!base.decimal && /\,/g.test(base.preview.value))) {
|
||
1431 | base.$decBtn
|
||
1432 | .attr({ |
||
1433 | 'disabled': 'disabled', |
||
1434 | 'aria-disabled': 'true' |
||
1435 | }) |
||
1436 | .removeClass(o.css.buttonHover) |
||
1437 | .addClass(o.css.buttonDisabled); |
||
1438 | } else {
|
||
1439 | base.$decBtn
|
||
1440 | .removeAttr('disabled')
|
||
1441 | .attr({ |
||
1442 | 'aria-disabled': 'false' |
||
1443 | }) |
||
1444 | .addClass(o.css.buttonDefault) |
||
1445 | .removeClass(o.css.buttonDisabled); |
||
1446 | } |
||
1447 | }; |
||
1448 | |||
1449 | // get other layer values for a specific key
|
||
1450 | base.getLayers = function ($el) { |
||
1451 | var kbcss = $keyboard.css, |
||
1452 | key = $el.attr('data-pos'), |
||
1453 | $keys = $el.closest('.' + kbcss.keyboard) |
||
1454 | .find('button[data-pos="' + key + '"]'); |
||
1455 | return $keys.filter(function () { |
||
1456 | return $(this) |
||
1457 | .find('.' + kbcss.keyText)
|
||
1458 | .text() !== '';
|
||
1459 | }) |
||
1460 | .add($el);
|
||
1461 | }; |
||
1462 | |||
1463 | // Go to next or prev inputs
|
||
1464 | // goToNext = true, then go to next input; if false go to prev
|
||
1465 | // isAccepted is from autoAccept option or true if user presses shift+enter
|
||
1466 | base.switchInput = function (goToNext, isAccepted) { |
||
1467 | if ($.isFunction(o.switchInput)) { |
||
1468 | o.switchInput(base, goToNext, isAccepted); |
||
1469 | } else {
|
||
1470 | // base.$keyboard may be an empty array - see #275 (apod42)
|
||
1471 | if (base.$keyboard.length) { |
||
1472 | base.$keyboard.hide();
|
||
1473 | } |
||
1474 | var kb,
|
||
1475 | stopped = false,
|
||
1476 | all = $('button, input, textarea, a') |
||
1477 | .filter(':visible')
|
||
1478 | .not(':disabled'),
|
||
1479 | indx = all.index(base.$el) + (goToNext ? 1 : -1); |
||
1480 | if (base.$keyboard.length) { |
||
1481 | base.$keyboard.show();
|
||
1482 | } |
||
1483 | if (indx > all.length - 1) { |
||
1484 | stopped = o.stopAtEnd; |
||
1485 | indx = 0; // go to first input |
||
1486 | } |
||
1487 | if (indx < 0) { |
||
1488 | stopped = o.stopAtEnd; |
||
1489 | indx = all.length - 1; // stop or go to last |
||
1490 | } |
||
1491 | if (!stopped) {
|
||
1492 | isAccepted = base.close(isAccepted); |
||
1493 | if (!isAccepted) {
|
||
1494 | return;
|
||
1495 | } |
||
1496 | kb = all.eq(indx).data('keyboard');
|
||
1497 | if (kb && kb.options.openOn.length) {
|
||
1498 | kb.focusOn(); |
||
1499 | } else {
|
||
1500 | all.eq(indx).focus(); |
||
1501 | } |
||
1502 | } |
||
1503 | } |
||
1504 | return false; |
||
1505 | }; |
||
1506 | |||
1507 | // Close the keyboard, if visible. Pass a status of true, if the content was accepted
|
||
1508 | // (for the event trigger).
|
||
1509 | base.close = function (accepted) { |
||
1510 | if (base.isOpen && base.$keyboard.length) { |
||
1511 | clearTimeout(base.throttled); |
||
1512 | var kbcss = $keyboard.css, |
||
1513 | kbevents = $keyboard.events,
|
||
1514 | val = (accepted) ? base.checkCombos() : base.originalContent; |
||
1515 | // validate input if accepted
|
||
1516 | if (accepted && $.isFunction(o.validate) && !o.validate(base, val, true)) { |
||
1517 | val = base.originalContent; |
||
1518 | accepted = false;
|
||
1519 | if (o.cancelClose) {
|
||
1520 | return;
|
||
1521 | } |
||
1522 | } |
||
1523 | base.isCurrent(false);
|
||
1524 | base.isOpen = o.alwaysOpen || o.userClosed; |
||
1525 | // update value for always open keyboards
|
||
1526 | base.$preview.val(val);
|
||
1527 | base.$el
|
||
1528 | .removeClass(kbcss.isCurrent + ' ' + kbcss.inputAutoAccepted)
|
||
1529 | // add 'ui-keyboard-autoaccepted' to inputs - see issue #66
|
||
1530 | .addClass((accepted || false) ? accepted === true ? '' : kbcss.inputAutoAccepted : '') |
||
1531 | .val(val) |
||
1532 | // trigger default change event - see issue #146
|
||
1533 | .trigger(kbevents.inputChange); |
||
1534 | // don't trigger an empty event - see issue #463
|
||
1535 | if (!o.alwaysOpen) {
|
||
1536 | // don't trigger beforeClose if keyboard is always open
|
||
1537 | base.$el.trigger(kbevents.kbBeforeClose, [base, base.el, (accepted || false)]); |
||
1538 | } |
||
1539 | base.$el
|
||
1540 | .trigger(((accepted || false) ? kbevents.inputAccepted : kbevents.inputCanceled), [base, base.el])
|
||
1541 | .trigger((o.alwaysOpen) ? kbevents.kbInactive : kbevents.kbHidden, [base, base.el]) |
||
1542 | .blur(); |
||
1543 | |||
1544 | // save caret after updating value (fixes userClosed issue with changing focus)
|
||
1545 | $keyboard.caret(base.$preview, base.last); |
||
1546 | // base is undefined if keyboard was destroyed - fixes #358
|
||
1547 | if (base) {
|
||
1548 | // add close event time
|
||
1549 | base.last.eventTime = new Date().getTime();
|
||
1550 | if (!(o.alwaysOpen || o.userClosed && accepted === 'true') && base.$keyboard.length) { |
||
1551 | // free up memory
|
||
1552 | base.removeKeyboard(); |
||
1553 | // rebind input focus - delayed to fix IE issue #72
|
||
1554 | base.timer = setTimeout(function () {
|
||
1555 | if(base){
|
||
1556 | base.bindFocus(); |
||
1557 | } |
||
1558 | }, 500);
|
||
1559 | } |
||
1560 | if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') { |
||
1561 | base.$el
|
||
1562 | .addClass(kbcss.placeholder) |
||
1563 | .val(base.inPlaceholder); |
||
1564 | } |
||
1565 | } |
||
1566 | } |
||
1567 | return !!accepted;
|
||
1568 | }; |
||
1569 | |||
1570 | base.accept = function () { |
||
1571 | return base.close(true); |
||
1572 | }; |
||
1573 | |||
1574 | base.checkClose = function (e) { |
||
1575 | if (base.opening) {
|
||
1576 | return;
|
||
1577 | } |
||
1578 | base.escClose(e); |
||
1579 | var kbcss = $.keyboard.css, |
||
1580 | $target = $(e.target); |
||
1581 | // needed for IE to allow switching between keyboards smoothly
|
||
1582 | if ($target.hasClass(kbcss.input)) { |
||
1583 | var kb = $target.data('keyboard'); |
||
1584 | // only trigger on self
|
||
1585 | if (kb === base && !kb.$el.hasClass(kbcss.isCurrent) && e.type === kb.options.openOn) { |
||
1586 | kb.focusOn(); |
||
1587 | } |
||
1588 | } |
||
1589 | }; |
||
1590 | |||
1591 | base.escClose = function (e) { |
||
1592 | if (e && e.type === 'keyup') { |
||
1593 | return (e.which === $keyboard.keyCodes.escape && !o.ignoreEsc) ? |
||
1594 | base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false) : |
||
1595 | '';
|
||
1596 | } |
||
1597 | // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or
|
||
1598 | // single stay open keyboard
|
||
1599 | if (!base.isOpen) {
|
||
1600 | return;
|
||
1601 | } |
||
1602 | // ignore autoaccept if using escape - good idea?
|
||
1603 | if (!base.isCurrent() && base.isOpen || base.isOpen && e.target !== base.el) {
|
||
1604 | // don't close if stayOpen is set; but close if a different keyboard is being opened
|
||
1605 | if ((o.stayOpen || o.userClosed) && !$(e.target).hasClass($keyboard.css.input)) { |
||
1606 | return;
|
||
1607 | } |
||
1608 | // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
|
||
1609 | if ($keyboard.allie) { |
||
1610 | e.preventDefault(); |
||
1611 | } |
||
1612 | if (o.closeByClickEvent) {
|
||
1613 | // only close the keyboard if the user is clicking on an input or if he causes a click
|
||
1614 | // event (touchstart/mousedown will not force the close with this setting)
|
||
1615 | var name = e.target.nodeName.toLowerCase();
|
||
1616 | if (name === 'input' || name === 'textarea' || e.type === 'click') { |
||
1617 | base.close(o.autoAccept ? 'true' : false); |
||
1618 | } |
||
1619 | } else {
|
||
1620 | // send 'true' instead of a true (boolean), the input won't get a 'ui-keyboard-autoaccepted'
|
||
1621 | // class name - see issue #66
|
||
1622 | base.close(o.autoAccept ? 'true' : false); |
||
1623 | } |
||
1624 | } |
||
1625 | }; |
||
1626 | |||
1627 | // Build default button
|
||
1628 | base.keyBtn = $('<button />') |
||
1629 | .attr({ |
||
1630 | 'role': 'button', |
||
1631 | 'type': 'button', |
||
1632 | 'aria-disabled': 'false', |
||
1633 | 'tabindex': '-1' |
||
1634 | }) |
||
1635 | .addClass($keyboard.css.keyButton);
|
||
1636 | |||
1637 | // convert key names into a class name
|
||
1638 | base.processName = function (name) { |
||
1639 | var index, n,
|
||
1640 | process = (name || '').replace(/[^a-z0-9-_]/gi, ''), |
||
1641 | len = process.length, |
||
1642 | newName = []; |
||
1643 | if (len > 1 && name === process) { |
||
1644 | // return name if basic text
|
||
1645 | return name;
|
||
1646 | } |
||
1647 | // return character code sequence
|
||
1648 | len = name.length; |
||
1649 | if (len) {
|
||
1650 | for (index = 0; index < len; index++) { |
||
1651 | n = name[index]; |
||
1652 | // keep '-' and '_'... so for dash, we get two dashes in a row
|
||
1653 | newName.push(/[a-z0-9-_]/i.test(n) ?
|
||
1654 | (/[-_]/.test(n) && index !== 0 ? '' : n) : |
||
1655 | (index === 0 ? '' : '-') + n.charCodeAt(0) |
||
1656 | ); |
||
1657 | } |
||
1658 | return newName.join(''); |
||
1659 | } else {
|
||
1660 | return name;
|
||
1661 | } |
||
1662 | }; |
||
1663 | |||
1664 | base.processKeys = function (name) { |
||
1665 | var tmp,
|
||
1666 | parts = name.split(':'),
|
||
1667 | data = { |
||
1668 | name: null, |
||
1669 | map: '', |
||
1670 | title: '' |
||
1671 | }; |
||
1672 | /* map defined keys
|
||
1673 | format 'key(A):Label_for_key_(ignore_parentheses_here)'
|
||
1674 | 'key' = key that is seen (can any character(s); but it might need to be escaped using '\'
|
||
1675 | or entered as unicode '\u####'
|
||
1676 | '(A)' = the actual key on the real keyboard to remap
|
||
1677 | ':Label_for_key' ends up in the title/tooltip
|
||
1678 | Examples:
|
||
1679 | '\u0391(A):alpha', 'x(y):this_(might)_cause_problems
|
||
1680 | or edge cases of ':(x)', 'x(:)', 'x(()' or 'x())'
|
||
1681 | Enhancement (if I can get alt keys to work):
|
||
1682 | A mapped key will include the mod key, e.g. 'x(alt-x)' or 'x(alt-shift-x)'
|
||
1683 | */
|
||
1684 | if (/\(.+\)/.test(parts[0]) || /^:\(.+\)/.test(name) || /\([(:)]\)/.test(name)) { |
||
1685 | // edge cases 'x(:)', 'x(()' or 'x())'
|
||
1686 | if (/\([(:)]\)/.test(name)) { |
||
1687 | tmp = parts[0].match(/([^(]+)\((.+)\)/); |
||
1688 | if (tmp && tmp.length) {
|
||
1689 | data.name = tmp[1];
|
||
1690 | data.map = tmp[2];
|
||
1691 | data.title = parts.length > 1 ? parts.slice(1).join(':') : ''; |
||
1692 | } else {
|
||
1693 | // edge cases 'x(:)', ':(x)' or ':(:)'
|
||
1694 | data.name = name.match(/([^(]+)/)[0]; |
||
1695 | if (data.name === ':') { |
||
1696 | // ':(:):test' => parts = [ '', '(', ')', 'title' ] need to slice 1
|
||
1697 | parts = parts.slice(1);
|
||
1698 | } |
||
1699 | if (tmp === null) { |
||
1700 | // 'x(:):test' => parts = [ 'x(', ')', 'title' ] need to slice 2
|
||
1701 | data.map = ':';
|
||
1702 | parts = parts.slice(2);
|
||
1703 | } |
||
1704 | data.title = parts.length ? parts.join(':') : ''; |
||
1705 | } |
||
1706 | } else {
|
||
1707 | // example: \u0391(A):alpha; extract 'A' from '(A)'
|
||
1708 | data.map = name.match(/\(([^()]+?)\)/)[1]; |
||
1709 | // remove '(A)', left with '\u0391:alpha'
|
||
1710 | name = name.replace(/\(([^()]+)\)/, ''); |
||
1711 | tmp = name.split(':');
|
||
1712 | // get '\u0391' from '\u0391:alpha'
|
||
1713 | if (tmp[0] === '') { |
||
1714 | data.name = ':';
|
||
1715 | parts = parts.slice(1);
|
||
1716 | } else {
|
||
1717 | data.name = tmp[0];
|
||
1718 | } |
||
1719 | data.title = parts.length > 1 ? parts.slice(1).join(':') : ''; |
||
1720 | } |
||
1721 | } else {
|
||
1722 | // find key label
|
||
1723 | // corner case of '::;' reduced to ':;', split as ['', ';']
|
||
1724 | if (name !== '' && parts[0] === '') { |
||
1725 | data.name = ':';
|
||
1726 | parts = parts.slice(1);
|
||
1727 | } else {
|
||
1728 | data.name = parts[0];
|
||
1729 | } |
||
1730 | data.title = parts.length > 1 ? parts.slice(1).join(':') : ''; |
||
1731 | } |
||
1732 | data.title = $.trim(data.title).replace(/_/g, ' '); |
||
1733 | return data;
|
||
1734 | }; |
||
1735 | |||
1736 | // Add key function
|
||
1737 | // keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
|
||
1738 | // name = name added to key, or cross-referenced in the display options
|
||
1739 | // base.temp[0] = keyset to attach the new button
|
||
1740 | // regKey = true when it is not an action key
|
||
1741 | base.addKey = function (keyName, action, regKey) { |
||
1742 | var keyClass, tmp, keys,
|
||
1743 | data = {}, |
||
1744 | txt = base.processKeys(regKey ? keyName : action), |
||
1745 | kbcss = $keyboard.css;
|
||
1746 | |||
1747 | if (!regKey && o.display[txt.name]) {
|
||
1748 | keys = base.processKeys(o.display[txt.name]); |
||
1749 | // action contained in "keyName" (e.g. keyName = "accept",
|
||
1750 | // action = "a" (use checkmark instead of text))
|
||
1751 | keys.action = base.processKeys(keyName).name; |
||
1752 | } else {
|
||
1753 | // when regKey is true, keyName is the same as action
|
||
1754 | keys = txt; |
||
1755 | keys.action = txt.name; |
||
1756 | } |
||
1757 | |||
1758 | data.name = base.processName(txt.name); |
||
1759 | |||
1760 | if (keys.map !== '') { |
||
1761 | $keyboard.builtLayouts[base.layout].mappedKeys[keys.map] = keys.name;
|
||
1762 | $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
|
||
1763 | } else if (regKey) { |
||
1764 | $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
|
||
1765 | } |
||
1766 | |||
1767 | if (regKey) {
|
||
1768 | keyClass = data.name === '' ? '' : kbcss.keyPrefix + data.name; |
||
1769 | } else {
|
||
1770 | // Action keys will have the 'ui-keyboard-actionkey' class
|
||
1771 | keyClass = kbcss.keyAction + ' ' + kbcss.keyPrefix + keys.action;
|
||
1772 | } |
||
1773 | // '\u2190'.length = 1 because the unicode is converted, so if more than one character,
|
||
1774 | // add the wide class
|
||
1775 | keyClass += (keys.name.length > 2 ? ' ' + kbcss.keyWide : '') + ' ' + o.css.buttonDefault; |
||
1776 | |||
1777 | data.html = '<span class="' + kbcss.keyText + '">' + |
||
1778 | // this prevents HTML from being added to the key
|
||
1779 | keys.name.replace(/[\u00A0-\u9999]/gim, function (i) { |
||
1780 | return '&#' + i.charCodeAt(0) + ';'; |
||
1781 | }) + |
||
1782 | '</span>';
|
||
1783 | |||
1784 | data.$key = base.keyBtn
|
||
1785 | .clone() |
||
1786 | .attr({ |
||
1787 | 'data-value': regKey ? keys.name : keys.action, // value |
||
1788 | 'data-name': keys.action,
|
||
1789 | 'data-pos': base.temp[1] + ',' + base.temp[2], |
||
1790 | 'data-action': keys.action,
|
||
1791 | 'data-html': data.html
|
||
1792 | }) |
||
1793 | // add 'ui-keyboard-' + data.name for all keys
|
||
1794 | // (e.g. 'Bksp' will have 'ui-keyboard-bskp' class)
|
||
1795 | // any non-alphanumeric characters will be replaced with
|
||
1796 | // their decimal unicode value
|
||
1797 | // (e.g. '~' is a regular key, class = 'ui-keyboard-126'
|
||
1798 | // (126 is the unicode decimal value - same as ~)
|
||
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 | } |