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