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