jquery.keyboard.js 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573
  1. /*!
  2. jQuery UI Virtual Keyboard
  3. Version 1.17.4
  4. Author: Jeremy Satterfield
  5. Modified: Rob Garrison (Mottie on github)
  6. -----------------------------------------
  7. Licensed under the MIT License
  8. Caret code modified from jquery.caret.1.02.js
  9. Licensed under the MIT License:
  10. http://www.opensource.org/licenses/mit-license.php
  11. -----------------------------------------
  12. An on-screen virtual keyboard embedded within the browser window which
  13. will popup when a specified entry field is focused. The user can then
  14. type and preview their input before Accepting or Canceling.
  15. As a plugin to jQuery UI styling and theme will automatically
  16. match that used by jQuery UI with the exception of the required
  17. CSS listed below.
  18. Requires:
  19. jQuery
  20. jQuery UI (position utility only) & CSS
  21. Usage:
  22. $('input[type=text], input[type=password], textarea')
  23. .keyboard({
  24. layout:"qwerty",
  25. customLayout: {
  26. 'default': [
  27. "q w e r t y {bksp}",
  28. "s a m p l e {shift}",
  29. "{accept} {space} {cancel}"
  30. ],
  31. 'shift' : [
  32. "Q W E R T Y {bksp}",
  33. "S A M P L E {shift}",
  34. "{accept} {space} {cancel}"
  35. ]
  36. }
  37. });
  38. Options:
  39. layout
  40. [String] specify which keyboard layout to use
  41. qwerty - Standard QWERTY layout (Default)
  42. international - US international layout
  43. alpha - Alphabetical layout
  44. dvorak - Dvorak Simplified layout
  45. num - Numerical (ten-key) layout
  46. custom - Uses a custom layout as defined by the customLayout option
  47. customLayout
  48. [Object] Specify a custom layout
  49. An Object containing a set of key:value pairs, each key is a keyset.
  50. The key can be one to four rows (default, shifted, alt and alt-shift) or any number of meta key sets (meta1, meta2, etc).
  51. The value is an array with string elements of which each defines a new keyboard row.
  52. Each string element must have each character or key seperated by a space.
  53. 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
  54. In the list below where two special/"Action" keys are shown, both keys have the same action but different appearances (abbreviated/full name keys).
  55. Special/"Action" keys include:
  56. {a}, {accept} - Updates element value and closes keyboard
  57. {alt},{altgr} - AltGr for International keyboard
  58. {b}, {bksp} - Backspace
  59. {c}, {cancel} - Clears changes and closes keyboard
  60. {clear} - Clear input window - used in num pad
  61. {combo} - Toggle combo (diacritic) key
  62. {dec} - Decimal for numeric entry, only allows one decimal (optional use in num pad)
  63. {default} - Switch to the default keyset
  64. {e}, {enter} - Return/New Line
  65. {empty} - empty (blank) key
  66. {lock} - Caps lock key
  67. {meta#} - Meta keys that change the key set (# can be any integer)
  68. {next} - Switch to next keyboard input/textarea
  69. {prev} - Switch to previous keyboard input/textarea
  70. {s}, {shift} - Shift
  71. {sign} - Change sign of numeric entry (positive or negative)
  72. {sp:#} - Replace # with a numerical value, adds blank space, value of 1 ~ width of one key
  73. {space} - Spacebar
  74. {t}, {tab} - Tab
  75. CSS:
  76. Please see the keyboard.css file
  77. */
  78. /*jshint browser:true, jquery:true, unused:false */
  79. ;(function($){
  80. "use strict";
  81. $.keyboard = function(el, options){
  82. var base = this, o;
  83. // Access to jQuery and DOM versions of element
  84. base.$el = $(el);
  85. base.el = el;
  86. // Add a reverse reference to the DOM object
  87. base.$el.data("keyboard", base);
  88. base.init = function(){
  89. base.options = o = $.extend(true, {}, $.keyboard.defaultOptions, options);
  90. // Shift and Alt key toggles, sets is true if a layout has more than one keyset - used for mousewheel message
  91. base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
  92. base.lastKeyset = [false, false, false]; // [shift, alt, meta]
  93. // Class names of the basic key set - meta keysets are handled by the keyname
  94. base.rows = [ '', '-shift', '-alt', '-alt-shift' ];
  95. base.acceptedKeys = [];
  96. base.mappedKeys = {}; // for remapping manually typed in keys
  97. $('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]><script>jQuery("body").addClass("ie");</script><![endif]-->').appendTo('body').remove();
  98. base.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
  99. base.allie = $('body').hasClass('ie');
  100. base.inPlaceholder = base.$el.attr('placeholder') || '';
  101. base.watermark = (typeof(document.createElement('input').placeholder) !== 'undefined' && base.inPlaceholder !== ''); // html 5 placeholder/watermark
  102. base.regex = $.keyboard.comboRegex; // save default regex (in case loading another layout changes it)
  103. base.decimal = ( /^\./.test(o.display.dec) ) ? true : false; // determine if US "." or European "," system being used
  104. // convert mouse repeater rate (characters per second) into a time in milliseconds.
  105. base.repeatTime = 1000/(o.repeatRate || 20);
  106. // Check if caret position is saved when input is hidden or loses focus
  107. // (*cough* all versions of IE and I think Opera has/had an issue as well
  108. base.temp = $('<input style="position:absolute;left:-9999em;top:-9999em;" type="text" value="testing">').appendTo('body').caret(3,3);
  109. // Also save caret position of the input if it is locked
  110. base.checkCaret = (o.lockInput || base.temp.hide().show().caret().start !== 3 ) ? true : false;
  111. base.temp.remove();
  112. base.lastCaret = { start:0, end:0 };
  113. base.temp = [ '', 0, 0 ]; // used when building the keyboard - [keyset element, row, index]
  114. // Bind events
  115. $.each('initialized beforeVisible visible hidden canceled accepted beforeClose'.split(' '), function(i,f){
  116. if ($.isFunction(o[f])){
  117. base.$el.bind(f + '.keyboard', o[f]);
  118. }
  119. });
  120. // Close with esc key & clicking outside
  121. if (o.alwaysOpen) { o.stayOpen = true; }
  122. $(document).bind('mousedown.keyboard keyup.keyboard touchstart.keyboard', function(e){
  123. if (base.opening) { return; }
  124. base.escClose(e);
  125. // needed for IE to allow switching between keyboards smoothly
  126. if ( e.target && $(e.target).hasClass('ui-keyboard-input') ) {
  127. var kb = $(e.target).data('keyboard');
  128. if (kb && kb.options.openOn) {
  129. kb.focusOn();
  130. }
  131. }
  132. });
  133. // Display keyboard on focus
  134. base.$el
  135. .addClass('ui-keyboard-input ' + o.css.input)
  136. .attr({ 'aria-haspopup' : 'true', 'role' : 'textbox' });
  137. // add disabled/readonly class - dynamically updated on reveal
  138. if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass('ui-keyboard-lockedinput'))) {
  139. base.$el.addClass('ui-keyboard-nokeyboard');
  140. }
  141. if (o.openOn) {
  142. base.$el.bind(o.openOn + '.keyboard', function(){
  143. base.focusOn();
  144. });
  145. }
  146. // Add placeholder if not supported by the browser
  147. if (!base.watermark && base.$el.val() === '' && base.inPlaceholder !== '' && base.$el.attr('placeholder') !== '') {
  148. base.$el
  149. .addClass('ui-keyboard-placeholder') // css watermark style (darker text)
  150. .val( base.inPlaceholder );
  151. }
  152. base.$el.trigger( 'initialized.keyboard', [ base, base.el ] );
  153. // initialized with keyboard open
  154. if (o.alwaysOpen) {
  155. base.reveal();
  156. }
  157. };
  158. base.focusOn = function(){
  159. if (base.$el.is(':visible')) {
  160. // caret position is always 0,0 in webkit; and nothing is focused at this point... odd
  161. // save caret position in the input to transfer it to the preview
  162. base.lastCaret = base.$el.caret();
  163. }
  164. if (!base.isVisible() || o.alwaysOpen) {
  165. clearTimeout(base.timer);
  166. base.reveal();
  167. }
  168. };
  169. base.reveal = function(){
  170. base.opening = true;
  171. // close all keyboards
  172. $('.ui-keyboard:not(.ui-keyboard-always-open)').hide();
  173. // Don't open if disabled
  174. if (base.$el.is(':disabled') || (base.$el.attr('readonly') && !base.$el.hasClass('ui-keyboard-lockedinput'))) {
  175. base.$el.addClass('ui-keyboard-nokeyboard');
  176. return;
  177. } else {
  178. base.$el.removeClass('ui-keyboard-nokeyboard');
  179. }
  180. // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
  181. if (o.openOn) {
  182. base.$el.unbind( o.openOn + '.keyboard' );
  183. }
  184. // build keyboard if it doesn't exist
  185. if (typeof(base.$keyboard) === 'undefined') { base.startup(); }
  186. // ui-keyboard-has-focus is applied in case multiple keyboards have alwaysOpen = true and are stacked
  187. $('.ui-keyboard-has-focus').removeClass('ui-keyboard-has-focus');
  188. $('.ui-keyboard-input-current').removeClass('ui-keyboard-input-current');
  189. base.$el.addClass('ui-keyboard-input-current');
  190. base.isCurrent(true);
  191. // clear watermark
  192. if (!base.watermark && base.el.value === base.inPlaceholder) {
  193. base.$el
  194. .removeClass('ui-keyboard-placeholder')
  195. .val('');
  196. }
  197. // save starting content, in case we cancel
  198. base.originalContent = base.$el.val();
  199. base.$preview.val( base.originalContent );
  200. // disable/enable accept button
  201. if (o.acceptValid) { base.checkValid(); }
  202. // get single target position || target stored in element data (multiple targets) || default, at the element
  203. var p, s;
  204. base.position = o.position;
  205. base.position.of = base.position.of || base.$el.data('keyboardPosition') || base.$el;
  206. base.position.collision = (o.usePreview) ? base.position.collision || 'fit fit' : 'flip flip';
  207. if (o.resetDefault) {
  208. base.shiftActive = base.altActive = base.metaActive = false;
  209. base.showKeySet();
  210. }
  211. // basic positioning before it is set by position utility
  212. base.$keyboard.css({ position: 'absolute', left: 0, top: 0 });
  213. // beforeVisible event
  214. base.$el.trigger( 'beforeVisible.keyboard', [ base, base.el ] );
  215. // show keyboard
  216. base.$keyboard
  217. .addClass('ui-keyboard-has-focus')
  218. .show();
  219. // adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
  220. if (o.usePreview && base.msie) {
  221. if (typeof base.width === 'undefined') {
  222. base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing
  223. base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row
  224. base.$preview.show();
  225. }
  226. base.$preview.width(base.width);
  227. }
  228. // position after keyboard is visible (required for UI position utility) and appropriately sized
  229. if ($.ui.position) {
  230. base.$keyboard.position(base.position);
  231. }
  232. if (o.initialFocus) {
  233. base.$preview.focus();
  234. }
  235. base.checkDecimal();
  236. // get preview area line height
  237. // add roughly 4px to get line height from font height, works well for font-sizes from 14-36px - needed for textareas
  238. base.lineHeight = parseInt( base.$preview.css('lineHeight'), 10) || parseInt(base.$preview.css('font-size') ,10) + 4;
  239. if (o.caretToEnd) {
  240. s = base.originalContent.length;
  241. base.lastCaret = {
  242. start: s,
  243. end : s
  244. };
  245. }
  246. // IE caret haxx0rs
  247. if (base.allie){
  248. // ensure caret is at the end of the text (needed for IE)
  249. s = base.lastCaret.start || base.originalContent.length;
  250. p = { start: s, end: s };
  251. if (!base.lastCaret) { base.lastCaret = p; } // set caret at end of content, if undefined
  252. if (base.lastCaret.end === 0 && base.lastCaret.start > 0) { base.lastCaret.end = base.lastCaret.start; } // sometimes end = 0 while start is > 0
  253. 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/).
  254. }
  255. base.$el.trigger( 'visible.keyboard', [ base, base.el ] );
  256. // opening keyboard flag; delay allows switching between keyboards without immediately closing the keyboard
  257. setTimeout(function(){
  258. base.opening = false;
  259. if (o.initialFocus) {
  260. base.$preview.caret( base.lastCaret.start, base.lastCaret.end );
  261. }
  262. }, 10);
  263. // return base to allow chaining in typing extension
  264. return base;
  265. };
  266. base.startup = function(){
  267. base.$keyboard = base.buildKeyboard();
  268. base.$allKeys = base.$keyboard.find('button.ui-keyboard-button');
  269. base.preview = base.$preview[0];
  270. base.$decBtn = base.$keyboard.find('.ui-keyboard-dec');
  271. base.wheel = $.isFunction( $.fn.mousewheel ); // is mousewheel plugin loaded?
  272. // keyCode of keys always allowed to be typed - caps lock, page up & down, end, home, arrow, insert & delete keys
  273. base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
  274. if (o.enterNavigation) { base.alwaysAllowed.push(13); } // add enter to allowed keys
  275. base.$preview
  276. .bind('keypress.keyboard', function(e){
  277. var k = base.lastKey = String.fromCharCode(e.charCode || e.which);
  278. base.$lastKey = []; // not a virtual keyboard key
  279. if (base.checkCaret) { base.lastCaret = base.$preview.caret(); }
  280. // update caps lock - can only do this while typing =(
  281. base.capsLock = (((k >= 65 && k <= 90) && !e.shiftKey) || ((k >= 97 && k <= 122) && e.shiftKey)) ? true : false;
  282. // restrict input - keyCode in keypress special keys: see http://www.asquare.net/javascript/tests/KeyCode.html
  283. if (o.restrictInput) {
  284. // allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
  285. if ( (e.which === 8 || e.which === 0) && $.inArray( e.keyCode, base.alwaysAllowed ) ) { return; }
  286. if ($.inArray(k, base.acceptedKeys) === -1) { e.preventDefault(); } // quick key check
  287. } else if ( (e.ctrlKey || e.metaKey) && (e.which === 97 || e.which === 99 || e.which === 118 || (e.which >= 120 && e.which <=122)) ) {
  288. // 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
  289. return;
  290. }
  291. // Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
  292. // 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)
  293. // example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
  294. if (base.hasMappedKeys) {
  295. if (base.mappedKeys.hasOwnProperty(k)){
  296. base.lastKey = base.mappedKeys[k];
  297. base.insertText( base.lastKey );
  298. e.preventDefault();
  299. }
  300. }
  301. base.checkMaxLength();
  302. })
  303. .bind('keyup.keyboard', function(e){
  304. switch (e.which) {
  305. // Insert tab key
  306. case 9 :
  307. // Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab to the keyboard preview
  308. // area on keyup. Sadly it still happens if you don't release the tab key immediately because keydown event auto-repeats
  309. if (base.tab && o.tabNavigation && !o.lockInput) {
  310. base.shiftActive = e.shiftKey;
  311. $.keyboard.keyaction.tab(base);
  312. base.tab = false;
  313. } else {
  314. e.preventDefault();
  315. }
  316. break;
  317. // Escape will hide the keyboard
  318. case 27:
  319. base.close();
  320. return false;
  321. }
  322. // throttle the check combo function because fast typers will have an incorrectly positioned caret
  323. clearTimeout(base.throttled);
  324. base.throttled = setTimeout(function(){
  325. // fix error in OSX? see issue #102
  326. if (base.isVisible()) {
  327. base.checkCombos();
  328. }
  329. }, 100);
  330. base.checkMaxLength();
  331. // change callback is no longer bound to the input element as the callback could be
  332. // called during an external change event with all the necessary parameters (issue #157)
  333. if ($.isFunction(o.change)){ o.change( $.Event("change"), base, base.el ); }
  334. base.$el.trigger( 'change.keyboard', [ base, base.el ] );
  335. })
  336. .bind('keydown.keyboard', function(e){
  337. switch (e.which) {
  338. // prevent tab key from leaving the preview window
  339. case 9 :
  340. if (o.tabNavigation) {
  341. // allow tab to pass through - tab to next input/shift-tab for prev
  342. base.tab = true;
  343. return false;
  344. } else {
  345. base.tab = true; // see keyup comment above
  346. return false;
  347. }
  348. break; // adding a break here to make jsHint happy
  349. case 13:
  350. $.keyboard.keyaction.enter(base, null, e);
  351. break;
  352. // Show capsLock
  353. case 20:
  354. base.shiftActive = base.capsLock = !base.capsLock;
  355. base.showKeySet(this);
  356. break;
  357. case 86:
  358. // prevent ctrl-v/cmd-v
  359. if (e.ctrlKey || e.metaKey) {
  360. if (o.preventPaste) { e.preventDefault(); return; }
  361. base.checkCombos(); // check pasted content
  362. }
  363. break;
  364. }
  365. })
  366. .bind('mouseup.keyboard touchend.keyboard', function(){
  367. if (base.checkCaret) { base.lastCaret = base.$preview.caret(); }
  368. });
  369. //.bind('mousemove.keyboard', function(){
  370. // if (!o.alwaysOpen && $.keyboard.currentKeyboard === base.el && !base.opening) { base.$preview.focus(); }
  371. //});
  372. // prevent keyboard event bubbling
  373. base.$keyboard.bind('mousedown.keyboard click.keyboard touchstart.keyboard', function(e){
  374. e.stopPropagation();
  375. });
  376. // If preventing paste, block context menu (right click)
  377. if (o.preventPaste){
  378. base.$preview.bind('contextmenu.keyboard', function(e){ e.preventDefault(); });
  379. base.$el.bind('contextmenu.keyboard', function(e){ e.preventDefault(); });
  380. }
  381. if (o.appendLocally) {
  382. base.$el.after( base.$keyboard );
  383. } else {
  384. base.$keyboard.appendTo('body');
  385. }
  386. base.$allKeys
  387. .bind(o.keyBinding.split(' ').join('.keyboard ') + '.keyboard repeater.keyboard', function(e){
  388. // prevent errors when external triggers attempt to "type" - see issue #158
  389. if (!base.$keyboard.is(":visible")){ return false; }
  390. // 'key', { action: doAction, original: n, curTxt : n, curNum: 0 }
  391. var txt, key = $.data(this, 'key'), action = key.action.split(':')[0];
  392. base.$preview.focus();
  393. base.$lastKey = $(this);
  394. base.lastKey = key.curTxt;
  395. // Start caret in IE when not focused (happens with each virtual keyboard button click
  396. if (base.checkCaret) { base.$preview.caret( base.lastCaret.start, base.lastCaret.end ); }
  397. if (action.match('meta')) { action = 'meta'; }
  398. if ($.keyboard.keyaction.hasOwnProperty(action) && $(this).hasClass('ui-keyboard-actionkey')) {
  399. // stop processing if action returns false (close & cancel)
  400. if ($.keyboard.keyaction[action](base,this,e) === false) { return false; }
  401. } else if (typeof key.action !== 'undefined') {
  402. txt = base.lastKey = (base.wheel && !$(this).hasClass('ui-keyboard-actionkey')) ? key.curTxt : key.action;
  403. base.insertText(txt);
  404. if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
  405. base.shiftActive = false;
  406. base.showKeySet(this);
  407. }
  408. }
  409. base.checkCombos();
  410. base.checkMaxLength();
  411. if ($.isFunction(o.change)){ o.change( $.Event("change"), base, base.el ); }
  412. base.$el.trigger( 'change.keyboard', [ base, base.el ] );
  413. base.$preview.focus();
  414. // attempt to fix issue #131
  415. if (base.checkCaret) { base.$preview.caret( base.lastCaret.start, base.lastCaret.end ); }
  416. e.preventDefault();
  417. })
  418. // Change hover class and tooltip
  419. .bind('mouseenter.keyboard mouseleave.keyboard', function(e){
  420. if (!base.isCurrent()) { return; }
  421. var el = this, $this = $(this),
  422. // 'key' = { action: doAction, original: n, curTxt : n, curNum: 0 }
  423. key = $.data(el, 'key');
  424. if (e.type === 'mouseenter' && base.el.type !== 'password' && !$this.hasClass(o.css.buttonDisabled) ){
  425. $this
  426. .addClass(o.css.buttonHover)
  427. .attr('title', function(i,t){
  428. // show mouse wheel message
  429. return (base.wheel && t === '' && base.sets) ? o.wheelMessage : t;
  430. });
  431. }
  432. if (e.type === 'mouseleave'){
  433. key.curTxt = key.original;
  434. key.curNum = 0;
  435. $.data(el, 'key', key);
  436. $this
  437. .removeClass( (base.el.type === 'password') ? '' : o.css.buttonHover) // needed or IE flickers really bad
  438. .attr('title', function(i,t){ return (t === o.wheelMessage) ? '' : t; })
  439. .find('span').text( key.original ); // restore original button text
  440. }
  441. })
  442. // Allow mousewheel to scroll through other key sets of the same key
  443. .bind('mousewheel.keyboard', function(e, delta){
  444. if (base.wheel) {
  445. var txt, $this = $(this), key = $.data(this, 'key');
  446. txt = key.layers || base.getLayers( $this );
  447. key.curNum += (delta > 0) ? -1 : 1;
  448. if (key.curNum > txt.length-1) { key.curNum = 0; }
  449. if (key.curNum < 0) { key.curNum = txt.length-1; }
  450. key.layers = txt;
  451. key.curTxt = txt[key.curNum];
  452. $.data(this, 'key', key);
  453. $this.find('span').text( txt[key.curNum] );
  454. return false;
  455. }
  456. })
  457. // using "kb" namespace for mouse repeat functionality to keep it separate
  458. // I need to trigger a "repeater.keyboard" to make it work
  459. .bind('mouseup.keyboard mouseleave.kb touchend.kb touchmove.kb touchcancel.kb', function(e){
  460. if (e.type === 'mouseleave') {
  461. $(this).removeClass(o.css.buttonHover); // needed for touch devices
  462. } else {
  463. if (base.isVisible() && base.isCurrent()) { base.$preview.focus(); }
  464. if (base.checkCaret) { base.$preview.caret( base.lastCaret.start, base.lastCaret.end ); }
  465. }
  466. base.mouseRepeat = [false,''];
  467. clearTimeout(base.repeater); // make sure key repeat stops!
  468. return false;
  469. })
  470. // prevent form submits when keyboard is bound locally - issue #64
  471. .bind('click.keyboard', function(){
  472. return false;
  473. })
  474. // no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
  475. .filter(':not(.ui-keyboard-actionkey)')
  476. // mouse repeated action key exceptions
  477. .add('.ui-keyboard-tab, .ui-keyboard-bksp, .ui-keyboard-space, .ui-keyboard-enter', base.$keyboard)
  478. .bind('mousedown.kb touchstart.kb', function(){
  479. if (o.repeatRate !== 0) {
  480. var key = $(this);
  481. base.mouseRepeat = [true, key]; // save the key, make sure we are repeating the right one (fast typers)
  482. setTimeout(function() {
  483. if (base.mouseRepeat[0] && base.mouseRepeat[1] === key) { base.repeatKey(key); }
  484. }, o.repeatDelay);
  485. }
  486. return false;
  487. });
  488. // adjust with window resize
  489. $(window).resize(function(){
  490. if (base.isVisible()) {
  491. base.$keyboard.position(base.position);
  492. }
  493. });
  494. };
  495. base.isVisible = function() {
  496. if (typeof(base.$keyboard) === 'undefined') {
  497. return false;
  498. }
  499. return base.$keyboard.is(":visible");
  500. };
  501. // Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
  502. base.insertText = function(txt){
  503. var bksp, t, h,
  504. // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
  505. val = base.$preview.val(),
  506. pos = base.$preview.caret(),
  507. scrL = base.$preview.scrollLeft(),
  508. scrT = base.$preview.scrollTop(),
  509. len = val.length; // save original content length
  510. // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea is still difficult
  511. if (pos.end < pos.start) { pos.end = pos.start; } // in IE, pos.end can be zero after input loses focus
  512. if (pos.start > len) { pos.end = pos.start = len; }
  513. if (base.preview.tagName === 'TEXTAREA') {
  514. // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
  515. if (base.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; }
  516. // Set scroll top so current text is in view - needed for virtual keyboard typing, not manual typing
  517. // this doesn't appear to work correctly in Opera
  518. h = (val.split('\n').length - 1);
  519. base.preview.scrollTop = (h>0) ? base.lineHeight * h : scrT;
  520. }
  521. bksp = (txt === 'bksp' && pos.start === pos.end) ? true : false;
  522. txt = (txt === 'bksp') ? '' : txt;
  523. t = pos.start + (bksp ? -1 : txt.length);
  524. scrL += parseInt(base.$preview.css('fontSize'),10) * (txt === 'bksp' ? -1 : 1);
  525. base.$preview
  526. .val( base.$preview.val().substr(0, pos.start - (bksp ? 1 : 0)) + txt + base.$preview.val().substr(pos.end) )
  527. .caret(t, t)
  528. .scrollLeft(scrL);
  529. if (base.checkCaret) { base.lastCaret = { start: t, end: t }; } // save caret in case of bksp
  530. };
  531. // check max length
  532. base.checkMaxLength = function(){
  533. var t, p = base.$preview.val();
  534. if (o.maxLength !== false && p.length > o.maxLength) {
  535. t = Math.min(base.$preview.caret().start, o.maxLength);
  536. base.$preview.val( p.substring(0, o.maxLength) );
  537. // restore caret on change, otherwise it ends up at the end.
  538. base.$preview.caret( t, t );
  539. base.lastCaret = { start: t, end: t };
  540. }
  541. if (base.$decBtn.length) {
  542. base.checkDecimal();
  543. }
  544. };
  545. // mousedown repeater
  546. base.repeatKey = function(key){
  547. key.trigger('repeater.keyboard');
  548. if (base.mouseRepeat[0]) {
  549. base.repeater = setTimeout(function() {
  550. base.repeatKey(key);
  551. }, base.repeatTime);
  552. }
  553. };
  554. base.showKeySet = function(el){
  555. var key = '',
  556. toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
  557. if (!base.shiftActive) { base.capsLock = false; }
  558. // check meta key set
  559. if (base.metaActive) {
  560. // the name attribute contains the meta set # "meta99"
  561. key = (el && el.name && /meta/.test(el.name)) ? el.name : '';
  562. // save active meta keyset name
  563. if (key === '') {
  564. key = (base.metaActive === true) ? '' : base.metaActive;
  565. } else {
  566. base.metaActive = key;
  567. }
  568. // if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
  569. if ( (!o.stickyShift && base.lastKeyset[2] !== base.metaActive) ||
  570. ( (base.shiftActive || base.altActive) && !base.$keyboard.find('.ui-keyboard-keyset-' + key + base.rows[toShow]).length) ) {
  571. base.shiftActive = base.altActive = false;
  572. }
  573. } else if (!o.stickyShift && base.lastKeyset[2] !== base.metaActive && base.shiftActive) {
  574. // switching from meta key set back to default, reset shift & alt if using stickyShift
  575. base.shiftActive = base.altActive = false;
  576. }
  577. toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
  578. key = (toShow === 0 && !base.metaActive) ? '-default' : (key === '') ? '' : '-' + key;
  579. if (!base.$keyboard.find('.ui-keyboard-keyset' + key + base.rows[toShow]).length) {
  580. // keyset doesn't exist, so restore last keyset settings
  581. base.shiftActive = base.lastKeyset[0];
  582. base.altActive = base.lastKeyset[1];
  583. base.metaActive = base.lastKeyset[2];
  584. return;
  585. }
  586. base.$keyboard
  587. .find('.ui-keyboard-alt, .ui-keyboard-shift, .ui-keyboard-actionkey[class*=meta]').removeClass(o.css.buttonAction).end()
  588. .find('.ui-keyboard-alt')[(base.altActive) ? 'addClass' : 'removeClass'](o.css.buttonAction).end()
  589. .find('.ui-keyboard-shift')[(base.shiftActive) ? 'addClass' : 'removeClass'](o.css.buttonAction).end()
  590. .find('.ui-keyboard-lock')[(base.capsLock) ? 'addClass' : 'removeClass'](o.css.buttonAction).end()
  591. .find('.ui-keyboard-keyset').hide().end()
  592. .find('.ui-keyboard-keyset' + key + base.rows[toShow]).show().end()
  593. .find('.ui-keyboard-actionkey.ui-keyboard' + key).addClass(o.css.buttonAction);
  594. base.lastKeyset = [ base.shiftActive, base.altActive, base.metaActive ];
  595. };
  596. // check for key combos (dead keys)
  597. base.checkCombos = function(){
  598. var i, r, t, t2,
  599. // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
  600. val = base.$preview.val(),
  601. pos = base.$preview.caret(),
  602. len = val.length; // save original content length
  603. // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea is still difficult
  604. if (pos.end < pos.start) { pos.end = pos.start; } // in IE, pos.end can be zero after input loses focus
  605. if (pos.start > len) { pos.end = pos.start = len; }
  606. // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
  607. if (base.msie && val.substr(pos.start, 1) === '\n') { pos.start += 1; pos.end += 1; }
  608. if (o.useCombos) {
  609. // keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
  610. // thanks to KennyTM: http://stackoverflow.com/questions/4275077/replace-characters-to-make-international-letters-diacritics
  611. // original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
  612. if (base.msie) {
  613. // old IE may not have the caret positioned correctly, so just check the whole thing
  614. val = val.replace(base.regex, function(s, accent, letter){
  615. return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
  616. });
  617. // prevent combo replace error, in case the keyboard closes - see issue #116
  618. } else if (base.$preview.length) {
  619. // Modern browsers - check for combos from last two characters left of the caret
  620. t = pos.start - (pos.start - 2 >= 0 ? 2 : 0);
  621. // target last two characters
  622. base.$preview.caret(t, pos.end);
  623. // do combo replace
  624. t2 = base.$preview.caret().text.replace(base.regex, function(s, accent, letter){
  625. return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
  626. });
  627. // add combo back
  628. base.$preview.val( base.$preview.caret().replace(t2) );
  629. val = base.$preview.val();
  630. }
  631. }
  632. // check input restrictions - in case content was pasted
  633. if (o.restrictInput && val !== '') {
  634. t = val;
  635. r = base.acceptedKeys.length;
  636. for (i=0; i < r; i++){
  637. if (t === '') { continue; }
  638. t2 = base.acceptedKeys[i];
  639. if (val.indexOf(t2) >= 0) {
  640. // escape out all special characters
  641. if (/[\[|\]|\\|\^|\$|\.|\||\?|\*|\+|\(|\)|\{|\}]/g.test(t2)) { t2 = '\\' + t2; }
  642. t = t.replace( (new RegExp(t2, "g")), '');
  643. }
  644. }
  645. // what's left over are keys that aren't in the acceptedKeys array
  646. if (t !== '') { val = val.replace(t, ''); }
  647. }
  648. // save changes, then reposition caret
  649. pos.start += val.length - len;
  650. pos.end += val.length - len;
  651. base.$preview.val(val);
  652. base.$preview.caret(pos.start, pos.end);
  653. // calculate current cursor scroll location and set scrolltop to keep it in view
  654. base.preview.scrollTop = base.lineHeight * (val.substring(0, pos.start).split('\n').length - 1); // find row, multiply by font-size
  655. base.lastCaret = { start: pos.start, end: pos.end };
  656. if (o.acceptValid) { base.checkValid(); }
  657. return val; // return text, used for keyboard closing section
  658. };
  659. // Toggle accept button classes, if validating
  660. base.checkValid = function(){
  661. var valid = true;
  662. if (o.validate && typeof o.validate === "function") {
  663. valid = o.validate(base, base.$preview.val(), false);
  664. }
  665. // toggle accept button classes; defined in the css
  666. base.$keyboard.find('.ui-keyboard-accept')
  667. [valid ? 'removeClass' : 'addClass']('ui-keyboard-invalid-input')
  668. [valid ? 'addClass' : 'removeClass']('ui-keyboard-valid-input');
  669. };
  670. // Decimal button for num pad - only allow one (not used by default)
  671. base.checkDecimal = function(){
  672. // Check US "." or European "," format
  673. if ( ( base.decimal && /\./g.test(base.preview.value) ) || ( !base.decimal && /\,/g.test(base.preview.value) ) ) {
  674. base.$decBtn
  675. .attr({ 'disabled': 'disabled', 'aria-disabled': 'true' })
  676. .removeClass(o.css.buttonDefault + ' ' + o.css.buttonHover)
  677. .addClass(o.css.buttonDisabled);
  678. } else {
  679. base.$decBtn
  680. .removeAttr('disabled')
  681. .attr({ 'aria-disabled': 'false' })
  682. .addClass(o.css.buttonDefault)
  683. .removeClass(o.css.buttonDisabled);
  684. }
  685. };
  686. // get other layer values for a specific key
  687. base.getLayers = function(el){
  688. var key, keys;
  689. key = el.attr('data-pos');
  690. keys = el.closest('.ui-keyboard').find('button[data-pos="' + key + '"]').map(function(){
  691. // added '> span' because jQuery mobile adds multiple spans inside the button
  692. return $(this).find('> span').text();
  693. }).get();
  694. return keys;
  695. };
  696. base.isCurrent = function(set){
  697. var cur = $.keyboard.currentKeyboard || false;
  698. if (set) {
  699. cur = $.keyboard.currentKeyboard = base.el;
  700. } else if (set === false && cur === base.el) {
  701. cur = $.keyboard.currentKeyboard = '';
  702. }
  703. return cur === base.el;
  704. };
  705. // Go to next or prev inputs
  706. // goToNext = true, then go to next input; if false go to prev
  707. // isAccepted is from autoAccept option or true if user presses shift-enter
  708. base.switchInput = function(goToNext, isAccepted){
  709. if (typeof o.switchInput === "function") {
  710. o.switchInput(base, goToNext, isAccepted);
  711. } else {
  712. base.$keyboard.hide();
  713. var kb, stopped = false,
  714. all = $('button, input, textarea, a').filter(':visible'),
  715. indx = all.index(base.$el) + (goToNext ? 1 : -1);
  716. base.$keyboard.show();
  717. if (indx > all.length - 1) {
  718. stopped = o.stopAtEnd;
  719. indx = 0; // go to first input
  720. }
  721. if (indx < 0) {
  722. stopped = o.stopAtEnd;
  723. indx = all.length - 1; // stop or go to last
  724. }
  725. if (!stopped) {
  726. base.close(isAccepted);
  727. kb = all.eq(indx).data('keyboard');
  728. if (kb && kb.options.openOn.length) {
  729. kb.focusOn();
  730. } else {
  731. all.eq(indx).focus();
  732. }
  733. }
  734. }
  735. return false;
  736. };
  737. // Close the keyboard, if visible. Pass a status of true, if the content was accepted (for the event trigger).
  738. base.close = function(accepted){
  739. if (base.isVisible()) {
  740. clearTimeout(base.throttled);
  741. var val = (accepted) ? base.checkCombos() : base.originalContent;
  742. // validate input if accepted
  743. if (accepted && o.validate && typeof(o.validate) === "function" && !o.validate(base, val, true)) {
  744. val = base.originalContent;
  745. accepted = false;
  746. if (o.cancelClose) { return; }
  747. }
  748. base.isCurrent(false);
  749. base.$el
  750. .removeClass('ui-keyboard-input-current ui-keyboard-autoaccepted')
  751. // add "ui-keyboard-autoaccepted" to inputs
  752. .addClass( (accepted || false) ? accepted === true ? '' : 'ui-keyboard-autoaccepted' : '' )
  753. .trigger( (o.alwaysOpen) ? '' : 'beforeClose.keyboard', [ base, base.el, (accepted || false) ] )
  754. .val( val )
  755. .scrollTop( base.el.scrollHeight )
  756. .trigger( ((accepted || false) ? 'accepted.keyboard' : 'canceled.keyboard'), [ base, base.el ] )
  757. .trigger( (o.alwaysOpen) ? 'inactive.keyboard' : 'hidden.keyboard', [ base, base.el ] )
  758. .blur();
  759. if (o.openOn) {
  760. // rebind input focus - delayed to fix IE issue #72
  761. base.timer = setTimeout(function(){
  762. base.$el.bind( o.openOn + '.keyboard', function(){ base.focusOn(); });
  763. // remove focus from element (needed for IE since blur doesn't seem to work)
  764. if ($(':focus')[0] === base.el) { base.$el.blur(); }
  765. }, 500);
  766. }
  767. if (!o.alwaysOpen) {
  768. base.$keyboard.hide();
  769. }
  770. if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') {
  771. base.$el
  772. .addClass('ui-keyboard-placeholder')
  773. .val(base.inPlaceholder);
  774. }
  775. // trigger default change event - see issue #146
  776. base.$el.trigger('change');
  777. }
  778. return !!accepted;
  779. };
  780. base.accept = function(){
  781. return base.close(true);
  782. };
  783. base.escClose = function(e){
  784. if ( e.type === 'keyup' ) {
  785. return ( e.which === 27 ) ? base.close() : '';
  786. }
  787. var cur = base.isCurrent();
  788. // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple always open keyboards or single stay open keyboard
  789. if ( !base.isVisible() || (o.alwaysOpen && !cur) || (!o.alwaysOpen && o.stayOpen && cur && !base.isVisible()) ) { return; }
  790. // ignore autoaccept if using escape - good idea?
  791. if ( e.target !== base.el && cur ) {
  792. // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
  793. if ( base.allie ) {
  794. e.preventDefault();
  795. }
  796. base.close( o.autoAccept ? 'true' : false );
  797. }
  798. };
  799. // Build default button
  800. base.keyBtn = $('<button />')
  801. .attr({ 'role': 'button', 'aria-disabled': 'false', 'tabindex' : '-1' })
  802. .addClass('ui-keyboard-button');
  803. // Add key function
  804. // keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
  805. // name = name added to key, or cross-referenced in the display options
  806. // newSet = keyset to attach the new button
  807. // regKey = true when it is not an action key
  808. base.addKey = function(keyName, name, regKey){
  809. var t, keyType, m, map, nm,
  810. n = (regKey === true) ? keyName : o.display[name] || keyName,
  811. kn = (regKey === true) ? keyName.charCodeAt(0) : keyName;
  812. // map defined keys - format "key(A):Label_for_key"
  813. // "key" = key that is seen (can any character; but it might need to be escaped using "\" or entered as unicode "\u####"
  814. // "(A)" = the actual key on the real keyboard to remap, ":Label_for_key" ends up in the title/tooltip
  815. if (/\(.+\)/.test(n)) { // n = "\u0391(A):alpha"
  816. map = n.replace(/\(([^()]+)\)/, ''); // remove "(A)", left with "\u0391:alpha"
  817. m = n.match(/\(([^()]+)\)/)[1]; // extract "A" from "(A)"
  818. n = map;
  819. nm = map.split(':');
  820. map = (nm[0] !== '' && nm.length > 1) ? nm[0] : map; // get "\u0391" from "\u0391:alpha"
  821. base.mappedKeys[m] = map;
  822. }
  823. // find key label
  824. nm = n.split(':');
  825. if (nm[0] === '' && nm[1] === '') { n = ':'; } // corner case of ":(:):;" reduced to "::;", split as ["", "", ";"]
  826. n = (nm[0] !== '' && nm.length > 1) ? $.trim(nm[0]) : n;
  827. t = (nm.length > 1) ? $.trim(nm[1]).replace(/_/g, " ") || '' : ''; // added to title
  828. // Action keys will have the 'ui-keyboard-actionkey' class
  829. // '\u2190'.length = 1 because the unicode is converted, so if more than one character, add the wide class
  830. keyType = (n.length > 1) ? ' ui-keyboard-widekey' : '';
  831. keyType += (regKey) ? '' : ' ui-keyboard-actionkey';
  832. return base.keyBtn
  833. .clone()
  834. .attr({ 'data-value' : n, 'name': kn, 'data-pos': base.temp[1] + ',' + base.temp[2], 'title' : t })
  835. .data('key', { action: keyName, original: n, curTxt : n, curNum: 0 })
  836. // add "ui-keyboard-" + keyName, if this is an action key (e.g. "Bksp" will have 'ui-keyboard-bskp' class)
  837. // add "ui-keyboard-" + unicode of 1st character (e.g. "~" is a regular key, class = 'ui-keyboard-126' (126 is the unicode value - same as typing &#126;)
  838. .addClass('ui-keyboard-' + kn + keyType + ' ' + o.css.buttonDefault)
  839. .html('<span>' + n + '</span>')
  840. .appendTo(base.temp[0]);
  841. };
  842. base.buildKeyboard = function(){
  843. var action, row, newSet, isAction,
  844. currentSet, key, keys, margin,
  845. sets = 0,
  846. container = $('<div />')
  847. .addClass('ui-keyboard ' + o.css.container + (o.alwaysOpen ? ' ui-keyboard-always-open' : '') )
  848. .attr({ 'role': 'textbox' })
  849. .hide();
  850. // build preview display
  851. if (o.usePreview) {
  852. base.$preview = base.$el.clone(false)
  853. .removeAttr('id')
  854. .removeClass('ui-keyboard-placeholder ui-keyboard-input')
  855. .addClass('ui-keyboard-preview ' + o.css.input)
  856. .attr('tabindex', '-1')
  857. .show(); // for hidden inputs
  858. // build preview container and append preview display
  859. $('<div />')
  860. .addClass('ui-keyboard-preview-wrapper')
  861. .append(base.$preview)
  862. .appendTo(container);
  863. } else {
  864. // No preview display, use element and reposition the keyboard under it.
  865. base.$preview = base.$el;
  866. o.position.at = o.position.at2;
  867. }
  868. if (o.lockInput) {
  869. base.$preview.addClass('ui-keyboard-lockedinput').attr({ 'readonly': 'readonly'});
  870. }
  871. // verify layout or setup custom keyboard
  872. if (o.layout === 'custom' || !$.keyboard.layouts.hasOwnProperty(o.layout)) {
  873. o.layout = 'custom';
  874. $.keyboard.layouts.custom = o.customLayout || { 'default' : ['{cancel}'] };
  875. }
  876. // Main keyboard building loop
  877. $.each($.keyboard.layouts[o.layout], function(set, keySet){
  878. if (set !== "") {
  879. sets++;
  880. newSet = $('<div />')
  881. .attr('name', set) // added for typing extension
  882. .addClass('ui-keyboard-keyset ui-keyboard-keyset-' + set)
  883. .appendTo(container)[(set === 'default') ? 'show' : 'hide']();
  884. for ( row = 0; row < keySet.length; row++ ){
  885. // remove extra spaces before spliting (regex probably could be improved)
  886. currentSet = $.trim(keySet[row]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g,'{$1:$2}');
  887. keys = currentSet.split(/\s+/);
  888. for ( key = 0; key < keys.length; key++ ) {
  889. // used by addKey function
  890. base.temp = [ newSet, row, key ];
  891. isAction = false;
  892. // ignore empty keys
  893. if (keys[key].length === 0) { continue; }
  894. // process here if it's an action key
  895. if( /^\{\S+\}$/.test(keys[key])){
  896. action = keys[key].match(/^\{(\S+)\}$/)[1].toLowerCase();
  897. // add active class if there are double exclamation points in the name
  898. if (/\!\!/.test(action)) {
  899. action = action.replace('!!','');
  900. isAction = true;
  901. }
  902. // add empty space
  903. if (/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/.test(action)) {
  904. // not perfect globalization, but allows you to use {sp:1,1em}, {sp:1.2em} or {sp:15px}
  905. margin = parseFloat( action.replace(/,/,'.').match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/)[1] || 0 );
  906. $('<span>&nbsp;</span>')
  907. // previously {sp:1} would add 1em margin to each side of a 0 width span
  908. // now Firefox doesn't seem to render 0px dimensions, so now we set the
  909. // 1em margin x 2 for the width
  910. .width( (action.match('px') ? margin + 'px' : (margin * 2) + 'em') )
  911. .addClass('ui-keyboard-button ui-keyboard-spacer')
  912. .appendTo(newSet);
  913. }
  914. // meta keys
  915. if (/^meta\d+\:?(\w+)?/.test(action)){
  916. base.addKey(action, action);
  917. continue;
  918. }
  919. // switch needed for action keys with multiple names/shortcuts or
  920. // default will catch all others
  921. switch(action){
  922. case 'a':
  923. case 'accept':
  924. base
  925. .addKey('accept', action)
  926. .addClass(o.css.buttonAction);
  927. break;
  928. case 'alt':
  929. case 'altgr':
  930. base.addKey('alt', 'alt');
  931. break;
  932. case 'b':
  933. case 'bksp':
  934. base.addKey('bksp', action);
  935. break;
  936. case 'c':
  937. case 'cancel':
  938. base
  939. .addKey('cancel', action)
  940. .addClass(o.css.buttonAction);
  941. break;
  942. // toggle combo/diacritic key
  943. case 'combo':
  944. base
  945. .addKey('combo', 'combo')
  946. .addClass(o.css.buttonAction);
  947. break;
  948. // Decimal - unique decimal point (num pad layout)
  949. case 'dec':
  950. base.acceptedKeys.push((base.decimal) ? '.' : ',');
  951. base.addKey('dec', 'dec');
  952. break;
  953. case 'e':
  954. case 'enter':
  955. base
  956. .addKey('enter', action)
  957. .addClass(o.css.buttonAction);
  958. break;
  959. case 'empty':
  960. base
  961. .addKey('', ' ')
  962. .addClass(o.css.buttonDisabled)
  963. .attr('aria-disabled', true);
  964. break;
  965. case 's':
  966. case 'shift':
  967. base.addKey('shift', action);
  968. break;
  969. // Change sign (for num pad layout)
  970. case 'sign':
  971. base.acceptedKeys.push('-');
  972. base.addKey('sign', 'sign');
  973. break;
  974. case 'space':
  975. base.acceptedKeys.push(' ');
  976. base.addKey('space', 'space');
  977. break;
  978. case 't':
  979. case 'tab':
  980. base.addKey('tab', action);
  981. break;
  982. default:
  983. if ($.keyboard.keyaction.hasOwnProperty(action)){
  984. // base.acceptedKeys.push(action);
  985. base.addKey(action, action)[isAction ? 'addClass' : 'removeClass'](o.css.buttonAction);
  986. }
  987. }
  988. } else {
  989. // regular button (not an action key)
  990. base.acceptedKeys.push(keys[key].split(':')[0]);
  991. base.addKey(keys[key], keys[key], true);
  992. }
  993. }
  994. newSet.find('.ui-keyboard-button:last').after('<br class="ui-keyboard-button-endrow">');
  995. }
  996. }
  997. });
  998. if (sets > 1) { base.sets = true; }
  999. base.hasMappedKeys = !( $.isEmptyObject(base.mappedKeys) ); // $.isEmptyObject() requires jQuery 1.4+
  1000. return container;
  1001. };
  1002. base.destroy = function() {
  1003. $(document).unbind('mousedown.keyboard keyup.keyboard touchstart.keyboard');
  1004. if (base.$keyboard) { base.$keyboard.remove(); }
  1005. var unb = $.trim(o.openOn + ' accepted beforeClose canceled change contextmenu hidden initialized keydown keypress keyup visible').split(' ').join('.keyboard ');
  1006. base.$el
  1007. .removeClass('ui-keyboard-input ui-keyboard-lockedinput ui-keyboard-placeholder ui-keyboard-notallowed ui-keyboard-always-open ' + o.css.input)
  1008. .removeAttr('aria-haspopup')
  1009. .removeAttr('role')
  1010. .unbind( unb + '.keyboard')
  1011. .removeData('keyboard');
  1012. };
  1013. // Run initializer
  1014. base.init();
  1015. };
  1016. // Action key function list
  1017. $.keyboard.keyaction = {
  1018. accept : function(base){
  1019. base.close(true); // same as base.accept();
  1020. return false; // return false prevents further processing
  1021. },
  1022. alt : function(base,el){
  1023. base.altActive = !base.altActive;
  1024. base.showKeySet(el);
  1025. },
  1026. bksp : function(base){
  1027. base.insertText('bksp'); // the script looks for the "bksp" string and initiates a backspace
  1028. },
  1029. cancel : function(base){
  1030. base.close();
  1031. return false; // return false prevents further processing
  1032. },
  1033. clear : function(base){
  1034. base.$preview.val('');
  1035. },
  1036. combo : function(base){
  1037. var c = !base.options.useCombos;
  1038. base.options.useCombos = c;
  1039. base.$keyboard.find('.ui-keyboard-combo')[(c) ? 'addClass' : 'removeClass'](base.options.css.buttonAction);
  1040. if (c) { base.checkCombos(); }
  1041. return false;
  1042. },
  1043. dec : function(base){
  1044. base.insertText((base.decimal) ? '.' : ',');
  1045. },
  1046. "default" : function(base,el){
  1047. base.shiftActive = base.altActive = base.metaActive = false;
  1048. base.showKeySet(el);
  1049. },
  1050. // el is the pressed key (button) object; it is null when the real keyboard enter is pressed
  1051. enter : function(base, el, e) {
  1052. var tag = base.el.tagName, o = base.options;
  1053. // shift-enter in textareas
  1054. if (e.shiftKey) {
  1055. // textarea & input - enterMod + shift + enter = accept, then go to prev; base.switchInput(goToNext, autoAccept)
  1056. // textarea & input - shift + enter = accept (no navigation)
  1057. return (o.enterNavigation) ? base.switchInput(!e[o.enterMod], true) : base.close(true);
  1058. }
  1059. // input only - enterMod + enter to navigate
  1060. if (o.enterNavigation && (tag !== 'TEXTAREA' || e[o.enterMod])) {
  1061. return base.switchInput(!e[o.enterMod], o.autoAccept ? 'true' : false);
  1062. }
  1063. // pressing virtual enter button inside of a textarea - add a carriage return
  1064. // e.target is span when clicking on text and button at other times
  1065. if (tag === 'TEXTAREA' && $(e.target).closest('button').length) {
  1066. base.insertText(' \n'); // IE8 fix (space + \n) - fixes #71 thanks Blookie!
  1067. }
  1068. },
  1069. // caps lock key
  1070. lock : function(base,el){
  1071. base.lastKeyset[0] = base.shiftActive = base.capsLock = !base.capsLock;
  1072. base.showKeySet(el);
  1073. },
  1074. meta : function(base,el){
  1075. base.metaActive = ($(el).hasClass(base.options.css.buttonAction)) ? false : true;
  1076. base.showKeySet(el);
  1077. },
  1078. next : function(base) {
  1079. base.switchInput(true, base.options.autoAccept);
  1080. return false;
  1081. },
  1082. prev : function(base) {
  1083. base.switchInput(false, base.options.autoAccept);
  1084. return false;
  1085. },
  1086. shift : function(base,el){
  1087. base.lastKeyset[0] = base.shiftActive = !base.shiftActive;
  1088. base.showKeySet(el);
  1089. },
  1090. sign : function(base){
  1091. if(/^\-?\d*\.?\d*$/.test( base.$preview.val() )) {
  1092. base.$preview.val( (base.$preview.val() * -1) );
  1093. }
  1094. },
  1095. space : function(base){
  1096. base.insertText(' ');
  1097. },
  1098. tab : function(base) {
  1099. var tag = base.el.tagName,
  1100. o = base.options;
  1101. if (tag === 'INPUT') {
  1102. if (o.tabNavigation) {
  1103. return base.switchInput(!base.shiftActive, true);
  1104. } else {
  1105. // ignore tab key in input
  1106. return false;
  1107. }
  1108. }
  1109. base.insertText('\t');
  1110. }
  1111. };
  1112. // Default keyboard layouts
  1113. $.keyboard.layouts = {
  1114. 'alpha' : {
  1115. 'default': [
  1116. '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
  1117. '{tab} a b c d e f g h i j [ ] \\',
  1118. 'k l m n o p q r s ; \' {enter}',
  1119. '{shift} t u v w x y z , . / {shift}',
  1120. '{accept} {space} {cancel}'
  1121. ],
  1122. 'shift': [
  1123. '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
  1124. '{tab} A B C D E F G H I J { } |',
  1125. 'K L M N O P Q R S : " {enter}',
  1126. '{shift} T U V W X Y Z < > ? {shift}',
  1127. '{accept} {space} {cancel}'
  1128. ]
  1129. },
  1130. 'qwerty' : {
  1131. 'default': [
  1132. '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
  1133. '{tab} q w e r t y u i o p [ ] \\',
  1134. 'a s d f g h j k l ; \' {enter}',
  1135. '{shift} z x c v b n m , . / {shift}',
  1136. '{accept} {space} {cancel}'
  1137. ],
  1138. 'shift': [
  1139. '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
  1140. '{tab} Q W E R T Y U I O P { } |',
  1141. 'A S D F G H J K L : " {enter}',
  1142. '{shift} Z X C V B N M < > ? {shift}',
  1143. '{accept} {space} {cancel}'
  1144. ]
  1145. },
  1146. 'international' : {
  1147. 'default': [
  1148. '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
  1149. '{tab} q w e r t y u i o p [ ] \\',
  1150. 'a s d f g h j k l ; \' {enter}',
  1151. '{shift} z x c v b n m , . / {shift}',
  1152. '{accept} {alt} {space} {alt} {cancel}'
  1153. ],
  1154. 'shift': [
  1155. '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
  1156. '{tab} Q W E R T Y U I O P { } |',
  1157. 'A S D F G H J K L : " {enter}',
  1158. '{shift} Z X C V B N M < > ? {shift}',
  1159. '{accept} {alt} {space} {alt} {cancel}'
  1160. ],
  1161. 'alt': [
  1162. '~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}',
  1163. '{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac',
  1164. '\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}',
  1165. '{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}',
  1166. '{accept} {alt} {space} {alt} {cancel}'
  1167. ],
  1168. 'alt-shift': [
  1169. '~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}',
  1170. '{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6',
  1171. '\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}',
  1172. '{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}',
  1173. '{accept} {alt} {space} {alt} {cancel}'
  1174. ]
  1175. },
  1176. 'dvorak' : {
  1177. 'default': [
  1178. '` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}',
  1179. '{tab} \' , . p y f g c r l / = \\',
  1180. 'a o e u i d h t n s - {enter}',
  1181. '{shift} ; q j k x b m w v z {shift}',
  1182. '{accept} {space} {cancel}'
  1183. ],
  1184. 'shift' : [
  1185. '~ ! @ # $ % ^ & * ( ) { } {bksp}',
  1186. '{tab} " < > P Y F G C R L ? + |',
  1187. 'A O E U I D H T N S _ {enter}',
  1188. '{shift} : Q J K X B M W V Z {shift}',
  1189. '{accept} {space} {cancel}'
  1190. ]
  1191. },
  1192. 'num' : {
  1193. 'default' : [
  1194. '= ( ) {b}',
  1195. '{clear} / * -',
  1196. '7 8 9 +',
  1197. '4 5 6 {sign}',
  1198. '1 2 3 %',
  1199. '0 . {a} {c}'
  1200. ]
  1201. }
  1202. };
  1203. $.keyboard.defaultOptions = {
  1204. // *** choose layout & positioning ***
  1205. layout : 'qwerty',
  1206. customLayout : null,
  1207. position : {
  1208. of : null, // optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
  1209. my : 'center top',
  1210. at : 'center top',
  1211. at2: 'center bottom' // used when "usePreview" is false (centers the keyboard at the bottom of the input/textarea)
  1212. },
  1213. // preview added above keyboard if true, original input/textarea used if false
  1214. usePreview : true,
  1215. // if true, the keyboard will always be visible
  1216. alwaysOpen : false,
  1217. // give the preview initial focus when the keyboard becomes visible
  1218. initialFocus : true,
  1219. // if true, keyboard will remain open even if the input loses focus, but closes on escape or when another keyboard opens.
  1220. stayOpen : false,
  1221. // *** change keyboard language & look ***
  1222. display : {
  1223. 'a' : '\u2714:Accept (Shift-Enter)', // check mark - same action as accept
  1224. 'accept' : 'Aceptar:Accept (Shift-Enter)',
  1225. 'alt' : 'Alt:\u2325 AltGr', // other alternatives \u2311
  1226. 'b' : '\u232b:Backspace', // Left arrow (same as &larr;)
  1227. 'bksp' : 'Bksp:Backspace',
  1228. 'c' : '\u2716:Cancel (Esc)', // big X, close - same action as cancel
  1229. 'cancel' : 'Anular:Cancel (Esc)',
  1230. 'clear' : 'C:Clear', // clear num pad
  1231. 'combo' : '\u00f6:Toggle Combo Keys',
  1232. 'dec' : '.:Decimal', // decimal point for num pad (optional), change '.' to ',' for European format
  1233. 'e' : '\u23ce:Enter', // down, then left arrow - enter symbol
  1234. 'empty' : '\u00a0',
  1235. 'enter' : 'Enter:Enter \u23ce',
  1236. 'lock' : 'Lock:\u21ea Caps Lock', // caps lock
  1237. 'next' : 'Next \u21e8',
  1238. 'prev' : '\u21e6 Prev',
  1239. 's' : '\u21e7:Shift', // thick hollow up arrow
  1240. 'shift' : 'Shift:Shift',
  1241. 'sign' : '\u00b1:Change Sign', // +/- sign for num pad
  1242. 'space' : '&nbsp;:Space',
  1243. 't' : '\u21e5:Tab', // right arrow to bar (used since this virtual keyboard works with one directional tabs)
  1244. 'tab' : '\u21e5 Tab:Tab' // \u21b9 is the true tab symbol (left & right arrows)
  1245. },
  1246. // Message added to the key title while hovering, if the mousewheel plugin exists
  1247. wheelMessage : 'Use mousewheel to see other keys',
  1248. css : {
  1249. input : 'ui-widget-content ui-corner-all', // input & preview
  1250. container : 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix', // keyboard container
  1251. buttonDefault : 'ui-state-default ui-corner-all', // default state
  1252. buttonHover : 'ui-state-hover', // hovered button
  1253. buttonAction : 'ui-state-active', // Action keys (e.g. Accept, Cancel, Tab, etc); this replaces "actionClass" option
  1254. buttonDisabled : 'ui-state-disabled' // used when disabling the decimal button {dec} when a decimal exists in the input area
  1255. },
  1256. // *** Useability ***
  1257. // Auto-accept content when clicking outside the keyboard (popup will close)
  1258. autoAccept : false,
  1259. // Prevents direct input in the preview window when true
  1260. lockInput : false,
  1261. // Prevent keys not in the displayed keyboard from being typed in
  1262. restrictInput: false,
  1263. // Check input against validate function, if valid the accept button gets a class name of "ui-keyboard-valid-input"
  1264. // if invalid, the accept button gets a class name of "ui-keyboard-invalid-input"
  1265. acceptValid : false,
  1266. // if acceptValid is true & the validate function returns a false, this option will cancel a keyboard
  1267. // close only after the accept button is pressed
  1268. cancelClose : true,
  1269. // tab to go to next, shift-tab for previous (default behavior)
  1270. tabNavigation: false,
  1271. // enter for next input; shift-enter accepts content & goes to next
  1272. // shift + "enterMod" + enter ("enterMod" is the alt as set below) will accept content and go to previous in a textarea
  1273. enterNavigation : false,
  1274. // mod key options: 'ctrlKey', 'shiftKey', 'altKey', 'metaKey' (MAC only)
  1275. enterMod : 'altKey', // alt-enter to go to previous; shift-alt-enter to accept & go to previous
  1276. // if true, the next button will stop on the last keyboard input/textarea; prev button stops at first
  1277. // if false, the next button will wrap to target the first input/textarea; prev will go to the last
  1278. stopAtEnd : true,
  1279. // Set this to append the keyboard immediately after the input/textarea it is attached to. This option
  1280. // works best when the input container doesn't have a set width and when the "tabNavigation" option is true
  1281. appendLocally: false,
  1282. // If false, the shift key will remain active until the next key is (mouse) clicked on; if true it will stay active until pressed again
  1283. stickyShift : true,
  1284. // Prevent pasting content into the area
  1285. preventPaste : false,
  1286. // caret places at the end of any text
  1287. caretToEnd : false,
  1288. // Set the max number of characters allowed in the input, setting it to false disables this option
  1289. maxLength : false,
  1290. // Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will start repeating
  1291. repeatDelay : 500,
  1292. // Mouse repeat rate - after the repeatDelay, this is the rate (characters per second) at which the key is repeated
  1293. // Added to simulate holding down a real keyboard key and having it repeat. I haven't calculated the upper limit of
  1294. // this rate, but it is limited to how fast the javascript can process the keys. And for me, in Firefox, it's around 20.
  1295. repeatRate : 20,
  1296. // resets the keyboard to the default keyset when visible
  1297. resetDefault : false,
  1298. // Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
  1299. openOn : 'focus',
  1300. // Event (namepaced) for when the character is added to the input (clicking on the keyboard)
  1301. keyBinding : 'mousedown touchstart',
  1302. // combos (emulate dead keys : http://en.wikipedia.org/wiki/Keyboard_layout#US-International)
  1303. // if user inputs `a the script converts it to à, ^o becomes ô, etc.
  1304. useCombos : true,
  1305. combos : {
  1306. '`' : { a:"\u00e0", A:"\u00c0", e:"\u00e8", E:"\u00c8", i:"\u00ec", I:"\u00cc", o:"\u00f2", O:"\u00d2", u:"\u00f9", U:"\u00d9", y:"\u1ef3", Y:"\u1ef2" }, // grave
  1307. "'" : { a:"\u00e1", A:"\u00c1", e:"\u00e9", E:"\u00c9", i:"\u00ed", I:"\u00cd", o:"\u00f3", O:"\u00d3", u:"\u00fa", U:"\u00da", y:"\u00fd", Y:"\u00dd" }, // acute & cedilla
  1308. '"' : { a:"\u00e4", A:"\u00c4", e:"\u00eb", E:"\u00cb", i:"\u00ef", I:"\u00cf", o:"\u00f6", O:"\u00d6", u:"\u00fc", U:"\u00dc", y:"\u00ff", Y:"\u0178" }, // umlaut/trema
  1309. '^' : { a:"\u00e2", A:"\u00c2", e:"\u00ea", E:"\u00ca", i:"\u00ee", I:"\u00ce", o:"\u00f4", O:"\u00d4", u:"\u00fb", U:"\u00db", y:"\u0177", Y:"\u0176" }, // circumflex
  1310. '~' : { a:"\u00e3", A:"\u00c3", e:"\u1ebd", E:"\u1ebc", i:"\u0129", I:"\u0128", o:"\u00f5", O:"\u00d5", u:"\u0169", U:"\u0168", y:"\u1ef9", Y:"\u1ef8", n:"\u00f1", N:"\u00d1" } // tilde
  1311. },
  1312. /*
  1313. // *** Methods ***
  1314. // commenting these out to reduce the size of the minified version
  1315. // Callbacks - attach a function to any of these callbacks as desired
  1316. initialized : function(e, keyboard, el) {},
  1317. visible : function(e, keyboard, el) {},
  1318. change : function(e, keyboard, el) {},
  1319. beforeClose : function(e, keyboard, el, accepted) {},
  1320. accepted : function(e, keyboard, el) {},
  1321. canceled : function(e, keyboard, el) {},
  1322. hidden : function(e, keyboard, el) {},
  1323. switchInput : null, // called instead of base.switchInput
  1324. */
  1325. // this callback is called just before the "beforeClose" to check the value
  1326. // if the value is valid, return true and the keyboard will continue as it should (close if not always open, etc)
  1327. // if the value is not value, return false and the clear the keyboard value ( like this "keyboard.$preview.val('');" ), if desired
  1328. // The validate function is called after each input, the "isClosing" value will be false; when the accept button is clicked, "isClosing" is true
  1329. validate : function(keyboard, value, isClosing) { return true; }
  1330. };
  1331. // for checking combos
  1332. $.keyboard.comboRegex = /([`\'~\^\"ao])([a-z])/mig;
  1333. // store current keyboard element; used by base.isCurrent()
  1334. $.keyboard.currentKeyboard = '';
  1335. $.fn.keyboard = function(options){
  1336. return this.each(function(){
  1337. if (!$(this).data('keyboard')) {
  1338. (new $.keyboard(this, options));
  1339. }
  1340. });
  1341. };
  1342. $.fn.getkeyboard = function(){
  1343. return this.data("keyboard");
  1344. };
  1345. })(jQuery);
  1346. /* Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
  1347. * Licensed under the MIT License:
  1348. * http://www.opensource.org/licenses/mit-license.php
  1349. * Highly modified from the original
  1350. */
  1351. (function($, len, createRange, duplicate){
  1352. "use strict";
  1353. $.fn.caret = function(options,opt2) {
  1354. if ( typeof this[0] === 'undefined' || this.is(':hidden') || this.css('visibility') === 'hidden' ) { return this; }
  1355. var n, s, start, e, end, selRange, range, stored_range, te, val,
  1356. selection = document.selection, t = this[0], sTop = t.scrollTop,
  1357. ss = typeof t.selectionStart !== 'undefined';
  1358. if (typeof options === 'number' && typeof opt2 === 'number') {
  1359. start = options;
  1360. end = opt2;
  1361. }
  1362. if (typeof start !== 'undefined') {
  1363. if (ss){
  1364. t.selectionStart=start;
  1365. t.selectionEnd=end;
  1366. } else {
  1367. selRange = t.createTextRange();
  1368. selRange.collapse(true);
  1369. selRange.moveStart('character', start);
  1370. selRange.moveEnd('character', end-start);
  1371. selRange.select();
  1372. }
  1373. // must be visible or IE8 crashes; IE9 in compatibility mode works fine - issue #56
  1374. if (this.is(':visible') || this.css('visibility') !== 'hidden') { this.focus(); }
  1375. t.scrollTop = sTop;
  1376. return this;
  1377. } else {
  1378. if (ss) {
  1379. s = t.selectionStart;
  1380. e = t.selectionEnd;
  1381. } else {
  1382. if (t.tagName === 'TEXTAREA') {
  1383. val = this.val();
  1384. range = selection[createRange]();
  1385. stored_range = range[duplicate]();
  1386. stored_range.moveToElementText(t);
  1387. stored_range.setEndPoint('EndToEnd', range);
  1388. // thanks to the awesome comments in the rangy plugin
  1389. s = stored_range.text.replace(/\r/g, '\n')[len];
  1390. e = s + range.text.replace(/\r/g, '\n')[len];
  1391. } else {
  1392. val = this.val().replace(/\r/g, '\n');
  1393. range = selection[createRange]()[duplicate]();
  1394. range.moveEnd('character', val[len]);
  1395. s = (range.text === '' ? val[len] : val.lastIndexOf(range.text));
  1396. range = selection[createRange]()[duplicate]();
  1397. range.moveStart('character', -val[len]);
  1398. e = range.text[len];
  1399. }
  1400. }
  1401. te = t.value.substring(s,e);
  1402. return { start : s, end : e, text : te, replace : function(st){
  1403. return t.value.substring(0,s) + st + t.value.substring(e, t.value[len]);
  1404. }};
  1405. }
  1406. };
  1407. })(jQuery, 'length', 'createRange', 'duplicate');