/*! jQuery UI Virtual Keyboard Version 1.12 Author: Jeremy Satterfield Modified: Rob Garrison (Mottie on github) ----------------------------------------- Licensed under the MIT License Caret code modified from jquery.caret.1.02.js Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php ----------------------------------------- An on-screen virtual keyboard embedded within the browser window which will popup when a specified entry field is focused. The user can then type and preview their input before Accepting or Canceling. As a plugin to jQuery UI styling and theme will automatically match that used by jQuery UI with the exception of the required CSS listed below. Requires: jQuery jQuery UI (position utility only) & CSS Usage: $('input[type=text], input[type=password], textarea') .keyboard({ layout:"qwerty", customLayout: { 'default': [ "q w e r t y {bksp}", "s a m p l e {shift}", "{accept} {space} {cancel}" ], 'shift' : [ "Q W E R T Y {bksp}", "S A M P L E {shift}", "{accept} {space} {cancel}" ] } }); Options: layout [String] specify which keyboard layout to use qwerty - Standard QWERTY layout (Default) international - US international layout alpha - Alphabetical layout dvorak - Dvorak Simplified layout num - Numerical (ten-key) layout custom - Uses a custom layout as defined by the customLayout option customLayout [Object] Specify a custom layout An Object containing a set of key:value pairs, each key is a keyset. The key can be one to four rows (default, shifted, alt and alt-shift) or any number of meta key sets (meta1, meta2, etc). The value is an array with string elements of which each defines a new keyboard row. Each string element must have each character or key seperated by a space. To include an action key, select the desired one from the list below, or define your own by adding it to the $.keyboard.keyaction variable In the list below where two special/"Action" keys are shown, both keys have the same action but different appearances (abbreviated/full name keys). Special/"Action" keys include: {a}, {accept} - Updates element value and closes keyboard {alt},{altgr} - AltGr for International keyboard {b}, {bksp} - Backspace {c}, {cancel} - Clears changes and closes keyboard {clear} - Clear input window - used in num pad {combo} - Toggle combo (diacritic) key {dec} - Decimal for numeric entry, only allows one decimal (optional use in num pad) {e}, {enter} - Return/New Line {lock} - Caps lock key {meta#} - Meta keys that change the key set (# can be any integer) {next} - Switch to next keyboard input/textarea {prev} - Switch to previous keyboard input/textarea {s}, {shift} - Shift {sign} - Change sign of numeric entry (positive or negative) {sp:#} - Replace # with a numerical value, adds blank space, value of 1 ~ width of one key {space} - Spacebar {t}, {tab} - Tab CSS: .ui-keyboard { padding: .3em; position: absolute; left: 0; top: 0; z-index: 16000; } .ui-keyboard-has-focus { z-index: 16001; } .ui-keyboard div { font-size: 1.1em; } .ui-keyboard-button { height: 2em; width: 2em; margin: .1em; cursor: pointer; overflow: hidden; line-height: 2em; } .ui-keyboard-button span { padding: 0; margin: 0; white-space:nowrap; } .ui-keyboard-button-endrow { clear: left; } .ui-keyboard-widekey { width: 4em; } .ui-keyboard-space { width: 15em; text-indent: -999em; } .ui-keyboard-preview-wrapper { text-align: center; } .ui-keyboard-preview { text-align: left; margin: 0 0 3px 0; display: inline; width: 99%;} - width is calculated in IE, since 99% = 99% full browser width .ui-keyboard-keyset { text-align: center; white-space: nowrap; } .ui-keyboard-input { text-align: left; } .ui-keyboard-input-current { -moz-box-shadow: 1px 1px 10px #00f; -webkit-box-shadow: 1px 1px 10px #00f; box-shadow: 1px 1px 10px #00f; } .ui-keyboard-placeholder { color: #888; } .ui-keyboard-nokeyboard { color: #888; border-color: #888; } - disabled or readonly inputs, or use input[disabled='disabled'] { color: #f00; } */ ;(function($){ $.keyboard = function(el, options){ var base = this, o; // Access to jQuery and DOM versions of element base.$el = $(el); base.el = el; // Add a reverse reference to the DOM object base.$el.data("keyboard", base); base.init = function(){ base.options = o = $.extend(true, {}, $.keyboard.defaultOptions, options); // Shift and Alt key toggles, sets is true if a layout has more than one keyset - used for mousewheel message base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false; base.lastKeyset = [false, false, false]; // [shift, alt, meta] // Class names of the basic key set - meta keysets are handled by the keyname base.rows = [ '', '-shift', '-alt', '-alt-shift' ]; base.acceptedKeys = []; base.mappedKeys = {}; // for remapping manually typed in keys $('').appendTo('body').remove(); base.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning base.allie = $('body').hasClass('ie'); // $.browser.msie being removed soon base.inPlaceholder = base.$el.attr('placeholder') || ''; base.watermark = (typeof(document.createElement('input').placeholder) !== 'undefined' && base.inPlaceholder !== ''); // html 5 placeholder/watermark base.regex = $.keyboard.comboRegex; // save default regex (in case loading another layout changes it) base.decimal = ( /^\./.test(o.display.dec) ) ? true : false; // determine if US "." or European "," system being used // convert mouse repeater rate (characters per second) into a time in milliseconds. base.repeatTime = 1000/(o.repeatRate || 20); // Check if caret position is saved when input is hidden or loses focus // (*cough* all versions of IE and I think Opera has/had an issue as well base.temp = $('').appendTo('body').caret(3,3); // Also save caret position of the input if it is locked base.checkCaret = (o.lockInput || base.temp.hide().show().caret().start !== 3 ) ? true : false; base.temp.remove(); base.lastCaret = { start:0, end:0 }; base.temp = [ '', 0, 0 ]; // used when building the keyboard - [keyset element, row, index] // Bind events $.each('initialized visible change hidden canceled accepted beforeClose'.split(' '), function(i,f){ if ($.isFunction(o[f])){ base.$el.bind(f + '.keyboard', o[f]); } }); // Close with esc key & clicking outside if (o.alwaysOpen) { o.stayOpen = true; } $(document).bind('mousedown.keyboard keyup.keyboard', function(e){ if (base.opening) { return; } base.escClose(e); // needed for IE to allow switching between keyboards smoothly if ( e.target && $(e.target).hasClass('ui-keyboard-input') ) { var kb = $(e.target).data('keyboard'); if (kb && kb.options.openOn.length) { kb.focusOn(); } } }); // Display keyboard on focus base.$el .addClass('ui-keyboard-input ' + o.css.input) .attr({ 'aria-haspopup' : 'true', 'role' : 'textbox' }); // add disabled/readonly class - dynamically updated on reveal if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass('ui-keyboard-lockedinput'))) { base.$el.addClass('ui-keyboard-nokeyboard'); } if (o.openOn) { base.$el.bind(o.openOn + '.keyboard', function(){ base.focusOn(); }); } // Add placeholder if not supported by the browser if (!base.watermark && base.$el.val() === '' && base.inPlaceholder !== '' && base.$el.attr('placeholder') !== '') { base.$el .addClass('ui-keyboard-placeholder') // css watermark style (darker text) .val( base.inPlaceholder ); } base.$el.trigger( 'initialized.keyboard', [ base, base.el ] ); // initialized with keyboard open if (o.alwaysOpen) { base.reveal(); } }; base.focusOn = function(){ if (base.$el.is(':visible')) { // caret position is always 0,0 in webkit; and nothing is focused at this point... odd // save caret position in the input to transfer it to the preview base.lastCaret = base.$el.caret(); } if (!base.isVisible() || o.alwaysOpen) { clearTimeout(base.timer); base.reveal(); setTimeout(function(){ base.$preview.focus(); }, 100); } }; base.reveal = function(){ base.opening = true; // close all keyboards $('.ui-keyboard:not(.ui-keyboard-always-open)').hide(); // Don't open if disabled if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass('ui-keyboard-lockedinput'))) { base.$el.addClass('ui-keyboard-nokeyboard'); return; } else { base.$el.removeClass('ui-keyboard-nokeyboard'); } // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally if (o.openOn) { base.$el.unbind( o.openOn + '.keyboard' ); } // build keyboard if it doesn't exist if (typeof(base.$keyboard) === 'undefined') { base.startup(); } // ui-keyboard-has-focus is applied in case multiple keyboards have alwaysOpen = true and are stacked $('.ui-keyboard-has-focus').removeClass('ui-keyboard-has-focus'); $('.ui-keyboard-input-current').removeClass('ui-keyboard-input-current'); base.$el.addClass('ui-keyboard-input-current'); base.isCurrent(true); // clear watermark if (!base.watermark && base.el.value === base.inPlaceholder) { base.$el .removeClass('ui-keyboard-placeholder') .val(''); } // save starting content, in case we cancel base.originalContent = base.$el.val(); base.$preview.val( base.originalContent ); // disable/enable accept button if (o.acceptValid) { base.checkValid(); } // get single target position || target stored in element data (multiple targets) || default, at the element var p, s; base.position = o.position; base.position.of = base.position.of || base.$el.data('keyboardPosition') || base.$el; base.position.collision = (o.usePreview) ? base.position.collision || 'fit fit' : 'flip flip'; if (o.resetDefault) { base.shiftActive = base.altActive = base.metaActive = false; base.showKeySet(); } // show & position keyboard base.$keyboard // basic positioning before it is set by position utility .css({ position: 'absolute', left: 0, top: 0 }) .addClass('ui-keyboard-has-focus') .show(); // adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6) if (o.usePreview && base.msie) { if (typeof base.width === 'undefined') { base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row base.$preview.show(); } base.$preview.width(base.width); } // position after keyboard is visible (required for UI position utility) and appropriately sized base.$keyboard.position(base.position); base.$preview.focus(); base.checkDecimal(); // get preview area line height // add roughly 4px to get line height from font height, works well for font-sizes from 14-36px - needed for textareas base.lineHeight = parseInt( base.$preview.css('lineHeight'), 10) || parseInt(base.$preview.css('font-size') ,10) + 4; // IE caret haxx0rs if (base.allie){ // ensure caret is at the end of the text (needed for IE) s = base.lastCaret.start || base.originalContent.length; p = { start: s, end: s }; if (!base.lastCaret) { base.lastCaret = p; } // set caret at end of content, if undefined if (base.lastCaret.end === 0 && base.lastCaret.start > 0) { base.lastCaret.end = base.lastCaret.start; } // sometimes end = 0 while start is > 0 if (base.lastCaret.start < 0) { base.lastCaret = p; } // IE will have start -1, end of 0 when not focused (see demo: http://jsfiddle.net/Mottie/fgryQ/3/). } base.$preview.caret(base.lastCaret.start, base.lastCaret.end ); base.$el.trigger( 'visible.keyboard', [ base, base.el ] ); // opening keyboard flag; delay allows switching between keyboards without immediately closing the keyboard setTimeout(function(){ base.opening = false; }, 500); // return base to allow chaining in typing extension return base; }; base.startup = function(){ base.$keyboard = base.buildKeyboard(); base.$allKeys = base.$keyboard.find('button.ui-keyboard-button'); base.preview = base.$preview[0]; base.$decBtn = base.$keyboard.find('.ui-keyboard-dec'); base.wheel = $.isFunction( $.fn.mousewheel ); // is mousewheel plugin loaded? // keyCode of keys always allowed to be typed - caps lock, page up & down, end, home, arrow, insert & delete keys base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46]; if (o.enterNavigation) { base.alwaysAllowed.push(13); } // add enter to allowed keys base.$preview .bind('keypress.keyboard', function(e){ var k = String.fromCharCode(e.charCode || e.which); if (base.checkCaret) { base.lastCaret = base.$preview.caret(); } // update caps lock - can only do this while typing =( base.capsLock = (((k >= 65 && k <= 90) && !e.shiftKey) || ((k >= 97 && k <= 122) && e.shiftKey)) ? true : false; // restrict input - keyCode in keypress special keys: see http://www.asquare.net/javascript/tests/KeyCode.html if (o.restrictInput) { // allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp) if ( (e.which === 8 || e.which === 0) && $.inArray( e.keyCode, base.alwaysAllowed ) ) { return; } if ($.inArray(k, base.acceptedKeys) === -1) { e.preventDefault(); } // quick key check } else if ( (e.ctrlKey || e.metaKey) && (e.which === 97 || e.which === 99 || e.which === 118 || (e.which >= 120 && e.which <=122)) ) { // Allow select all (ctrl-a:97), copy (ctrl-c:99), paste (ctrl-v:118) & cut (ctrl-x:120) & redo (ctrl-y:121)& undo (ctrl-z:122); meta key for mac return; } // Mapped Keys - allows typing on a regular keyboard and the mapped key is entered // Set up a key in the layout as follows: "m(a):label"; m = key to map, (a) = actual keyboard key to map to (optional), ":label" = title/tooltip (optional) // example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha if (base.hasMappedKeys) { if (base.mappedKeys.hasOwnProperty(k)){ base.insertText( base.mappedKeys[k] ); e.preventDefault(); } } base.checkMaxLength(); }) .bind('keyup.keyboard', function(e){ switch (e.which) { // Insert tab key case 9 : // Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab to the keyboard preview // area on keyup. Sadly it still happens if you don't release the tab key immediately because keydown event auto-repeats if (base.tab && !o.lockInput) { $.keyboard.keyaction.tab(base); base.tab = false; } else { e.preventDefault(); } break; // Escape will hide the keyboard case 27: base.close(); return false; } // throttle the check combo function because fast typers will have an incorrectly positioned caret clearTimeout(base.throttled); base.throttled = setTimeout(function(){ base.checkCombos(); }, 100); base.checkMaxLength(); base.$el.trigger( 'change.keyboard', [ base, base.el ] ); }) .bind('keydown.keyboard', function(e){ switch (e.which) { // prevent tab key from leaving the preview window case 9 : if (o.tabNavigation) { // allow tab to pass through - tab to next input/shift-tab for prev return true; } else { base.tab = true; // see keyup comment above return false; } case 13: $.keyboard.keyaction.enter(base, null, e); break; // Show capsLock case 20: base.shiftActive = base.capsLock = !base.capsLock; base.showKeySet(this); break; case 86: // prevent ctrl-v/cmd-v if (e.ctrlKey || e.metaKey) { if (o.preventPaste) { e.preventDefault(); return; } base.checkCombos(); // check pasted content } break; } }) .bind('mouseup.keyboard', function(){ if (base.checkCaret) { base.lastCaret = base.$preview.caret(); } }); // prevent keyboard event bubbling base.$keyboard.bind('mousedown.keyboard click.keyboard', function(e){ e.stopPropagation(); }); // If preventing paste, block context menu (right click) if (o.preventPaste){ base.$preview.bind('contextmenu.keyboard', function(e){ e.preventDefault(); }); base.$el.bind('contextmenu.keyboard', function(e){ e.preventDefault(); }); } if (o.appendLocally) { base.$el.after( base.$keyboard ); } else { base.$keyboard.appendTo('body'); } base.$allKeys .bind(o.keyBinding.split(' ').join('.keyboard ') + '.keyboard repeater.keyboard', function(e){ // 'key', { action: doAction, original: n, curTxt : n, curNum: 0 } var txt, key = $.data(this, 'key'), action = key.action.split(':')[0]; base.$preview.focus(); // Start caret in IE when not focused (happens with each virtual keyboard button click if (base.checkCaret) { base.$preview.caret( base.lastCaret.start, base.lastCaret.end ); } if (action.match('meta')) { action = 'meta'; } if ($.keyboard.keyaction.hasOwnProperty(action) && $(this).hasClass('ui-keyboard-actionkey')) { // stop processing if action returns false (close & cancel) if ($.keyboard.keyaction[action](base,this,e) === false) { return; } } else if (typeof key.action !== 'undefined') { txt = (base.wheel && !$(this).hasClass('ui-keyboard-actionkey')) ? key.curTxt : key.action; base.insertText(txt); if (!base.capsLock && !o.stickyShift && !e.shiftKey) { base.shiftActive = false; base.showKeySet(this); } } base.checkCombos(); base.checkMaxLength(); base.$el.trigger( 'change.keyboard', [ base, base.el ] ); base.$preview.focus(); e.preventDefault(); }) // Change hover class and tooltip .bind('mouseenter.keyboard mouseleave.keyboard', function(e){ var el = this, $this = $(this), // 'key' = { action: doAction, original: n, curTxt : n, curNum: 0 } key = $.data(el, 'key'); if (e.type === 'mouseenter' && base.el.type !== 'password' ){ $this .addClass(o.css.buttonHover) .attr('title', function(i,t){ // show mouse wheel message return (base.wheel && t === '' && base.sets) ? o.wheelMessage : t; }); } if (e.type === 'mouseleave'){ key.curTxt = key.original; key.curNum = 0; $.data(el, 'key', key); $this .removeClass( (base.el.type === 'password') ? '' : o.css.buttonHover) // needed or IE flickers really bad .attr('title', function(i,t){ return (t === o.wheelMessage) ? '' : t; }) .find('span').text( key.original ); // restore original button text } }) // Allow mousewheel to scroll through other key sets of the same key .bind('mousewheel.keyboard', function(e, delta){ if (base.wheel) { var txt, $this = $(this), key = $.data(this, 'key'); txt = key.layers || base.getLayers( $this ); key.curNum += (delta > 0) ? -1 : 1; if (key.curNum > txt.length-1) { key.curNum = 0; } if (key.curNum < 0) { key.curNum = txt.length-1; } key.layers = txt; key.curTxt = txt[key.curNum]; $.data(this, 'key', key); $this.find('span').text( txt[key.curNum] ); return false; } }) // using "kb" namespace for mouse repeat functionality to keep it separate // I need to trigger a "repeater.keyboard" to make it work .bind('mouseup.keyboard mouseleave.kb touchend.kb touchmove.kb touchcancel.kb', function(){ if (base.isVisible() && base.isCurrent()) { base.$preview.focus(); } base.mouseRepeat = [false,'']; clearTimeout(base.repeater); // make sure key repeat stops! if (base.checkCaret) { base.$preview.caret( base.lastCaret.start, base.lastCaret.end ); } return false; }) // prevent form submits when keyboard is bound locally - issue #64 .bind('click.keyboard', function(){ return false; }) // no mouse repeat for action keys (shift, ctrl, alt, meta, etc) .filter(':not(.ui-keyboard-actionkey)') // mouse repeated action key exceptions .add('.ui-keyboard-tab, .ui-keyboard-bksp, .ui-keyboard-space, .ui-keyboard-enter', base.$keyboard) .bind('mousedown.kb touchstart.kb', function(){ if (o.repeatRate !== 0) { var key = $(this); base.mouseRepeat = [true, key]; // save the key, make sure we are repeating the right one (fast typers) setTimeout(function() { if (base.mouseRepeat[0] && base.mouseRepeat[1] === key) { base.repeatKey(key); } }, o.repeatDelay); } return false; }); // adjust with window resize $(window).resize(function(){ if (base.isVisible()) { base.$keyboard.position(base.position); } }); }; base.isVisible = function() { if (typeof(base.$keyboard) === 'undefined') { return false; } return base.$keyboard.is(":visible"); }; // Insert text at caret/selection - thanks to Derek Wickwire for fixing this up! base.insertText = function(txt){ var bksp, t, h, // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE). val = base.$preview.val(), pos = base.$preview.caret(), scrL = base.$preview.scrollLeft(), scrT = base.$preview.scrollTop(), len = val.length; // save original content length // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea is still difficult if (pos.end < pos.start) { pos.end = pos.start; } // in IE, pos.end can be zero after input loses focus if (pos.start > len) { pos.end = pos.start = len; } if (base.preview.tagName === 'TEXTAREA') { // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine) if (base.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; } // Set scroll top so current text is in view - needed for virtual keyboard typing, not manual typing // this doesn't appear to work correctly in Opera h = (val.split('\n').length - 1); base.preview.scrollTop = (h>0) ? base.lineHeight * h : scrT; } bksp = (txt === 'bksp' && pos.start === pos.end) ? true : false; txt = (txt === 'bksp') ? '' : txt; t = pos.start + (bksp ? -1 : txt.length); scrL += parseInt(base.$preview.css('fontSize'),10) * (txt === 'bksp' ? -1 : 1); base.$preview .val( base.$preview.val().substr(0, pos.start - (bksp ? 1 : 0)) + txt + base.$preview.val().substr(pos.end) ) .caret(t, t) .scrollLeft(scrL); if (base.checkCaret) { base.lastCaret = { start: t, end: t }; } // save caret in case of bksp }; // check max length base.checkMaxLength = function(){ var t, p = base.$preview.val(); if (o.maxLength !== false && p.length > o.maxLength) { t = Math.min(base.$preview.caret().start, o.maxLength); base.$preview.val( p.substring(0, o.maxLength) ); // restore caret on change, otherwise it ends up at the end. base.$preview.caret( t, t ); base.lastCaret = { start: t, end: t }; } if (base.$decBtn.length) { base.checkDecimal(); } }; // mousedown repeater base.repeatKey = function(key){ key.trigger('repeater.keyboard'); if (base.mouseRepeat[0]) { base.repeater = setTimeout(function() { base.repeatKey(key); }, base.repeatTime); } }; base.showKeySet = function(el){ var key = '', toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0); if (!base.shiftActive) { base.capsLock = false; } // check meta key set if (base.metaActive) { // the name attribute contains the meta set # "meta99" key = (el && el.name && /meta/.test(el.name)) ? el.name : ''; // save active meta keyset name if (key === '') { key = (base.metaActive === true) ? '' : base.metaActive; } else { base.metaActive = key; } // if meta keyset doesn't have a shift or alt keyset, then show just the meta key set if ( (!o.stickyShift && base.lastKeyset[2] !== base.metaActive) || ( (base.shiftActive || base.altActive) && !base.$keyboard.find('.ui-keyboard-keyset-' + key + base.rows[toShow]).length) ) { base.shiftActive = base.altActive = false; } } else if (!o.stickyShift && base.lastKeyset[2] !== base.metaActive && base.shiftActive) { // switching from meta key set back to default, reset shift & alt if using stickyShift base.shiftActive = base.altActive = false; } toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0); key = (toShow === 0 && !base.metaActive) ? '-default' : (key === '') ? '' : '-' + key; if (!base.$keyboard.find('.ui-keyboard-keyset' + key + base.rows[toShow]).length) { // keyset doesn't exist, so restore last keyset settings base.shiftActive = base.lastKeyset[0]; base.altActive = base.lastKeyset[1]; base.metaActive = base.lastKeyset[2]; return; } base.$keyboard .find('.ui-keyboard-alt, .ui-keyboard-shift, .ui-keyboard-actionkey[class*=meta]').removeClass(o.css.buttonAction).end() .find('.ui-keyboard-alt')[(base.altActive) ? 'addClass' : 'removeClass'](o.css.buttonAction).end() .find('.ui-keyboard-shift')[(base.shiftActive) ? 'addClass' : 'removeClass'](o.css.buttonAction).end() .find('.ui-keyboard-lock')[(base.capsLock) ? 'addClass' : 'removeClass'](o.css.buttonAction).end() .find('.ui-keyboard-keyset').hide().end() .find('.ui-keyboard-keyset' + key + base.rows[toShow]).show().end() .find('.ui-keyboard-actionkey.ui-keyboard' + key).addClass(o.css.buttonAction); base.lastKeyset = [ base.shiftActive, base.altActive, base.metaActive ]; }; // check for key combos (dead keys) base.checkCombos = function(){ var i, r, t, t2, // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE). val = base.$preview.val(), pos = base.$preview.caret(), len = val.length; // save original content length // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea is still difficult if (pos.end < pos.start) { pos.end = pos.start; } // in IE, pos.end can be zero after input loses focus if (pos.start > len) { pos.end = pos.start = len; } // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine) if (base.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; } if (o.useCombos) { // keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ) // thanks to KennyTM: http://stackoverflow.com/questions/4275077/replace-characters-to-make-international-letters-diacritics // original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex if (base.msie) { // old IE may not have the caret positioned correctly, so just check the whole thing val = val.replace(base.regex, function(s, accent, letter){ return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s; }); } else { // Modern browsers - check for combos from last two characters left of the caret t = pos.start - (pos.start - 2 >= 0 ? 2 : 0); // target last two characters base.$preview.caret(t, pos.end); // do combo replace t2 = base.$preview.caret().text.replace(base.regex, function(s, accent, letter){ return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s; }); // add combo back base.$preview.val( base.$preview.caret().replace(t2) ); val = base.$preview.val(); } } // check input restrictions - in case content was pasted if (o.restrictInput && val !== '') { t = val; r = base.acceptedKeys.length; for (i=0; i < r; i++){ if (t === '') { continue; } t2 = base.acceptedKeys[i]; if (val.indexOf(t2) >= 0) { // escape out all special characters if (/[\[|\]|\\|\^|\$|\.|\||\?|\*|\+|\(|\)|\{|\}]/g.test(t2)) { t2 = '\\' + t2; } t = t.replace( (new RegExp(t2, "g")), ''); } } // what's left over are keys that aren't in the acceptedKeys array if (t !== '') { val = val.replace(t, ''); } } // save changes, then reposition caret pos.start += val.length - len; pos.end += val.length - len; base.$preview.val(val); base.$preview.caret(pos.start, pos.end); // calculate current cursor scroll location and set scrolltop to keep it in view base.preview.scrollTop = base.lineHeight * (val.substring(0, pos.start).split('\n').length - 1); // find row, multiply by font-size base.lastCaret = { start: pos.start, end: pos.end }; if (o.acceptValid) { base.checkValid(); } return val; // return text, used for keyboard closing section }; // Toggle accept button classes, if validating base.checkValid = function(){ var valid = true; if (o.validate && typeof o.validate === "function") { valid = o.validate(base, base.$preview.val(), false); } // toggle accept button classes; defined in the css base.$keyboard.find('.ui-keyboard-accept') [valid ? 'removeClass' : 'addClass']('ui-keyboard-invalid-input') [valid ? 'addClass' : 'removeClass']('ui-keyboard-valid-input'); }; // Decimal button for num pad - only allow one (not used by default) base.checkDecimal = function(){ // Check US "." or European "," format if ( ( base.decimal && /\./g.test(base.preview.value) ) || ( !base.decimal && /\,/g.test(base.preview.value) ) ) { base.$decBtn .attr({ 'disabled': 'disabled', 'aria-disabled': 'true' }) .removeClass(o.css.buttonDefault + ' ' + o.css.buttonHover) .addClass(o.css.buttonDisabled); } else { base.$decBtn .removeAttr('disabled') .attr({ 'aria-disabled': 'false' }) .addClass(o.css.buttonDefault) .removeClass(o.css.buttonDisabled); } }; // get other layer values for a specific key base.getLayers = function(el){ var key, keys; key = el.attr('data-pos'); keys = el.closest('.ui-keyboard').find('button[data-pos="' + key + '"]').map(function(){ // added '> span' because jQuery mobile adds multiple spans inside the button return $(this).find('> span').text(); }).get(); return keys; }; base.isCurrent = function(set){ var cur = $.keyboard.currentKeyboard || false; if (set) { cur = $.keyboard.currentKeyboard = base.el; } else if (set === false && cur === base.el) { cur = $.keyboard.currentKeyboard = ''; } return cur === base.el; }; // Go to next or prev inputs // goToNext = true, then go to next input; if false go to prev // isAccepted is from autoAccept option or true if user presses shift-enter base.switchInput = function(goToNext, isAccepted){ if (typeof o.switchInput === "function") { o.switchInput(base, goToNext, isAccepted); } else { var kb, stopped = false, all = $('.ui-keyboard-input:visible'), indx = all.index(base.$el) + (goToNext ? 1 : -1); if (indx > all.length - 1) { stopped = o.stopAtEnd; indx = 0; // go to first input } if (indx < 0) { stopped = o.stopAtEnd; indx = all.length - 1; // stop or go to last } if (!stopped) { base.close(isAccepted); kb = all.eq(indx).data('keyboard'); if (kb && kb.options.openOn.length) { kb.focusOn(); } } } return false; }; // Close the keyboard, if visible. Pass a status of true, if the content was accepted (for the event trigger). base.close = function(accepted){ if (base.isVisible()) { clearTimeout(base.throttled); var val = (accepted) ? base.checkCombos() : base.originalContent; // validate input if accepted if (accepted && o.validate && typeof(o.validate) === "function" && !o.validate(base, val, true)) { val = base.originalContent; accepted = false; if (o.cancelClose) { return; } } base.isCurrent(false); base.$el .removeClass('ui-keyboard-input-current ui-keyboard-autoaccepted') // add "ui-keyboard-autoaccepted" to inputs .addClass( (accepted || false) ? accepted === true ? '' : 'ui-keyboard-autoaccepted' : '' ) .trigger( (o.alwaysOpen) ? '' : 'beforeClose.keyboard', [ base, base.el, (accepted || false) ] ) .val( val ) .scrollTop( base.el.scrollHeight ) .trigger( ((accepted || false) ? 'accepted.keyboard' : 'canceled.keyboard'), [ base, base.el ] ) .trigger( (o.alwaysOpen) ? 'inactive.keyboard' : 'hidden.keyboard', [ base, base.el ] ) .blur(); if (o.openOn) { // rebind input focus - delayed to fix IE issue #72 base.timer = setTimeout(function(){ base.$el.bind( o.openOn + '.keyboard', function(){ base.focusOn(); }); // remove focus from element (needed for IE since blur doesn't seem to work) if ($(':focus')[0] === base.el) { base.$el.blur(); } }, 500); } if (!o.alwaysOpen) { base.$keyboard.hide(); } if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') { base.$el .addClass('ui-keyboard-placeholder') .val(base.inPlaceholder); } } return !!accepted; }; base.accept = function(){ return base.close(true); }; base.escClose = function(e){ if ( e.type === 'keyup' ) { return ( e.which === 27 ) ? base.close() : ''; } var cur = base.isCurrent(); // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or single stay open keyboard if ( !base.isVisible() || (o.alwaysOpen && !cur) || (!o.alwaysOpen && o.stayOpen && cur && !base.isVisible()) ) { return; } // ignore autoaccept if using escape - good idea? if ( e.target !== base.el && cur ) { // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open if ( base.allie ) { e.preventDefault(); } base.close( o.autoAccept ? 'true' : false ); } }; // Build default button base.keyBtn = $('