jquery.keyboard.js 110 KB

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