123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561 |
- /*! jQuery UI Virtual Keyboard v1.28.7 *//*
- Author: Jeremy Satterfield
- Maintained: Rob Garrison (Mottie on github)
- Licensed under the MIT License
- An on-screen virtual keyboard embedded within the browser window which
- will popup when a specified entry field is focused. The user can then
- type and preview their input before Accepting or Canceling.
- This plugin adds default class names to match jQuery UI theme styling.
- Bootstrap & custom themes may also be applied - See
- https://github.com/Mottie/Keyboard#themes
- Requires:
- jQuery v1.4.3+
- Caret plugin (included)
- Optional:
- jQuery UI (position utility only) & CSS theme
- jQuery mousewheel
- Setup/Usage:
- Please refer to https://github.com/Mottie/Keyboard/wiki
- -----------------------------------------
- Caret code modified from jquery.caret.1.02.js
- Licensed under the MIT License:
- http://www.opensource.org/licenses/mit-license.php
- -----------------------------------------
- */
- /*jshint browser:true, jquery:true, unused:false */
- /*global require:false, define:false, module:false */
- ;(function (factory) {
- if (typeof define === 'function' && define.amd) {
- define(['jquery'], factory);
- } else if (typeof module === 'object' && typeof module.exports === 'object') {
- module.exports = factory(require('jquery'));
- } else {
- factory(jQuery);
- }
- }(function ($) {
- 'use strict';
- var $keyboard = $.keyboard = function (el, options) {
- var o, base = this;
- base.version = '1.28.7';
- // Access to jQuery and DOM versions of element
- base.$el = $(el);
- base.el = el;
- // Add a reverse reference to the DOM object
- base.$el.data('keyboard', base);
- base.init = function () {
- base.initialized = false;
- var k, position, tmp,
- kbcss = $keyboard.css,
- kbevents = $keyboard.events;
- base.settings = options || {};
- // shallow copy position to prevent performance issues; see #357
- if (options && options.position) {
- position = $.extend({}, options.position);
- options.position = null;
- }
- base.options = o = $.extend(true, {}, $keyboard.defaultOptions, options);
- if (position) {
- o.position = position;
- options.position = position;
- }
- // keyboard is active (not destroyed);
- base.el.active = true;
- // unique keyboard namespace
- base.namespace = '.keyboard' + Math.random().toString(16).slice(2);
- // extension namespaces added here (to unbind listeners on base.$el upon destroy)
- base.extensionNamespace = [];
- // Shift and Alt key toggles, sets is true if a layout has more than one keyset
- // used for mousewheel message
- base.shiftActive = base.altActive = base.metaActive = base.sets = base.capsLock = false;
- // Class names of the basic key set - meta keysets are handled by the keyname
- base.rows = ['', '-shift', '-alt', '-alt-shift'];
- base.inPlaceholder = base.$el.attr('placeholder') || '';
- // html 5 placeholder/watermark
- base.watermark = $keyboard.watermark && base.inPlaceholder !== '';
- // convert mouse repeater rate (characters per second) into a time in milliseconds.
- base.repeatTime = 1000 / (o.repeatRate || 20);
- // delay in ms to prevent mousedown & touchstart from both firing events at the same time
- o.preventDoubleEventTime = o.preventDoubleEventTime || 100;
- // flag indication that a keyboard is open
- base.isOpen = false;
- // is mousewheel plugin loaded?
- base.wheel = $.isFunction($.fn.mousewheel);
- // special character in regex that need to be escaped
- base.escapeRegex = /[-\/\\^$*+?.()|[\]{}]/g;
- // detect contenteditable
- base.isContentEditable = !/(input|textarea)/i.test(base.el.nodeName) &&
- base.el.isContentEditable;
- // keyCode of keys always allowed to be typed
- k = $keyboard.keyCodes;
- // base.alwaysAllowed = [20,33,34,35,36,37,38,39,40,45,46];
- base.alwaysAllowed = [
- k.capsLock,
- k.pageUp,
- k.pageDown,
- k.end,
- k.home,
- k.left,
- k.up,
- k.right,
- k.down,
- k.insert,
- k.delete
- ];
- base.$keyboard = [];
- // keyboard enabled; set to false on destroy
- base.enabled = true;
- base.checkCaret = (o.lockInput || $keyboard.checkCaretSupport());
- // disable problematic usePreview for contenteditable
- if (base.isContentEditable) {
- o.usePreview = false;
- }
- base.last = {
- start: 0,
- end: 0,
- key: '',
- val: '',
- preVal: '',
- layout: '',
- virtual: true,
- keyset: [false, false, false], // [shift, alt, meta]
- wheel_$Keys: [],
- wheelIndex: 0,
- wheelLayers: []
- };
- // used when building the keyboard - [keyset element, row, index]
- base.temp = ['', 0, 0];
- // Callbacks
- $.each([
- kbevents.kbInit,
- kbevents.kbBeforeVisible,
- kbevents.kbVisible,
- kbevents.kbHidden,
- kbevents.inputCanceled,
- kbevents.inputAccepted,
- kbevents.kbBeforeClose,
- kbevents.inputRestricted
- ], function (i, callback) {
- if ($.isFunction(o[callback])) {
- // bind callback functions within options to triggered events
- base.$el.bind(callback + base.namespace + 'callbacks', o[callback]);
- }
- });
- // Close with esc key & clicking outside
- if (o.alwaysOpen) {
- o.stayOpen = true;
- }
- tmp = $(document);
- if (base.el.ownerDocument !== document) {
- tmp = tmp.add(base.el.ownerDocument);
- }
- var bindings = 'keyup checkkeyboard mousedown touchstart ';
- if (o.closeByClickEvent) {
- bindings += 'click ';
- }
- // debounce bindings... see #542
- tmp.bind(bindings.split(' ').join(base.namespace + ' '), function(e) {
- clearTimeout(base.timer3);
- base.timer3 = setTimeout(function() {
- base.checkClose(e);
- }, 1);
- });
- // Display keyboard on focus
- base.$el
- .addClass(kbcss.input + ' ' + o.css.input)
- .attr({
- 'aria-haspopup': 'true',
- 'role': 'textbox'
- });
- // set lockInput if the element is readonly; or make the element readonly if lockInput is set
- if (o.lockInput || base.el.readOnly) {
- o.lockInput = true;
- base.$el
- .addClass(kbcss.locked)
- .attr({
- 'readonly': 'readonly'
- });
- }
- // add disabled/readonly class - dynamically updated on reveal
- if (base.isUnavailable()) {
- base.$el.addClass(kbcss.noKeyboard);
- }
- if (o.openOn) {
- base.bindFocus();
- }
- // Add placeholder if not supported by the browser
- if (
- !base.watermark &&
- base.getValue(base.$el) === '' &&
- base.inPlaceholder !== '' &&
- base.$el.attr('placeholder') !== ''
- ) {
- // css watermark style (darker text)
- base.$el.addClass(kbcss.placeholder);
- base.setValue(base.inPlaceholder, base.$el);
- }
- base.$el.trigger(kbevents.kbInit, [base, base.el]);
- // initialized with keyboard open
- if (o.alwaysOpen) {
- base.reveal();
- }
- base.initialized = true;
- };
- base.toggle = function () {
- if (!base.hasKeyboard()) { return; }
- var $toggle = base.$keyboard.find('.' + $keyboard.css.keyToggle),
- locked = !base.enabled;
- // prevent physical keyboard from working
- base.preview.readonly = locked || base.options.lockInput;
- // disable all buttons
- base.$keyboard
- .toggleClass($keyboard.css.keyDisabled, locked)
- .find('.' + $keyboard.css.keyButton)
- .not($toggle)
- .attr('aria-disabled', locked)
- .each(function() {
- this.disabled = locked;
- });
- $toggle.toggleClass($keyboard.css.keyDisabled, locked);
- // stop auto typing
- if (locked && base.typing_options) {
- base.typing_options.text = '';
- }
- // allow chaining
- return base;
- };
- base.setCurrent = function () {
- var kbcss = $keyboard.css,
- // close any "isCurrent" keyboard (just in case they are always open)
- $current = $('.' + kbcss.isCurrent),
- kb = $current.data('keyboard');
- // close keyboard, if not self
- if (!$.isEmptyObject(kb) && kb.el !== base.el) {
- kb.close(kb.options.autoAccept ? 'true' : false);
- }
- $current.removeClass(kbcss.isCurrent);
- // ui-keyboard-has-focus is applied in case multiple keyboards have
- // alwaysOpen = true and are stacked
- $('.' + kbcss.hasFocus).removeClass(kbcss.hasFocus);
- base.$el.addClass(kbcss.isCurrent);
- base.$keyboard.addClass(kbcss.hasFocus);
- base.isCurrent(true);
- base.isOpen = true;
- };
- base.isUnavailable = function() {
- return (
- base.$el.is(':disabled') || (
- !base.options.activeOnReadonly &&
- base.$el.attr('readonly') &&
- !base.$el.hasClass($keyboard.css.locked)
- )
- );
- };
- base.isCurrent = function (set) {
- var cur = $keyboard.currentKeyboard || false;
- if (set) {
- cur = $keyboard.currentKeyboard = base.el;
- } else if (set === false && cur === base.el) {
- cur = $keyboard.currentKeyboard = '';
- }
- return cur === base.el;
- };
- base.hasKeyboard = function () {
- return base.$keyboard && base.$keyboard.length > 0;
- };
- base.isVisible = function () {
- return base.hasKeyboard() ? base.$keyboard.is(':visible') : false;
- };
- base.setFocus = function () {
- var $el = base.$preview || base.$el;
- if (!o.noFocus) {
- $el.focus();
- }
- if (base.isContentEditable) {
- $keyboard.setEditableCaret($el, base.last.start, base.last.end);
- } else {
- $keyboard.caret($el, base.last);
- }
- };
- base.focusOn = function () {
- if (!base && base.el.active) {
- // keyboard was destroyed
- return;
- }
- if (!base.isVisible()) {
- clearTimeout(base.timer);
- base.reveal();
- } else {
- // keyboard already open, make it the current keyboard
- base.setCurrent();
- }
- };
- // add redraw method to make API more clear
- base.redraw = function (layout) {
- if (layout) {
- // allow updating the layout by calling redraw
- base.options.layout = layout;
- }
- // update keyboard after a layout change
- if (base.$keyboard.length) {
- base.last.preVal = '' + base.last.val;
- base.saveLastChange();
- base.setValue(base.last.val, base.$el);
- base.removeKeyboard();
- base.shiftActive = base.altActive = base.metaActive = false;
- }
- base.isOpen = o.alwaysOpen;
- base.reveal(true);
- return base;
- };
- base.reveal = function (redraw) {
- var temp,
- alreadyOpen = base.isOpen,
- kbcss = $keyboard.css;
- base.opening = !alreadyOpen;
- // remove all 'extra' keyboards by calling close function
- $('.' + kbcss.keyboard).not('.' + kbcss.alwaysOpen).each(function(){
- var kb = $(this).data('keyboard');
- if (!$.isEmptyObject(kb)) {
- // this closes previous keyboard when clicking another input - see #515
- kb.close(kb.options.autoAccept ? 'true' : false);
- }
- });
- // Don't open if disabled
- if (base.isUnavailable()) {
- return;
- }
- base.$el.removeClass(kbcss.noKeyboard);
- // Unbind focus to prevent recursion - openOn may be empty if keyboard is opened externally
- if (o.openOn) {
- base.$el.unbind($.trim((o.openOn + ' ').split(/\s+/).join(base.namespace + ' ')));
- }
- // build keyboard if it doesn't exist; or attach keyboard if it was removed, but not cleared
- if (!base.$keyboard || base.$keyboard &&
- (!base.$keyboard.length || $.contains(base.el.ownerDocument.body, base.$keyboard[0]))) {
- base.startup();
- }
- // clear watermark
- if (!base.watermark && base.getValue() === base.inPlaceholder) {
- base.$el.removeClass(kbcss.placeholder);
- base.setValue('', base.$el);
- }
- // save starting content, in case we cancel
- base.originalContent = base.isContentEditable ?
- base.$el.html() :
- base.getValue(base.$el);
- if (base.el !== base.preview && !base.isContentEditable) {
- base.setValue(base.originalContent);
- }
- // disable/enable accept button
- if (o.acceptValid && o.checkValidOnInit) {
- base.checkValid();
- }
- if (o.resetDefault) {
- base.shiftActive = base.altActive = base.metaActive = false;
- }
- base.showSet();
- // beforeVisible event
- if (!base.isVisible()) {
- base.$el.trigger($keyboard.events.kbBeforeVisible, [base, base.el]);
- }
- if (
- base.initialized ||
- o.initialFocus ||
- ( !o.initialFocus && base.$el.hasClass($keyboard.css.initialFocus) )
- ) {
- base.setCurrent();
- }
- // update keyboard - enabled or disabled?
- base.toggle();
- // show keyboard
- base.$keyboard.show();
- // adjust keyboard preview window width - save width so IE won't keep expanding (fix issue #6)
- if (o.usePreview && $keyboard.msie) {
- if (typeof base.width === 'undefined') {
- base.$preview.hide(); // preview is 100% browser width in IE7, so hide the damn thing
- base.width = Math.ceil(base.$keyboard.width()); // set input width to match the widest keyboard row
- base.$preview.show();
- }
- base.$preview.width(base.width);
- }
- base.reposition();
- base.checkDecimal();
- // get preview area line height
- // add roughly 4px to get line height from font height, works well for font-sizes from 14-36px
- // needed for textareas
- base.lineHeight = parseInt(base.$preview.css('lineHeight'), 10) ||
- parseInt(base.$preview.css('font-size'), 10) + 4;
- if (o.caretToEnd) {
- temp = base.isContentEditable ? $keyboard.getEditableLength(base.el) : base.originalContent.length;
- base.saveCaret(temp, temp);
- }
- // IE caret haxx0rs
- if ($keyboard.allie) {
- // sometimes end = 0 while start is > 0
- if (base.last.end === 0 && base.last.start > 0) {
- base.last.end = base.last.start;
- }
- // IE will have start -1, end of 0 when not focused (see demo: https://jsfiddle.net/Mottie/fgryQ/3/)
- if (base.last.start < 0) {
- // ensure caret is at the end of the text (needed for IE)
- base.last.start = base.last.end = base.originalContent.length;
- }
- }
- if (alreadyOpen || redraw) {
- // restore caret position (userClosed)
- $keyboard.caret(base.$preview, base.last);
- base.opening = false;
- return base;
- }
- // opening keyboard flag; delay allows switching between keyboards without immediately closing
- // the keyboard
- base.timer2 = setTimeout(function () {
- var undef;
- base.opening = false;
- // Number inputs don't support selectionStart and selectionEnd
- // Number/email inputs don't support selectionStart and selectionEnd
- if (!/(number|email)/i.test(base.el.type) && !o.caretToEnd) {
- // caret position is always 0,0 in webkit; and nothing is focused at this point... odd
- // save caret position in the input to transfer it to the preview
- // inside delay to get correct caret position
- base.saveCaret(undef, undef, base.$el);
- }
- if (o.initialFocus || base.$el.hasClass($keyboard.css.initialFocus)) {
- $keyboard.caret(base.$preview, base.last);
- }
- // save event time for keyboards with stayOpen: true
- base.last.eventTime = new Date().getTime();
- base.$el.trigger($keyboard.events.kbVisible, [base, base.el]);
- base.timer = setTimeout(function () {
- // get updated caret information after visible event - fixes #331
- if (base) { // Check if base exists, this is a case when destroy is called, before timers fire
- base.saveCaret();
- }
- }, 200);
- }, 10);
- // return base to allow chaining in typing extension
- return base;
- };
- base.updateLanguage = function () {
- // change language if layout is named something like 'french-azerty-1'
- var layouts = $keyboard.layouts,
- lang = o.language || layouts[o.layout] && layouts[o.layout].lang &&
- layouts[o.layout].lang || [o.language || 'en'],
- kblang = $keyboard.language;
- // some languages include a dash, e.g. 'en-gb' or 'fr-ca'
- // allow o.language to be a string or array...
- // array is for future expansion where a layout can be set for multiple languages
- lang = ($.isArray(lang) ? lang[0] : lang);
- base.language = lang;
- lang = lang.split('-')[0];
- // set keyboard language
- o.display = $.extend(true, {},
- kblang.en.display,
- kblang[lang] && kblang[lang].display || {},
- base.settings.display
- );
- o.combos = $.extend(true, {},
- kblang.en.combos,
- kblang[lang] && kblang[lang].combos || {},
- base.settings.combos
- );
- o.wheelMessage = kblang[lang] && kblang[lang].wheelMessage || kblang.en.wheelMessage;
- // rtl can be in the layout or in the language definition; defaults to false
- o.rtl = layouts[o.layout] && layouts[o.layout].rtl || kblang[lang] && kblang[lang].rtl || false;
- // save default regex (in case loading another layout changes it)
- base.regex = kblang[lang] && kblang[lang].comboRegex || $keyboard.comboRegex;
- // determine if US '.' or European ',' system being used
- base.decimal = /^\./.test(o.display.dec);
- base.$el
- .toggleClass('rtl', o.rtl)
- .css('direction', o.rtl ? 'rtl' : '');
- };
- base.startup = function () {
- var kbcss = $keyboard.css;
- // ensure base.$preview is defined; but don't overwrite it if keyboard is always visible
- if (!((o.alwaysOpen || o.userClosed) && base.$preview)) {
- base.makePreview();
- }
- if (!base.hasKeyboard()) {
- // custom layout - create a unique layout name based on the hash
- if (o.layout === 'custom') {
- o.layoutHash = 'custom' + base.customHash();
- }
- base.layout = o.layout === 'custom' ? o.layoutHash : o.layout;
- base.last.layout = base.layout;
- base.updateLanguage();
- if (typeof $keyboard.builtLayouts[base.layout] === 'undefined') {
- if ($.isFunction(o.create)) {
- // create must call buildKeyboard() function; or create it's own keyboard
- base.$keyboard = o.create(base);
- } else if (!base.$keyboard.length) {
- base.buildKeyboard(base.layout, true);
- }
- }
- base.$keyboard = $keyboard.builtLayouts[base.layout].$keyboard.clone();
- base.$keyboard.data('keyboard', base);
- if ((base.el.id || '') !== '') {
- // add ID to keyboard for styling purposes
- base.$keyboard.attr('id', base.el.id + $keyboard.css.idSuffix);
- }
- base.makePreview();
- }
- // Add layout and laguage data-attibutes
- base.$keyboard
- .attr('data-' + kbcss.keyboard + '-layout', o.layout)
- .attr('data-' + kbcss.keyboard + '-language', base.language);
- base.$decBtn = base.$keyboard.find('.' + kbcss.keyPrefix + 'dec');
- // add enter to allowed keys; fixes #190
- if (o.enterNavigation || base.el.nodeName === 'TEXTAREA') {
- base.alwaysAllowed.push($keyboard.keyCodes.enter);
- }
- base.bindKeyboard();
- base.$keyboard.appendTo(o.appendLocally ? base.$el.parent() : o.appendTo || 'body');
- base.bindKeys();
- // reposition keyboard on window resize
- if (o.reposition && $.ui && $.ui.position && o.appendTo === 'body') {
- $(window).bind('resize' + base.namespace, function () {
- base.reposition();
- });
- }
- };
- base.reposition = function () {
- base.position = $.isEmptyObject(o.position) ? false : o.position;
- // position after keyboard is visible (required for UI position utility)
- // and appropriately sized
- if ($.ui && $.ui.position && base.position) {
- base.position.of =
- // get single target position
- base.position.of ||
- // OR target stored in element data (multiple targets)
- base.$el.data('keyboardPosition') ||
- // OR default @ element
- base.$el;
- base.position.collision = base.position.collision || 'flipfit flipfit';
- base.position.at = o.usePreview ? o.position.at : o.position.at2;
- if (base.isVisible()) {
- base.$keyboard.position(base.position);
- }
- }
- // make chainable
- return base;
- };
- base.makePreview = function () {
- if (o.usePreview) {
- var indx, attrs, attr, removedAttr,
- kbcss = $keyboard.css;
- base.$preview = base.$el.clone(false)
- .data('keyboard', base)
- .removeClass(kbcss.placeholder + ' ' + kbcss.input)
- .addClass(kbcss.preview + ' ' + o.css.input)
- .attr('tabindex', '-1')
- .show(); // for hidden inputs
- base.preview = base.$preview[0];
- // Switch the number input field to text so the caret positioning will work again
- if (base.preview.type === 'number') {
- base.preview.type = 'text';
- }
- // remove extraneous attributes.
- removedAttr = /^(data-|id|aria-haspopup)/i;
- attrs = base.$preview.get(0).attributes;
- for (indx = attrs.length - 1; indx >= 0; indx--) {
- attr = attrs[indx] && attrs[indx].name;
- if (removedAttr.test(attr)) {
- // remove data-attributes - see #351
- base.preview.removeAttribute(attr);
- }
- }
- // build preview container and append preview display
- $('<div />')
- .addClass(kbcss.wrapper)
- .append(base.$preview)
- .prependTo(base.$keyboard);
- } else {
- base.$preview = base.$el;
- base.preview = base.el;
- }
- };
- // Added in v1.26.8 to allow chaining of the caret function, e.g.
- // keyboard.reveal().caret(4,5).insertText('test').caret('end');
- base.caret = function(param1, param2) {
- var result = $keyboard.caret(base.$preview, param1, param2),
- wasSetCaret = result instanceof $;
- // Caret was set, save last position & make chainable
- if (wasSetCaret) {
- base.saveCaret(result.start, result.end);
- return base;
- }
- // return caret position if using .caret()
- return result;
- };
- base.saveCaret = function (start, end, $el) {
- if (base.isCurrent()) {
- var p;
- if (typeof start === 'undefined') {
- // grab & save current caret position
- p = $keyboard.caret($el || base.$preview);
- } else {
- p = $keyboard.caret($el || base.$preview, start, end);
- }
- base.last.start = typeof start === 'undefined' ? p.start : start;
- base.last.end = typeof end === 'undefined' ? p.end : end;
- }
- };
- base.saveLastChange = function (val) {
- base.last.val = val || base.getValue(base.$preview || base.$el);
- if (base.isContentEditable) {
- base.last.elms = base.el.cloneNode(true);
- }
- };
- base.setScroll = function () {
- // Set scroll so caret & current text is in view
- // needed for virtual keyboard typing, NOT manual typing - fixes #23
- if (!base.isContentEditable && base.last.virtual) {
- var scrollWidth, clientWidth, adjustment, direction,
- isTextarea = base.preview.nodeName === 'TEXTAREA',
- value = base.last.val.substring(0, Math.max(base.last.start, base.last.end));
- if (!base.$previewCopy) {
- // clone preview
- base.$previewCopy = base.$preview.clone()
- .removeAttr('id') // fixes #334
- .css({
- position: 'absolute',
- left: 0,
- zIndex: -10,
- visibility: 'hidden'
- })
- .addClass($keyboard.css.inputClone);
- // prevent submitting content on form submission
- base.$previewCopy[0].disabled = true;
- if (!isTextarea) {
- // make input zero-width because we need an accurate scrollWidth
- base.$previewCopy.css({
- 'white-space': 'pre',
- 'width': 0
- });
- }
- if (o.usePreview) {
- // add clone inside of preview wrapper
- base.$preview.after(base.$previewCopy);
- } else {
- // just slap that thing in there somewhere
- base.$keyboard.prepend(base.$previewCopy);
- }
- }
- if (isTextarea) {
- // need the textarea scrollHeight, so set the clone textarea height to be the line height
- base.$previewCopy
- .height(base.lineHeight)
- .val(value);
- // set scrollTop for Textarea
- base.preview.scrollTop = base.lineHeight *
- (Math.floor(base.$previewCopy[0].scrollHeight / base.lineHeight) - 1);
- } else {
- // add non-breaking spaces
- base.$previewCopy.val(value.replace(/\s/g, '\xa0'));
- // if scrollAdjustment option is set to "c" or "center" then center the caret
- adjustment = /c/i.test(o.scrollAdjustment) ? base.preview.clientWidth / 2 : o.scrollAdjustment;
- scrollWidth = base.$previewCopy[0].scrollWidth - 1;
- // set initial state as moving right
- if (typeof base.last.scrollWidth === 'undefined') {
- base.last.scrollWidth = scrollWidth;
- base.last.direction = true;
- }
- // if direction = true; we're scrolling to the right
- direction = base.last.scrollWidth === scrollWidth ?
- base.last.direction :
- base.last.scrollWidth < scrollWidth;
- clientWidth = base.preview.clientWidth - adjustment;
- // set scrollLeft for inputs; try to mimic the inherit caret positioning + scrolling:
- // hug right while scrolling right...
- if (direction) {
- if (scrollWidth < clientWidth) {
- base.preview.scrollLeft = 0;
- } else {
- base.preview.scrollLeft = scrollWidth - clientWidth;
- }
- } else {
- // hug left while scrolling left...
- if (scrollWidth >= base.preview.scrollWidth - clientWidth) {
- base.preview.scrollLeft = base.preview.scrollWidth - adjustment;
- } else if (scrollWidth - adjustment > 0) {
- base.preview.scrollLeft = scrollWidth - adjustment;
- } else {
- base.preview.scrollLeft = 0;
- }
- }
- base.last.scrollWidth = scrollWidth;
- base.last.direction = direction;
- }
- }
- };
- base.bindFocus = function () {
- if (o.openOn) {
- // make sure keyboard isn't destroyed
- // Check if base exists, this is a case when destroy is called, before timers have fired
- if (base && base.el.active) {
- base.$el.bind(o.openOn + base.namespace, function () {
- base.focusOn();
- });
- // remove focus from element (needed for IE since blur doesn't seem to work)
- if ($(':focus')[0] === base.el) {
- base.$el.blur();
- }
- }
- }
- };
- base.bindKeyboard = function () {
- var evt,
- keyCodes = $keyboard.keyCodes,
- layout = $keyboard.builtLayouts[base.layout],
- namespace = base.namespace + 'keybindings';
- base.$preview
- .unbind(base.namespace)
- .bind('click' + namespace + ' touchstart' + namespace, function () {
- if (o.alwaysOpen && !base.isCurrent()) {
- base.reveal();
- }
- // update last caret position after user click, use at least 150ms or it doesn't work in IE
- base.timer2 = setTimeout(function () {
- if (base){
- base.saveCaret();
- }
- }, 150);
- })
- .bind('keypress' + namespace, function (e) {
- if (o.lockInput) {
- return false;
- }
- if (!base.isCurrent()) {
- return;
- }
- var k = e.charCode || e.which,
- // capsLock can only be checked while typing a-z
- k1 = k >= keyCodes.A && k <= keyCodes.Z,
- k2 = k >= keyCodes.a && k <= keyCodes.z,
- str = base.last.key = String.fromCharCode(k);
- // check, that keypress wasn't rise by functional key
- // space is first typing symbol in UTF8 table
- if (k < keyCodes.space) { //see #549
- return;
- }
- base.last.virtual = false;
- base.last.event = e;
- base.last.$key = []; // not a virtual keyboard key
- if (base.checkCaret) {
- base.saveCaret();
- }
- // update capsLock
- if (k !== keyCodes.capsLock && (k1 || k2)) {
- base.capsLock = (k1 && !e.shiftKey) || (k2 && e.shiftKey);
- // if shifted keyset not visible, then show it
- if (base.capsLock && !base.shiftActive) {
- base.shiftActive = true;
- base.showSet();
- }
- }
- // restrict input - keyCode in keypress special keys:
- // see http://www.asquare.net/javascript/tests/KeyCode.html
- if (o.restrictInput) {
- // allow navigation keys to work - Chrome doesn't fire a keypress event (8 = bksp)
- if ((e.which === keyCodes.backSpace || e.which === 0) &&
- $.inArray(e.keyCode, base.alwaysAllowed)) {
- return;
- }
- // quick key check
- if ($.inArray(str, layout.acceptedKeys) === -1) {
- e.preventDefault();
- // copy event object in case e.preventDefault() breaks when changing the type
- evt = $.extend({}, e);
- evt.type = $keyboard.events.inputRestricted;
- base.$el.trigger(evt, [base, base.el]);
- }
- } else if ((e.ctrlKey || e.metaKey) &&
- (e.which === keyCodes.A || e.which === keyCodes.C || e.which === keyCodes.V ||
- (e.which >= keyCodes.X && e.which <= keyCodes.Z))) {
- // Allow select all (ctrl-a), copy (ctrl-c), paste (ctrl-v) & cut (ctrl-x) &
- // redo (ctrl-y)& undo (ctrl-z); meta key for mac
- return;
- }
- // Mapped Keys - allows typing on a regular keyboard and the mapped key is entered
- // Set up a key in the layout as follows: 'm(a):label'; m = key to map, (a) = actual keyboard key
- // to map to (optional), ':label' = title/tooltip (optional)
- // example: \u0391 or \u0391(A) or \u0391:alpha or \u0391(A):alpha
- if (layout.hasMappedKeys && layout.mappedKeys.hasOwnProperty(str)) {
- base.last.key = layout.mappedKeys[str];
- base.insertText(base.last.key);
- e.preventDefault();
- }
- if (typeof o.beforeInsert === 'function') {
- base.insertText(base.last.key);
- e.preventDefault();
- }
- base.checkMaxLength();
- })
- .bind('keyup' + namespace, function (e) {
- if (!base.isCurrent()) { return; }
- base.last.virtual = false;
- switch (e.which) {
- // Insert tab key
- case keyCodes.tab:
- // Added a flag to prevent from tabbing into an input, keyboard opening, then adding the tab
- // to the keyboard preview area on keyup. Sadly it still happens if you don't release the tab
- // key immediately because keydown event auto-repeats
- if (base.tab && !o.lockInput) {
- base.shiftActive = e.shiftKey;
- // when switching inputs, the tab keyaction returns false
- var notSwitching = $keyboard.keyaction.tab(base);
- base.tab = false;
- if (!notSwitching) {
- return false;
- }
- } else {
- e.preventDefault();
- }
- break;
- // Escape will hide the keyboard
- case keyCodes.escape:
- if (!o.ignoreEsc) {
- base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false);
- }
- return false;
- }
- // throttle the check combo function because fast typers will have an incorrectly positioned caret
- clearTimeout(base.throttled);
- base.throttled = setTimeout(function () {
- // fix error in OSX? see issue #102
- if (base && base.isVisible()) {
- base.checkCombos();
- }
- }, 100);
- base.checkMaxLength();
- base.last.preVal = '' + base.last.val;
- base.saveLastChange();
- // don't alter "e" or the "keyup" event never finishes processing; fixes #552
- var event = $.Event( $keyboard.events.kbChange );
- // base.last.key may be empty string (shift, enter, tab, etc) when keyboard is first visible
- // use e.key instead, if browser supports it
- event.action = base.last.key;
- base.$el.trigger(event, [base, base.el]);
- // change callback is no longer bound to the input element as the callback could be
- // called during an external change event with all the necessary parameters (issue #157)
- if ($.isFunction(o.change)) {
- event.type = $keyboard.events.inputChange;
- o.change(event, base, base.el);
- return false;
- }
- if (o.acceptValid && o.autoAcceptOnValid) {
- if (
- $.isFunction(o.validate) &&
- o.validate(base, base.getValue(base.$preview))
- ) {
- base.$preview.blur();
- base.accept();
- }
- }
- })
- .bind('keydown' + namespace, function (e) {
- base.last.keyPress = e.which;
- // ensure alwaysOpen keyboards are made active
- if (o.alwaysOpen && !base.isCurrent()) {
- base.reveal();
- }
- // prevent tab key from leaving the preview window
- if (e.which === keyCodes.tab) {
- // allow tab to pass through - tab to next input/shift-tab for prev
- base.tab = true;
- return false;
- }
- if (o.lockInput || e.timeStamp === base.last.timeStamp) {
- return !o.lockInput;
- }
- base.last.timeStamp = e.timeStamp; // fixes #659
- base.last.virtual = false;
- switch (e.which) {
- case keyCodes.backSpace:
- $keyboard.keyaction.bksp(base, null, e);
- e.preventDefault();
- break;
- case keyCodes.enter:
- $keyboard.keyaction.enter(base, null, e);
- break;
- // Show capsLock
- case keyCodes.capsLock:
- base.shiftActive = base.capsLock = !base.capsLock;
- base.showSet();
- break;
- case keyCodes.V:
- // prevent ctrl-v/cmd-v
- if (e.ctrlKey || e.metaKey) {
- if (o.preventPaste) {
- e.preventDefault();
- return;
- }
- base.checkCombos(); // check pasted content
- }
- break;
- }
- })
- .bind('mouseup touchend '.split(' ').join(namespace + ' '), function () {
- base.last.virtual = true;
- base.saveCaret();
- });
- // prevent keyboard event bubbling
- base.$keyboard.bind('mousedown click touchstart '.split(' ').join(base.namespace + ' '), function (e) {
- e.stopPropagation();
- if (!base.isCurrent()) {
- base.reveal();
- $(base.el.ownerDocument).trigger('checkkeyboard' + base.namespace);
- }
- base.setFocus();
- });
- // If preventing paste, block context menu (right click)
- if (o.preventPaste) {
- base.$preview.bind('contextmenu' + base.namespace, function (e) {
- e.preventDefault();
- });
- base.$el.bind('contextmenu' + base.namespace, function (e) {
- e.preventDefault();
- });
- }
- };
- base.bindButton = function(events, handler) {
- var button = '.' + $keyboard.css.keyButton,
- callback = function(e) {
- e.stopPropagation();
- // save closest keyboard wrapper/input to check in checkClose function
- e.$target = $(this).closest('.' + $keyboard.css.keyboard + ', .' + $keyboard.css.input);
- handler.call(this, e);
- };
- if ($.fn.on) {
- // jQuery v1.7+
- base.$keyboard.on(events, button, callback);
- } else if ($.fn.delegate) {
- // jQuery v1.4.2 - 3.0.0
- base.$keyboard.delegate(button, events, callback);
- }
- return base;
- };
- base.unbindButton = function(namespace) {
- if ($.fn.off) {
- // jQuery v1.7+
- base.$keyboard.off(namespace);
- } else if ($.fn.undelegate) {
- // jQuery v1.4.2 - 3.0.0 (namespace only added in v1.6)
- base.$keyboard.undelegate('.' + $keyboard.css.keyButton, namespace);
- }
- return base;
- };
- base.bindKeys = function () {
- var kbcss = $keyboard.css;
- base
- .unbindButton(base.namespace + ' ' + base.namespace + 'kb')
- // Change hover class and tooltip - moved this touchstart before option.keyBinding touchstart
- // to prevent mousewheel lag/duplication - Fixes #379 & #411
- .bindButton('mouseenter mouseleave touchstart '.split(' ').join(base.namespace + ' '), function (e) {
- if ((o.alwaysOpen || o.userClosed) && e.type !== 'mouseleave' && !base.isCurrent()) {
- base.reveal();
- base.setFocus();
- }
- if (!base.isCurrent() || this.disabled) {
- return;
- }
- var $keys, txt,
- last = base.last,
- $this = $(this),
- type = e.type;
- if (o.useWheel && base.wheel) {
- $keys = base.getLayers($this);
- txt = ($keys.length ? $keys.map(function () {
- return $(this).attr('data-value') || '';
- })
- .get() : '') || [$this.text()];
- last.wheel_$Keys = $keys;
- last.wheelLayers = txt;
- last.wheelIndex = $.inArray($this.attr('data-value'), txt);
- }
- if ((type === 'mouseenter' || type === 'touchstart') && base.el.type !== 'password' &&
- !$this.hasClass(o.css.buttonDisabled)) {
- $this.addClass(o.css.buttonHover);
- if (o.useWheel && base.wheel) {
- $this.attr('title', function (i, t) {
- // show mouse wheel message
- return (base.wheel && t === '' && base.sets && txt.length > 1 && type !== 'touchstart') ?
- o.wheelMessage : t;
- });
- }
- }
- if (type === 'mouseleave') {
- // needed or IE flickers really bad
- $this.removeClass((base.el.type === 'password') ? '' : o.css.buttonHover);
- if (o.useWheel && base.wheel) {
- last.wheelIndex = 0;
- last.wheelLayers = [];
- last.wheel_$Keys = [];
- $this
- .attr('title', function (i, t) {
- return (t === o.wheelMessage) ? '' : t;
- })
- .html($this.attr('data-html')); // restore original button text
- }
- }
- })
- // keyBinding = 'mousedown touchstart' by default
- .bindButton(o.keyBinding.split(' ').join(base.namespace + ' ') + base.namespace + ' ' +
- $keyboard.events.kbRepeater, function (e) {
- e.preventDefault();
- // prevent errors when external triggers attempt to 'type' - see issue #158
- if (!base.$keyboard.is(':visible') || this.disabled) {
- return false;
- }
- var action,
- last = base.last,
- $key = $(this),
- // prevent mousedown & touchstart from both firing events at the same time - see #184
- timer = new Date().getTime();
- if (o.useWheel && base.wheel) {
- // get keys from other layers/keysets (shift, alt, meta, etc) that line up by data-position
- // target mousewheel selected key
- $key = last.wheel_$Keys.length && last.wheelIndex > -1 ? last.wheel_$Keys.eq(last.wheelIndex) : $key;
- }
- action = $key.attr('data-action');
- if (timer - (last.eventTime || 0) < o.preventDoubleEventTime) {
- return;
- }
- last.eventTime = timer;
- last.event = e;
- last.virtual = true;
- last.$key = $key;
- last.key = $key.attr('data-value');
- last.keyPress = '';
- // Start caret in IE when not focused (happens with each virtual keyboard button click
- base.setFocus();
- if (/^meta/.test(action)) {
- action = 'meta';
- }
- // keyaction is added as a string, override original action & text
- if (action === last.key && typeof $keyboard.keyaction[action] === 'string') {
- last.key = action = $keyboard.keyaction[action];
- } else if (action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[action])) {
- // stop processing if action returns false (close & cancel)
- if ($keyboard.keyaction[action](base, this, e) === false) {
- return false;
- }
- action = null; // prevent inserting action name
- }
- // stop processing if keyboard closed and keyaction did not return false - see #536
- if (!base.hasKeyboard()) {
- return false;
- }
- if (typeof action !== 'undefined' && action !== null) {
- last.key = $(this).hasClass(kbcss.keyAction) ? action : last.key;
- base.insertText(last.key);
- if (!base.capsLock && !o.stickyShift && !e.shiftKey) {
- base.shiftActive = false;
- base.showSet($key.attr('data-name'));
- }
- }
- // set caret if caret moved by action function; also, attempt to fix issue #131
- $keyboard.caret(base.$preview, last);
- base.checkCombos();
- e = $.extend({}, e, $.Event($keyboard.events.kbChange));
- e.target = base.el;
- e.action = last.key;
- base.$el.trigger(e, [base, base.el]);
- last.preVal = '' + last.val;
- base.saveLastChange();
- if ($.isFunction(o.change)) {
- e.type = $keyboard.events.inputChange;
- o.change(e, base, base.el);
- // return false to prevent reopening keyboard if base.accept() was called
- return false;
- }
- })
- // using 'kb' namespace for mouse repeat functionality to keep it separate
- // I need to trigger a 'repeater.keyboard' to make it work
- .bindButton('mouseup' + base.namespace + ' ' + 'mouseleave touchend touchmove touchcancel '.split(' ')
- .join(base.namespace + 'kb '), function (e) {
- base.last.virtual = true;
- var offset,
- $this = $(this);
- if (e.type === 'touchmove') {
- // if moving within the same key, don't stop repeating
- offset = $this.offset();
- offset.right = offset.left + $this.outerWidth();
- offset.bottom = offset.top + $this.outerHeight();
- if (e.originalEvent.touches[0].pageX >= offset.left &&
- e.originalEvent.touches[0].pageX < offset.right &&
- e.originalEvent.touches[0].pageY >= offset.top &&
- e.originalEvent.touches[0].pageY < offset.bottom) {
- return true;
- }
- } else if (/(mouseleave|touchend|touchcancel)/i.test(e.type)) {
- $this.removeClass(o.css.buttonHover); // needed for touch devices
- } else {
- if (!o.noFocus && base.isCurrent() && base.isVisible()) {
- base.$preview.focus();
- }
- if (base.checkCaret) {
- $keyboard.caret(base.$preview, base.last);
- }
- }
- base.mouseRepeat = [false, ''];
- clearTimeout(base.repeater); // make sure key repeat stops!
- if (o.acceptValid && o.autoAcceptOnValid) {
- if (
- $.isFunction(o.validate) &&
- o.validate(base, base.getValue())
- ) {
- base.$preview.blur();
- base.accept();
- }
- }
- return false;
- })
- // prevent form submits when keyboard is bound locally - issue #64
- .bindButton('click' + base.namespace, function () {
- return false;
- })
- // Allow mousewheel to scroll through other keysets of the same (non-action) key
- .bindButton('mousewheel' + base.namespace, base.throttleEvent(function (e, delta) {
- var $btn = $(this);
- // no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
- if (!$btn || $btn.hasClass(kbcss.keyAction) || base.last.wheel_$Keys[0] !== this) {
- return;
- }
- if (o.useWheel && base.wheel) {
- // deltaY used by newer versions of mousewheel plugin
- delta = delta || e.deltaY;
- var n,
- txt = base.last.wheelLayers || [];
- if (txt.length > 1) {
- n = base.last.wheelIndex + (delta > 0 ? -1 : 1);
- if (n > txt.length - 1) {
- n = 0;
- }
- if (n < 0) {
- n = txt.length - 1;
- }
- } else {
- n = 0;
- }
- base.last.wheelIndex = n;
- $btn.html(txt[n]);
- return false;
- }
- }, 30))
- .bindButton('mousedown touchstart '.split(' ').join(base.namespace + 'kb '), function () {
- var $btn = $(this);
- // no mouse repeat for action keys (shift, ctrl, alt, meta, etc)
- if (
- !$btn || (
- $btn.hasClass(kbcss.keyAction) &&
- // mouse repeated action key exceptions
- !$btn.is('.' + kbcss.keyPrefix + ('tab bksp space enter'.split(' ').join(',.' + kbcss.keyPrefix)))
- )
- ) {
- return;
- }
- if (o.repeatRate !== 0) {
- // save the key, make sure we are repeating the right one (fast typers)
- base.mouseRepeat = [true, $btn];
- setTimeout(function () {
- // don't repeat keys if it is disabled - see #431
- if (base && base.mouseRepeat[0] && base.mouseRepeat[1] === $btn && !$btn[0].disabled) {
- base.repeatKey($btn);
- }
- }, o.repeatDelay);
- }
- return false;
- });
- };
- // No call on tailing event
- base.throttleEvent = function(cb, time) {
- var interm;
- return function() {
- if (!interm) {
- cb.apply(this, arguments);
- interm = true;
- setTimeout(function() {
- interm = false;
- }, time);
- }
- };
- };
- base.execCommand = function(cmd, str) {
- base.el.ownerDocument.execCommand(cmd, false, str);
- base.el.normalize();
- if (o.reposition) {
- base.reposition();
- }
- };
- base.getValue = function ($el) {
- $el = $el || base.$preview;
- return $el[base.isContentEditable ? 'text' : 'val']();
- };
- base.setValue = function (txt, $el) {
- $el = $el || base.$preview;
- if (base.isContentEditable) {
- if (txt !== $el.text()) {
- $keyboard.replaceContent($el, txt);
- base.saveCaret();
- }
- } else {
- $el.val(txt);
- }
- return base;
- };
- // Insert text at caret/selection - thanks to Derek Wickwire for fixing this up!
- base.insertText = function (txt) {
- if (!base.$preview) { return base; }
- if (typeof o.beforeInsert === 'function') {
- txt = o.beforeInsert(base.last.event, base, base.el, txt);
- }
- if (typeof txt === 'undefined' || txt === false) {
- base.last.key = '';
- return base;
- }
- if (base.isContentEditable) {
- return base.insertContentEditable(txt);
- }
- var t,
- bksp = false,
- isBksp = txt === '\b',
- // use base.$preview.val() instead of base.preview.value (val.length includes carriage returns in IE).
- val = base.getValue(),
- pos = $keyboard.caret(base.$preview),
- len = val.length; // save original content length
- // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
- // is still difficult
- // in IE, pos.end can be zero after input loses focus
- if (pos.end < pos.start) {
- pos.end = pos.start;
- }
- if (pos.start > len) {
- pos.end = pos.start = len;
- }
- if (base.preview.nodeName === 'TEXTAREA') {
- // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
- if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
- pos.start += 1;
- pos.end += 1;
- }
- }
- t = pos.start;
- if (txt === '{d}') {
- txt = '';
- pos.end += 1;
- }
- if (isBksp) {
- txt = '';
- bksp = isBksp && t === pos.end && t > 0;
- }
- val = val.substr(0, t - (bksp ? 1 : 0)) + txt + val.substr(pos.end);
- t += bksp ? -1 : txt.length;
- base.setValue(val);
- base.saveCaret(t, t); // save caret in case of bksp
- base.setScroll();
- // see #506.. allow chaining of insertText
- return base;
- };
- base.insertContentEditable = function (txt) {
- base.$preview.focus();
- base.execCommand('insertText', txt);
- base.saveCaret();
- return base;
- };
- // check max length
- base.checkMaxLength = function () {
- if (!base.$preview) { return; }
- var start, caret,
- val = base.getValue(),
- len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length;
- if (o.maxLength !== false && len > o.maxLength) {
- start = $keyboard.caret(base.$preview).start;
- caret = Math.min(start, o.maxLength);
- // prevent inserting new characters when maxed #289
- if (!o.maxInsert) {
- val = base.last.val;
- caret = start - 1; // move caret back one
- }
- base.setValue(val.substring(0, o.maxLength));
- // restore caret on change, otherwise it ends up at the end.
- base.saveCaret(caret, caret);
- }
- if (base.$decBtn.length) {
- base.checkDecimal();
- }
- // allow chaining
- return base;
- };
- // mousedown repeater
- base.repeatKey = function (key) {
- key.trigger($keyboard.events.kbRepeater);
- if (base.mouseRepeat[0]) {
- base.repeater = setTimeout(function () {
- if (base){
- base.repeatKey(key);
- }
- }, base.repeatTime);
- }
- };
- base.getKeySet = function () {
- var sets = [];
- if (base.altActive) {
- sets.push('alt');
- }
- if (base.shiftActive) {
- sets.push('shift');
- }
- if (base.metaActive) {
- // base.metaActive contains the string name of the
- // current meta keyset
- sets.push(base.metaActive);
- }
- return sets.length ? sets.join('+') : 'normal';
- };
- // make it easier to switch keysets via API
- // showKeySet('shift+alt+meta1')
- base.showKeySet = function (str) {
- if (typeof str === 'string') {
- base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
- base.shiftActive = /shift/i.test(str);
- base.altActive = /alt/i.test(str);
- if (/\bmeta/.test(str)) {
- base.metaActive = true;
- base.showSet(str.match(/\bmeta[\w-]+/i)[0]);
- } else {
- base.metaActive = false;
- base.showSet();
- }
- } else {
- base.showSet(str);
- }
- // allow chaining
- return base;
- };
- base.showSet = function (name) {
- if (!base.hasKeyboard()) { return; }
- o = base.options; // refresh options
- var kbcss = $keyboard.css,
- prefix = '.' + kbcss.keyPrefix,
- active = o.css.buttonActive,
- key = '',
- toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
- if (!base.shiftActive) {
- base.capsLock = false;
- }
- // check meta key set
- if (base.metaActive) {
- // remove "-shift" and "-alt" from meta name if it exists
- if (base.shiftActive) {
- name = (name || '').replace('-shift', '');
- }
- if (base.altActive) {
- name = (name || '').replace('-alt', '');
- }
- // the name attribute contains the meta set name 'meta99'
- key = (/^meta/i.test(name)) ? name : '';
- // save active meta keyset name
- if (key === '') {
- key = (base.metaActive === true) ? '' : base.metaActive;
- } else {
- base.metaActive = key;
- }
- // if meta keyset doesn't have a shift or alt keyset, then show just the meta key set
- if ((!o.stickyShift && base.last.keyset[2] !== base.metaActive) ||
- ((base.shiftActive || base.altActive) &&
- !base.$keyboard.find('.' + kbcss.keySet + '-' + key + base.rows[toShow]).length)) {
- base.shiftActive = base.altActive = false;
- }
- } else if (!o.stickyShift && base.last.keyset[2] !== base.metaActive && base.shiftActive) {
- // switching from meta key set back to default, reset shift & alt if using stickyShift
- base.shiftActive = base.altActive = false;
- }
- toShow = (base.shiftActive ? 1 : 0) + (base.altActive ? 2 : 0);
- key = (toShow === 0 && !base.metaActive) ? '-normal' : (key === '') ? '' : '-' + key;
- if (!base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow]).length) {
- // keyset doesn't exist, so restore last keyset settings
- base.shiftActive = base.last.keyset[0];
- base.altActive = base.last.keyset[1];
- base.metaActive = base.last.keyset[2];
- return;
- }
- base.$keyboard
- .find(prefix + 'alt,' + prefix + 'shift,.' + kbcss.keyAction + '[class*=meta]')
- .removeClass(active)
- .end()
- .find(prefix + 'alt')
- .toggleClass(active, base.altActive)
- .end()
- .find(prefix + 'shift')
- .toggleClass(active, base.shiftActive)
- .end()
- .find(prefix + 'lock')
- .toggleClass(active, base.capsLock)
- .end()
- .find('.' + kbcss.keySet)
- .hide()
- .end()
- .find('.' + (kbcss.keyAction + prefix + key).replace('--', '-'))
- .addClass(active);
- // show keyset using inline-block ( extender layout will then line up )
- base.$keyboard.find('.' + kbcss.keySet + key + base.rows[toShow])[0].style.display = 'inline-block';
- if (base.metaActive) {
- base.$keyboard.find(prefix + base.metaActive)
- // base.metaActive contains the string "meta#" or false
- // without the !== false, jQuery UI tries to transition the classes
- .toggleClass(active, base.metaActive !== false);
- }
- base.last.keyset = [base.shiftActive, base.altActive, base.metaActive];
- base.$el.trigger($keyboard.events.kbKeysetChange, [base, base.el]);
- if (o.reposition) {
- base.reposition();
- }
- };
- // check for key combos (dead keys)
- base.checkCombos = function () {
- // return val for close function
- if ( !(
- base.isVisible() || (
- base.hasKeyboard() &&
- base.$keyboard.hasClass( $keyboard.css.hasFocus )
- )
- ) ) {
- return base.getValue(base.$preview || base.$el);
- }
- var r, t, t2, repl,
- // use base.$preview.val() instead of base.preview.value
- // (val.length includes carriage returns in IE).
- val = base.getValue(),
- pos = $keyboard.caret(base.$preview),
- layout = $keyboard.builtLayouts[base.layout],
- max = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length,
- // save original content length
- len = max;
- // return if val is empty; fixes #352
- if (val === '') {
- // check valid on empty string - see #429
- if (o.acceptValid) {
- base.checkValid();
- }
- return val;
- }
- // silly IE caret hacks... it should work correctly, but navigating using arrow keys in a textarea
- // is still difficult
- // in IE, pos.end can be zero after input loses focus
- if (pos.end < pos.start) {
- pos.end = pos.start;
- }
- if (pos.start > len) {
- pos.end = pos.start = len;
- }
- // This makes sure the caret moves to the next line after clicking on enter (manual typing works fine)
- if ($keyboard.msie && val.substr(pos.start, 1) === '\n') {
- pos.start += 1;
- pos.end += 1;
- }
- if (o.useCombos) {
- // keep 'a' and 'o' in the regex for ae and oe ligature (æ,œ)
- // thanks to KennyTM: http://stackoverflow.com/q/4275077
- // original regex /([`\'~\^\"ao])([a-z])/mig moved to $.keyboard.comboRegex
- if ($keyboard.msie) {
- // old IE may not have the caret positioned correctly, so just check the whole thing
- val = val.replace(base.regex, function (s, accent, letter) {
- return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
- });
- // prevent combo replace error, in case the keyboard closes - see issue #116
- } else if (base.$preview.length) {
- // Modern browsers - check for combos from last two characters left of the caret
- t = pos.start - (pos.start - 2 >= 0 ? 2 : 0);
- // target last two characters
- $keyboard.caret(base.$preview, t, pos.end);
- // do combo replace
- t = $keyboard.caret(base.$preview);
- repl = function (txt) {
- return (txt || '').replace(base.regex, function (s, accent, letter) {
- return (o.combos.hasOwnProperty(accent)) ? o.combos[accent][letter] || s : s;
- });
- };
- t2 = repl(t.text);
- // add combo back
- // prevent error if caret doesn't return a function
- if (t && t.replaceStr && t2 !== t.text) {
- if (base.isContentEditable) {
- $keyboard.replaceContent(el, repl);
- } else {
- base.setValue(t.replaceStr(t2));
- }
- }
- val = base.getValue();
- }
- }
- // check input restrictions - in case content was pasted
- if (o.restrictInput && val !== '') {
- t = layout.acceptedKeys.length;
- r = layout.acceptedKeysRegex;
- if (!r) {
- t2 = $.map(layout.acceptedKeys, function (v) {
- // escape any special characters
- return v.replace(base.escapeRegex, '\\$&');
- });
- if (base.alwaysAllowed.indexOf($keyboard.keyCodes.enter) > -1) {
- t2.push('\\n'); // Fixes #686
- }
- r = layout.acceptedKeysRegex = new RegExp('(' + t2.join('|') + ')', 'g');
- }
- // only save matching keys
- t2 = val.match(r);
- if (t2) {
- val = t2.join('');
- } else {
- // no valid characters
- val = '';
- len = 0;
- }
- }
- // save changes, then reposition caret
- pos.start += max - len;
- pos.end += max - len;
- base.setValue(val);
- base.saveCaret(pos.start, pos.end);
- // set scroll to keep caret in view
- base.setScroll();
- base.checkMaxLength();
- if (o.acceptValid) {
- base.checkValid();
- }
- return val; // return text, used for keyboard closing section
- };
- // Toggle accept button classes, if validating
- base.checkValid = function () {
- var kbcss = $keyboard.css,
- $accept = base.$keyboard.find('.' + kbcss.keyPrefix + 'accept'),
- valid = true;
- if ($.isFunction(o.validate)) {
- valid = o.validate(base, base.getValue(), false);
- }
- // toggle accept button classes; defined in the css
- $accept
- .toggleClass(kbcss.inputInvalid, !valid)
- .toggleClass(kbcss.inputValid, valid)
- // update title to indicate that the entry is valid or invalid
- .attr('title', $accept.attr('data-title') + ' (' + o.display[valid ? 'valid' : 'invalid'] + ')');
- };
- // Decimal button for num pad - only allow one (not used by default)
- base.checkDecimal = function () {
- // Check US '.' or European ',' format
- if ((base.decimal && /\./g.test(base.preview.value)) ||
- (!base.decimal && /\,/g.test(base.preview.value))) {
- base.$decBtn
- .attr({
- 'disabled': 'disabled',
- 'aria-disabled': 'true'
- })
- .removeClass(o.css.buttonHover)
- .addClass(o.css.buttonDisabled);
- } else {
- base.$decBtn
- .removeAttr('disabled')
- .attr({
- 'aria-disabled': 'false'
- })
- .addClass(o.css.buttonDefault)
- .removeClass(o.css.buttonDisabled);
- }
- };
- // get other layer values for a specific key
- base.getLayers = function ($el) {
- var kbcss = $keyboard.css,
- key = $el.attr('data-pos'),
- $keys = $el.closest('.' + kbcss.keyboard)
- .find('button[data-pos="' + key + '"]');
- return $keys.filter(function () {
- return $(this)
- .find('.' + kbcss.keyText)
- .text() !== '';
- })
- .add($el);
- };
- // Go to next or prev inputs
- // goToNext = true, then go to next input; if false go to prev
- // isAccepted is from autoAccept option or true if user presses shift+enter
- base.switchInput = function (goToNext, isAccepted) {
- if ($.isFunction(o.switchInput)) {
- o.switchInput(base, goToNext, isAccepted);
- } else {
- // base.$keyboard may be an empty array - see #275 (apod42)
- if (base.$keyboard.length) {
- base.$keyboard.hide();
- }
- var kb,
- stopped = false,
- all = $('button, input, select, textarea, a, [contenteditable]')
- .filter(':visible')
- .not(':disabled'),
- indx = all.index(base.$el) + (goToNext ? 1 : -1);
- if (base.$keyboard.length) {
- base.$keyboard.show();
- }
- if (indx > all.length - 1) {
- stopped = o.stopAtEnd;
- indx = 0; // go to first input
- }
- if (indx < 0) {
- stopped = o.stopAtEnd;
- indx = all.length - 1; // stop or go to last
- }
- if (!stopped) {
- isAccepted = base.close(isAccepted);
- if (!isAccepted) {
- return;
- }
- kb = all.eq(indx).data('keyboard');
- if (kb && kb.options.openOn.length) {
- kb.focusOn();
- } else {
- all.eq(indx).focus();
- }
- }
- }
- return false;
- };
- // Close the keyboard, if visible. Pass a status of true, if the content was accepted
- // (for the event trigger).
- base.close = function (accepted) {
- if (base.isOpen && base.$keyboard.length) {
- clearTimeout(base.throttled);
- var kbcss = $keyboard.css,
- kbevents = $keyboard.events,
- val = accepted ? base.checkCombos() : base.originalContent;
- // validate input if accepted
- if (accepted && $.isFunction(o.validate) && !o.validate(base, val, true)) {
- val = base.originalContent;
- accepted = false;
- if (o.cancelClose) {
- return;
- }
- }
- base.isCurrent(false);
- base.isOpen = o.alwaysOpen || o.userClosed;
- if (base.isContentEditable && !accepted) {
- // base.originalContent stores the HTML
- base.$el.html(val);
- } else {
- base.setValue(val, base.$el);
- }
- base.$el
- .removeClass(kbcss.isCurrent + ' ' + kbcss.inputAutoAccepted)
- // add 'ui-keyboard-autoaccepted' to inputs - see issue #66
- .addClass((accepted || false) ? accepted === true ? '' : kbcss.inputAutoAccepted : '')
- // trigger default change event - see issue #146
- .trigger(kbevents.inputChange);
- // don't trigger an empty event - see issue #463
- if (!o.alwaysOpen) {
- // don't trigger beforeClose if keyboard is always open
- base.$el.trigger(kbevents.kbBeforeClose, [base, base.el, (accepted || false)]);
- }
- // save caret after updating value (fixes userClosed issue with changing focus)
- $keyboard.caret(base.$preview, base.last);
- base.$el
- .trigger(((accepted || false) ? kbevents.inputAccepted : kbevents.inputCanceled), [base, base.el])
- .trigger((o.alwaysOpen) ? kbevents.kbInactive : kbevents.kbHidden, [base, base.el])
- .blur();
- // base is undefined if keyboard was destroyed - fixes #358
- if (base) {
- // add close event time
- base.last.eventTime = new Date().getTime();
- if (!(o.alwaysOpen || o.userClosed && accepted === 'true') && base.$keyboard.length) {
- // free up memory
- base.removeKeyboard();
- // rebind input focus - delayed to fix IE issue #72
- base.timer = setTimeout(function () {
- if (base) {
- base.bindFocus();
- }
- }, 200);
- }
- if (!base.watermark && base.el.value === '' && base.inPlaceholder !== '') {
- base.$el.addClass(kbcss.placeholder);
- base.setValue(base.inPlaceholder, base.$el);
- }
- }
- }
- return !!accepted;
- };
- base.accept = function () {
- return base.close(true);
- };
- base.checkClose = function (e) {
- if (base.opening) {
- return;
- }
- var kbcss = $.keyboard.css,
- $target = e.$target || $(e.target).closest('.' + $keyboard.css.keyboard + ', .' + $keyboard.css.input);
- if (!$target.length) {
- $target = $(e.target);
- }
- // needed for IE to allow switching between keyboards smoothly
- if ($target.length && $target.hasClass(kbcss.keyboard)) {
- var kb = $target.data('keyboard');
- // only trigger on self
- if (
- kb !== base &&
- !kb.$el.hasClass(kbcss.isCurrent) &&
- kb.options.openOn &&
- e.type === o.openOn
- ) {
- kb.focusOn();
- }
- } else {
- base.escClose(e, $target);
- }
- };
- // callback functions called to check if the keyboard needs to be closed
- // e.g. on escape or clicking outside the keyboard
- base.escCloseCallback = {
- // keep keyboard open if alwaysOpen or stayOpen is true - fixes mutliple
- // always open keyboards or single stay open keyboard
- keepOpen: function() {
- return !base.isOpen;
- }
- };
- base.escClose = function (e, $el) {
- if (!base.isOpen) {
- return;
- }
- if (e && e.type === 'keyup') {
- return (e.which === $keyboard.keyCodes.escape && !o.ignoreEsc) ?
- base.close(o.autoAccept && o.autoAcceptOnEsc ? 'true' : false) :
- '';
- }
- var shouldStayOpen = false,
- $target = $el.length && $el || $(e.target);
- $.each(base.escCloseCallback, function(i, callback) {
- if (typeof callback === 'function') {
- shouldStayOpen = shouldStayOpen || callback($target);
- }
- });
- if (shouldStayOpen) {
- return;
- }
- // ignore autoaccept if using escape - good idea?
- if (!base.isCurrent() && base.isOpen || base.isOpen && $target[0] !== base.el) {
- // don't close if stayOpen is set; but close if a different keyboard is being opened
- if ((o.stayOpen || o.userClosed) && !$target.hasClass($keyboard.css.input)) {
- return;
- }
- // stop propogation in IE - an input getting focus doesn't open a keyboard if one is already open
- if ($keyboard.allie) {
- e.preventDefault();
- }
- if (o.closeByClickEvent) {
- // only close the keyboard if the user is clicking on an input or if they cause a click
- // event (touchstart/mousedown will not force the close with this setting)
- var name = $target[0] && $target[0].nodeName.toLowerCase();
- if (name === 'input' || name === 'textarea' || e.type === 'click') {
- base.close(o.autoAccept ? 'true' : false);
- }
- } else {
- // send 'true' instead of a true (boolean), the input won't get a 'ui-keyboard-autoaccepted'
- // class name - see issue #66
- base.close(o.autoAccept ? 'true' : false);
- }
- }
- };
- // Build default button
- base.keyBtn = $('<button />')
- .attr({
- 'role': 'button',
- 'type': 'button',
- 'aria-disabled': 'false',
- 'tabindex': '-1'
- })
- .addClass($keyboard.css.keyButton);
- // convert key names into a class name
- base.processName = function (name) {
- var index, n,
- process = (name || '').replace(/[^a-z0-9-_]/gi, ''),
- len = process.length,
- newName = [];
- if (len > 1 && name === process) {
- // return name if basic text
- return name;
- }
- // return character code sequence
- len = name.length;
- if (len) {
- for (index = 0; index < len; index++) {
- n = name[index];
- // keep '-' and '_'... so for dash, we get two dashes in a row
- newName.push(/[a-z0-9-_]/i.test(n) ?
- (/[-_]/.test(n) && index !== 0 ? '' : n) :
- (index === 0 ? '' : '-') + n.charCodeAt(0)
- );
- }
- return newName.join('');
- }
- return name;
- };
- base.processKeys = function (name) {
- var tmp,
- // Don't split colons followed by //, e.g. https://; Fixes #555
- parts = name.split(/:(?!\/\/)/),
- data = {
- name: null,
- map: '',
- title: ''
- };
- /* map defined keys
- format 'key(A):Label_for_key_(ignore_parentheses_here)'
- 'key' = key that is seen (can any character(s); but it might need to be escaped using '\'
- or entered as unicode '\u####'
- '(A)' = the actual key on the real keyboard to remap
- ':Label_for_key' ends up in the title/tooltip
- Examples:
- '\u0391(A):alpha', 'x(y):this_(might)_cause_problems
- or edge cases of ':(x)', 'x(:)', 'x(()' or 'x())'
- Enhancement (if I can get alt keys to work):
- A mapped key will include the mod key, e.g. 'x(alt-x)' or 'x(alt-shift-x)'
- */
- if (/\(.+\)/.test(parts[0]) || /^:\(.+\)/.test(name) || /\([(:)]\)/.test(name)) {
- // edge cases 'x(:)', 'x(()' or 'x())'
- if (/\([(:)]\)/.test(name)) {
- tmp = parts[0].match(/([^(]+)\((.+)\)/);
- if (tmp && tmp.length) {
- data.name = tmp[1];
- data.map = tmp[2];
- data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
- } else {
- // edge cases 'x(:)', ':(x)' or ':(:)'
- data.name = name.match(/([^(]+)/)[0];
- if (data.name === ':') {
- // ':(:):test' => parts = [ '', '(', ')', 'title' ] need to slice 1
- parts = parts.slice(1);
- }
- if (tmp === null) {
- // 'x(:):test' => parts = [ 'x(', ')', 'title' ] need to slice 2
- data.map = ':';
- parts = parts.slice(2);
- }
- data.title = parts.length ? parts.join(':') : '';
- }
- } else {
- // example: \u0391(A):alpha; extract 'A' from '(A)'
- data.map = name.match(/\(([^()]+?)\)/)[1];
- // remove '(A)', left with '\u0391:alpha'
- name = name.replace(/\(([^()]+)\)/, '');
- tmp = name.split(':');
- // get '\u0391' from '\u0391:alpha'
- if (tmp[0] === '') {
- data.name = ':';
- parts = parts.slice(1);
- } else {
- data.name = tmp[0];
- }
- data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
- }
- } else {
- // find key label
- // corner case of '::;' reduced to ':;', split as ['', ';']
- if (name !== '' && parts[0] === '') {
- data.name = ':';
- parts = parts.slice(1);
- } else {
- data.name = parts[0];
- }
- data.title = parts.length > 1 ? parts.slice(1).join(':') : '';
- }
- data.title = $.trim(data.title).replace(/_/g, ' ');
- return data;
- };
- // Add key function
- // keyName = the name of the function called in $.keyboard.keyaction when the button is clicked
- // name = name added to key, or cross-referenced in the display options
- // base.temp[0] = keyset to attach the new button
- // regKey = true when it is not an action key
- base.addKey = function (keyName, action, regKey) {
- var keyClass, tmp, keys,
- data = {},
- txt = base.processKeys(regKey ? keyName : action),
- kbcss = $keyboard.css;
- if (!regKey && o.display[txt.name]) {
- keys = base.processKeys(o.display[txt.name]);
- // action contained in "keyName" (e.g. keyName = "accept",
- // action = "a" (use checkmark instead of text))
- keys.action = base.processKeys(keyName).name;
- } else {
- // when regKey is true, keyName is the same as action
- keys = txt;
- keys.action = txt.name;
- }
- data.name = base.processName(txt.name);
- if (keys.name !== '') {
- if (keys.map !== '') {
- $keyboard.builtLayouts[base.layout].mappedKeys[keys.map] = keys.name;
- $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
- } else if (regKey) {
- $keyboard.builtLayouts[base.layout].acceptedKeys.push(keys.name);
- }
- }
- if (regKey) {
- keyClass = data.name === '' ? '' : kbcss.keyPrefix + data.name;
- } else {
- // Action keys will have the 'ui-keyboard-actionkey' class
- keyClass = kbcss.keyAction + ' ' + kbcss.keyPrefix + keys.action;
- }
- // '\u2190'.length = 1 because the unicode is converted, so if more than one character,
- // add the wide class
- keyClass += (keys.name.length > 2 ? ' ' + kbcss.keyWide : '') + ' ' + o.css.buttonDefault;
- data.html = '<span class="' + kbcss.keyText + '">' +
- // this prevents HTML from being added to the key
- keys.name.replace(/[\u00A0-\u9999]/gim, function (i) {
- return '&#' + i.charCodeAt(0) + ';';
- }) +
- '</span>';
- data.$key = base.keyBtn
- .clone()
- .attr({
- 'data-value': regKey ? keys.name : keys.action, // value
- 'data-name': keys.action,
- 'data-pos': base.temp[1] + ',' + base.temp[2],
- 'data-action': keys.action,
- 'data-html': data.html
- })
- // add 'ui-keyboard-' + data.name for all keys
- // (e.g. 'Bksp' will have 'ui-keyboard-bskp' class)
- // any non-alphanumeric characters will be replaced with
- // their decimal unicode value
- // (e.g. '~' is a regular key, class = 'ui-keyboard-126'
- // (126 is the unicode decimal value - same as ~)
- // See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
- .addClass(keyClass)
- .html(data.html)
- .appendTo(base.temp[0]);
- if (keys.map) {
- data.$key.attr('data-mapped', keys.map);
- }
- if (keys.title || txt.title) {
- data.$key.attr({
- 'data-title': txt.title || keys.title, // used to allow adding content to title
- 'title': txt.title || keys.title
- });
- }
- if (typeof o.buildKey === 'function') {
- data = o.buildKey(base, data);
- // copy html back to attributes
- tmp = data.$key.html();
- data.$key.attr('data-html', tmp);
- }
- return data.$key;
- };
- base.customHash = function (layout) {
- /*jshint bitwise:false */
- var i, array, hash, character, len,
- arrays = [],
- merged = [];
- // pass layout to allow for testing
- layout = typeof layout === 'undefined' ? o.customLayout : layout;
- // get all layout arrays
- for (array in layout) {
- if (layout.hasOwnProperty(array)) {
- arrays.push(layout[array]);
- }
- }
- // flatten array
- merged = merged.concat.apply(merged, arrays).join(' ');
- // produce hash name - http://stackoverflow.com/a/7616484/145346
- hash = 0;
- len = merged.length;
- if (len === 0) {
- return hash;
- }
- for (i = 0; i < len; i++) {
- character = merged.charCodeAt(i);
- hash = ((hash << 5) - hash) + character;
- hash = hash & hash; // Convert to 32bit integer
- }
- return hash;
- };
- base.buildKeyboard = function (name, internal) {
- // o.display is empty when this is called from the scramble extension (when alwaysOpen:true)
- if ($.isEmptyObject(o.display)) {
- // set keyboard language
- base.updateLanguage();
- }
- var index, row, $row, currentSet,
- kbcss = $keyboard.css,
- sets = 0,
- layout = $keyboard.builtLayouts[name || base.layout || o.layout] = {
- mappedKeys: {},
- acceptedKeys: []
- },
- acceptedKeys = layout.acceptedKeys = o.restrictInclude ?
- ('' + o.restrictInclude).split(/\s+/) || [] :
- [],
- // using $layout temporarily to hold keyboard popup classnames
- $layout = kbcss.keyboard + ' ' + o.css.popup + ' ' + o.css.container +
- (o.alwaysOpen || o.userClosed ? ' ' + kbcss.alwaysOpen : ''),
- container = $('<div />')
- .addClass($layout)
- .attr({
- 'role': 'textbox'
- })
- .hide();
- // allow adding "{space}" as an accepted key - Fixes #627
- index = $.inArray('{space}', acceptedKeys);
- if (index > -1) {
- acceptedKeys[index] = ' ';
- }
- // verify layout or setup custom keyboard
- if ((internal && o.layout === 'custom') || !$keyboard.layouts.hasOwnProperty(o.layout)) {
- o.layout = 'custom';
- $layout = $keyboard.layouts.custom = o.customLayout || {
- 'normal': ['{cancel}']
- };
- } else {
- $layout = $keyboard.layouts[internal ? o.layout : name || base.layout || o.layout];
- }
- // Main keyboard building loop
- $.each($layout, function (set, keySet) {
- // skip layout name & lang settings
- if (set !== '' && !/^(name|lang|rtl)$/i.test(set)) {
- // keep backwards compatibility for change from default to normal naming
- if (set === 'default') {
- set = 'normal';
- }
- sets++;
- $row = $('<div />')
- .attr('name', set) // added for typing extension
- .addClass(kbcss.keySet + ' ' + kbcss.keySet + '-' + set)
- .appendTo(container)
- .toggle(set === 'normal');
- for (row = 0; row < keySet.length; row++) {
- // remove extra spaces before spliting (regex probably could be improved)
- currentSet = $.trim(keySet[row]).replace(/\{(\.?)[\s+]?:[\s+]?(\.?)\}/g, '{$1:$2}');
- base.buildRow($row, row, currentSet.split(/\s+/), acceptedKeys);
- $row.find('.' + kbcss.keyButton + ',.' + kbcss.keySpacer)
- .filter(':last')
- .after('<br class="' + kbcss.endRow + '"/>');
- }
- }
- });
- if (sets > 1) {
- base.sets = true;
- }
- layout.hasMappedKeys = !($.isEmptyObject(layout.mappedKeys));
- layout.$keyboard = container;
- return container;
- };
- base.buildRow = function ($row, row, keys, acceptedKeys) {
- var t, txt, key, isAction, action, margin,
- kbcss = $keyboard.css;
- for (key = 0; key < keys.length; key++) {
- // used by addKey function
- base.temp = [$row, row, key];
- isAction = false;
- // ignore empty keys
- if (keys[key].length === 0) {
- continue;
- }
- // process here if it's an action key
- if (/^\{\S+\}$/.test(keys[key])) {
- action = keys[key].match(/^\{(\S+)\}$/)[1];
- // add active class if there are double exclamation points in the name
- if (/\!\!/.test(action)) {
- action = action.replace('!!', '');
- isAction = true;
- }
- // add empty space
- if (/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i.test(action)) {
- // not perfect globalization, but allows you to use {sp:1,1em}, {sp:1.2em} or {sp:15px}
- margin = parseFloat(action
- .replace(/,/, '.')
- .match(/^sp:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
- );
- $('<span class="' + kbcss.keyText + '"></span>')
- // previously {sp:1} would add 1em margin to each side of a 0 width span
- // now Firefox doesn't seem to render 0px dimensions, so now we set the
- // 1em margin x 2 for the width
- .width((action.match(/px/i) ? margin + 'px' : (margin * 2) + 'em'))
- .addClass(kbcss.keySpacer)
- .appendTo($row);
- }
- // add empty button
- if (/^empty(:((\d+)?([\.|,]\d+)?)(em|px)?)?$/i.test(action)) {
- margin = (/:/.test(action)) ? parseFloat(action
- .replace(/,/, '.')
- .match(/^empty:((\d+)?([\.|,]\d+)?)(em|px)?$/i)[1] || 0
- ) : '';
- base
- .addKey('', ' ', true)
- .addClass(o.css.buttonDisabled + ' ' + o.css.buttonEmpty)
- .attr('aria-disabled', true)
- .width(margin ? (action.match('px') ? margin + 'px' : (margin * 2) + 'em') : '');
- continue;
- }
- // meta keys
- if (/^meta[\w-]+\:?(\w+)?/i.test(action)) {
- base
- .addKey(action.split(':')[0], action)
- .addClass(kbcss.keyHasActive);
- continue;
- }
- // switch needed for action keys with multiple names/shortcuts or
- // default will catch all others
- txt = action.split(':');
- switch (txt[0].toLowerCase()) {
- case 'a':
- case 'accept':
- base
- .addKey('accept', action)
- .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
- break;
- case 'alt':
- case 'altgr':
- base
- .addKey('alt', action)
- .addClass(kbcss.keyHasActive);
- break;
- case 'b':
- case 'bksp':
- base.addKey('bksp', action);
- break;
- case 'c':
- case 'cancel':
- base
- .addKey('cancel', action)
- .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
- break;
- // toggle combo/diacritic key
- /*jshint -W083 */
- case 'combo':
- base
- .addKey('combo', action)
- .addClass(kbcss.keyHasActive)
- .attr('title', function (indx, title) {
- // add combo key state to title
- return title + ' ' + o.display[o.useCombos ? 'active' : 'disabled'];
- })
- .toggleClass(o.css.buttonActive, o.useCombos);
- break;
- // Decimal - unique decimal point (num pad layout)
- case 'dec':
- acceptedKeys.push((base.decimal) ? '.' : ',');
- base.addKey('dec', action);
- break;
- case 'e':
- case 'enter':
- base
- .addKey('enter', action)
- .addClass(o.css.buttonAction + ' ' + kbcss.keyAction);
- break;
- case 'lock':
- base
- .addKey('lock', action)
- .addClass(kbcss.keyHasActive);
- break;
- case 's':
- case 'shift':
- base
- .addKey('shift', action)
- .addClass(kbcss.keyHasActive);
- break;
- // Change sign (for num pad layout)
- case 'sign':
- acceptedKeys.push('-');
- base.addKey('sign', action);
- break;
- case 'space':
- acceptedKeys.push(' ');
- base.addKey('space', action);
- break;
- case 't':
- case 'tab':
- base.addKey('tab', action);
- break;
- default:
- if ($keyboard.keyaction.hasOwnProperty(txt[0])) {
- base
- .addKey(txt[0], action)
- .toggleClass(o.css.buttonAction + ' ' + kbcss.keyAction, isAction);
- }
- }
- } else {
- // regular button (not an action key)
- t = keys[key];
- base.addKey(t, t, true);
- }
- }
- };
- base.removeBindings = function (namespace) {
- $(document).unbind(namespace);
- if (base.el.ownerDocument !== document) {
- $(base.el.ownerDocument).unbind(namespace);
- }
- $(window).unbind(namespace);
- base.$el.unbind(namespace);
- };
- base.removeKeyboard = function () {
- base.$decBtn = [];
- // base.$preview === base.$el when o.usePreview is false - fixes #442
- if (o.usePreview) {
- base.$preview.removeData('keyboard');
- }
- base.$preview.unbind(base.namespace + 'keybindings');
- base.preview = null;
- base.$preview = null;
- base.$previewCopy = null;
- base.$keyboard.removeData('keyboard');
- base.$keyboard.remove();
- base.$keyboard = [];
- base.isOpen = false;
- base.isCurrent(false);
- };
- base.destroy = function (callback) {
- var index,
- kbcss = $keyboard.css,
- len = base.extensionNamespace.length,
- tmp = [
- kbcss.input,
- kbcss.locked,
- kbcss.placeholder,
- kbcss.noKeyboard,
- kbcss.alwaysOpen,
- o.css.input,
- kbcss.isCurrent
- ].join(' ');
- clearTimeout(base.timer);
- clearTimeout(base.timer2);
- clearTimeout(base.timer3);
- if (base.$keyboard.length) {
- base.removeKeyboard();
- }
- base.removeBindings(base.namespace);
- base.removeBindings(base.namespace + 'callbacks');
- for (index = 0; index < len; index++) {
- base.removeBindings(base.extensionNamespace[index]);
- }
- base.el.active = false;
- base.$el
- .removeClass(tmp)
- .removeAttr('aria-haspopup')
- .removeAttr('role')
- .removeData('keyboard');
- base = null;
- if (typeof callback === 'function') {
- callback();
- }
- };
- // Run initializer
- base.init();
- }; // end $.keyboard definition
- // event.which & ASCII values
- $keyboard.keyCodes = {
- backSpace: 8,
- tab: 9,
- enter: 13,
- capsLock: 20,
- escape: 27,
- space: 32,
- pageUp: 33,
- pageDown: 34,
- end: 35,
- home: 36,
- left: 37,
- up: 38,
- right: 39,
- down: 40,
- insert: 45,
- delete: 46,
- // event.which keyCodes (uppercase letters)
- A: 65,
- Z: 90,
- V: 86,
- C: 67,
- X: 88,
- // ASCII lowercase a & z
- a: 97,
- z: 122
- };
- $keyboard.css = {
- // keyboard id suffix
- idSuffix: '_keyboard',
- // class name to set initial focus
- initialFocus: 'keyboard-init-focus',
- // element class names
- input: 'ui-keyboard-input',
- inputClone: 'ui-keyboard-preview-clone',
- wrapper: 'ui-keyboard-preview-wrapper',
- preview: 'ui-keyboard-preview',
- keyboard: 'ui-keyboard',
- keySet: 'ui-keyboard-keyset',
- keyButton: 'ui-keyboard-button',
- keyWide: 'ui-keyboard-widekey',
- keyPrefix: 'ui-keyboard-',
- keyText: 'ui-keyboard-text', // span with button text
- keyHasActive: 'ui-keyboard-hasactivestate',
- keyAction: 'ui-keyboard-actionkey',
- keySpacer: 'ui-keyboard-spacer', // empty keys
- keyToggle: 'ui-keyboard-toggle',
- keyDisabled: 'ui-keyboard-disabled',
- // Class for BRs with a div wrapper inside of contenteditable
- divWrapperCE: 'ui-keyboard-div-wrapper',
- // states
- locked: 'ui-keyboard-lockedinput',
- alwaysOpen: 'ui-keyboard-always-open',
- noKeyboard: 'ui-keyboard-nokeyboard',
- placeholder: 'ui-keyboard-placeholder',
- hasFocus: 'ui-keyboard-has-focus',
- isCurrent: 'ui-keyboard-input-current',
- // validation & autoaccept
- inputValid: 'ui-keyboard-valid-input',
- inputInvalid: 'ui-keyboard-invalid-input',
- inputAutoAccepted: 'ui-keyboard-autoaccepted',
- endRow: 'ui-keyboard-button-endrow' // class added to <br>
- };
- $keyboard.events = {
- // keyboard events
- kbChange: 'keyboardChange',
- kbBeforeClose: 'beforeClose',
- kbBeforeVisible: 'beforeVisible',
- kbVisible: 'visible',
- kbInit: 'initialized',
- kbInactive: 'inactive',
- kbHidden: 'hidden',
- kbRepeater: 'repeater',
- kbKeysetChange: 'keysetChange',
- // input events
- inputAccepted: 'accepted',
- inputCanceled: 'canceled',
- inputChange: 'change',
- inputRestricted: 'restricted'
- };
- // Action key function list
- $keyboard.keyaction = {
- accept: function (base) {
- base.close(true); // same as base.accept();
- return false; // return false prevents further processing
- },
- alt: function (base) {
- base.altActive = !base.altActive;
- base.showSet();
- },
- bksp: function (base) {
- if (base.isContentEditable) {
- base.execCommand('delete');
- // save new caret position
- base.saveCaret();
- } else {
- // the script looks for the '\b' string and initiates a backspace
- base.insertText('\b');
- }
- },
- cancel: function (base) {
- base.close();
- return false; // return false prevents further processing
- },
- clear: function (base) {
- base.$preview[base.isContentEditable ? 'text' : 'val']('');
- if (base.$decBtn.length) {
- base.checkDecimal();
- }
- },
- combo: function (base) {
- var o = base.options,
- c = !o.useCombos,
- $combo = base.$keyboard.find('.' + $keyboard.css.keyPrefix + 'combo');
- o.useCombos = c;
- $combo
- .toggleClass(o.css.buttonActive, c)
- // update combo key state
- .attr('title', $combo.attr('data-title') + ' (' + o.display[c ? 'active' : 'disabled'] + ')');
- if (c) {
- base.checkCombos();
- }
- return false;
- },
- dec: function (base) {
- base.insertText((base.decimal) ? '.' : ',');
- },
- del: function (base) {
- if (base.isContentEditable) {
- base.execCommand('forwardDelete');
- } else {
- // the script looks for the '{d}' string and initiates a delete
- base.insertText('{d}');
- }
- },
- // resets to base keyset (deprecated because "default" is a reserved word)
- 'default': function (base) {
- base.shiftActive = base.altActive = base.metaActive = false;
- base.showSet();
- },
- // el is the pressed key (button) object; it is null when the real keyboard enter is pressed
- enter: function (base, el, e) {
- var tag = base.el.nodeName,
- o = base.options;
- // shift+enter in textareas
- if (e.shiftKey) {
- // textarea, input & contenteditable - enterMod + shift + enter = accept,
- // then go to prev; base.switchInput(goToNext, autoAccept)
- // textarea & input - shift + enter = accept (no navigation)
- return (o.enterNavigation) ? base.switchInput(!e[o.enterMod], true) : base.close(true);
- }
- // input only - enterMod + enter to navigate
- if (o.enterNavigation && (tag !== 'TEXTAREA' || e[o.enterMod])) {
- return base.switchInput(!e[o.enterMod], o.autoAccept ? 'true' : false);
- }
- // pressing virtual enter button inside of a textarea - add a carriage return
- // e.target is span when clicking on text and button at other times
- if (tag === 'TEXTAREA' && $(e.target).closest('button').length) {
- // IE8 fix (space + \n) - fixes #71 thanks Blookie!
- base.insertText(($keyboard.msie ? ' ' : '') + '\n');
- }
- if (base.isContentEditable && !o.enterNavigation) {
- base.execCommand('insertHTML', '<div><br class="' + $keyboard.css.divWrapperCE + '"></div>');
- // Using backspace on wrapped BRs will now shift the textnode inside of the wrapped BR
- // Although not ideal, the caret is moved after the block - see the wiki page for
- // more details: https://github.com/Mottie/Keyboard/wiki/Contenteditable#limitations
- // move caret after a delay to allow rendering of HTML
- setTimeout(function() {
- $keyboard.keyaction.right(base);
- base.saveCaret();
- }, 0);
- }
- },
- // caps lock key
- lock: function (base) {
- base.last.keyset[0] = base.shiftActive = base.capsLock = !base.capsLock;
- base.showSet();
- },
- left: function (base) {
- var p = $keyboard.caret(base.$preview);
- if (p.start - 1 >= 0) {
- // move both start and end of caret (prevents text selection) & save caret position
- base.last.start = base.last.end = p.start - 1;
- $keyboard.caret(base.$preview, base.last);
- base.setScroll();
- }
- },
- meta: function (base, el) {
- var $el = $(el);
- base.metaActive = !$el.hasClass(base.options.css.buttonActive);
- base.showSet($el.attr('data-name'));
- },
- next: function (base) {
- base.switchInput(true, base.options.autoAccept);
- return false;
- },
- // same as 'default' - resets to base keyset
- normal: function (base) {
- base.shiftActive = base.altActive = base.metaActive = false;
- base.showSet();
- },
- prev: function (base) {
- base.switchInput(false, base.options.autoAccept);
- return false;
- },
- right: function (base) {
- var p = $keyboard.caret(base.$preview),
- len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : base.getValue().length;
- if (p.end + 1 <= len) {
- // move both start and end of caret to end position
- // (prevents text selection) && save caret position
- base.last.start = base.last.end = p.end + 1;
- $keyboard.caret(base.$preview, base.last);
- base.setScroll();
- }
- },
- shift: function (base) {
- base.last.keyset[0] = base.shiftActive = !base.shiftActive;
- base.showSet();
- },
- sign: function (base) {
- if (/^[+-]?\d*\.?\d*$/.test(base.getValue())) {
- var caret,
- p = $keyboard.caret(base.$preview),
- val = base.getValue(),
- len = base.isContentEditable ? $keyboard.getEditableLength(base.el) : val.length;
- base.setValue(val * -1);
- caret = len - val.length;
- base.last.start = p.start + caret;
- base.last.end = p.end + caret;
- $keyboard.caret(base.$preview, base.last);
- base.setScroll();
- }
- },
- space: function (base) {
- base.insertText(' ');
- },
- tab: function (base) {
- var tag = base.el.nodeName,
- o = base.options;
- if (tag !== 'TEXTAREA') {
- if (o.tabNavigation) {
- return base.switchInput(!base.shiftActive, true);
- } else if (tag === 'INPUT') {
- // ignore tab key in input
- return false;
- }
- }
- base.insertText('\t');
- },
- toggle: function (base) {
- base.enabled = !base.enabled;
- base.toggle();
- },
- // *** Special action keys: NBSP & zero-width characters ***
- // Non-breaking space
- NBSP: '\u00a0',
- // zero width space
- ZWSP: '\u200b',
- // Zero width non-joiner
- ZWNJ: '\u200c',
- // Zero width joiner
- ZWJ: '\u200d',
- // Left-to-right Mark
- LRM: '\u200e',
- // Right-to-left Mark
- RLM: '\u200f'
- };
- // Default keyboard layouts
- $keyboard.builtLayouts = {};
- $keyboard.layouts = {
- 'alpha': {
- 'normal': [
- '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
- '{tab} a b c d e f g h i j [ ] \\',
- 'k l m n o p q r s ; \' {enter}',
- '{shift} t u v w x y z , . / {shift}',
- '{accept} {space} {cancel}'
- ],
- 'shift': [
- '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
- '{tab} A B C D E F G H I J { } |',
- 'K L M N O P Q R S : " {enter}',
- '{shift} T U V W X Y Z < > ? {shift}',
- '{accept} {space} {cancel}'
- ]
- },
- 'qwerty': {
- 'normal': [
- '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
- '{tab} q w e r t y u i o p [ ] \\',
- 'a s d f g h j k l ; \' {enter}',
- '{shift} z x c v b n m , . / {shift}',
- '{accept} {space} {cancel}'
- ],
- 'shift': [
- '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
- '{tab} Q W E R T Y U I O P { } |',
- 'A S D F G H J K L : " {enter}',
- '{shift} Z X C V B N M < > ? {shift}',
- '{accept} {space} {cancel}'
- ]
- },
- 'international': {
- 'normal': [
- '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
- '{tab} q w e r t y u i o p [ ] \\',
- 'a s d f g h j k l ; \' {enter}',
- '{shift} z x c v b n m , . / {shift}',
- '{accept} {alt} {space} {alt} {cancel}'
- ],
- 'shift': [
- '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
- '{tab} Q W E R T Y U I O P { } |',
- 'A S D F G H J K L : " {enter}',
- '{shift} Z X C V B N M < > ? {shift}',
- '{accept} {alt} {space} {alt} {cancel}'
- ],
- 'alt': [
- '~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}',
- '{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac',
- '\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}',
- '{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}',
- '{accept} {alt} {space} {alt} {cancel}'
- ],
- 'alt-shift': [
- '~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}',
- '{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6',
- '\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}',
- '{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}',
- '{accept} {alt} {space} {alt} {cancel}'
- ]
- },
- 'colemak': {
- 'normal': [
- '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
- '{tab} q w f p g j l u y ; [ ] \\',
- '{bksp} a r s t d h n e i o \' {enter}',
- '{shift} z x c v b k m , . / {shift}',
- '{accept} {space} {cancel}'
- ],
- 'shift': [
- '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
- '{tab} Q W F P G J L U Y : { } |',
- '{bksp} A R S T D H N E I O " {enter}',
- '{shift} Z X C V B K M < > ? {shift}',
- '{accept} {space} {cancel}'
- ]
- },
- 'dvorak': {
- 'normal': [
- '` 1 2 3 4 5 6 7 8 9 0 [ ] {bksp}',
- '{tab} \' , . p y f g c r l / = \\',
- 'a o e u i d h t n s - {enter}',
- '{shift} ; q j k x b m w v z {shift}',
- '{accept} {space} {cancel}'
- ],
- 'shift': [
- '~ ! @ # $ % ^ & * ( ) { } {bksp}',
- '{tab} " < > P Y F G C R L ? + |',
- 'A O E U I D H T N S _ {enter}',
- '{shift} : Q J K X B M W V Z {shift}',
- '{accept} {space} {cancel}'
- ]
- },
- 'num': {
- 'normal': [
- '= ( ) {b}',
- '{clear} / * -',
- '7 8 9 +',
- '4 5 6 {sign}',
- '1 2 3 %',
- '0 {dec} {a} {c}'
- ]
- }
- };
- $keyboard.language = {
- en: {
- display: {
- // check mark - same action as accept
- 'a': '\u2714:Accept (Shift+Enter)',
- 'accept': 'Accept:Accept (Shift+Enter)',
- // other alternatives \u2311
- 'alt': 'Alt:\u2325 AltGr',
- // Left arrow (same as ←)
- 'b': '\u232b:Backspace',
- 'bksp': 'Bksp:Backspace',
- // big X, close - same action as cancel
- 'c': '\u2716:Cancel (Esc)',
- 'cancel': 'Cancel:Cancel (Esc)',
- // clear num pad
- 'clear': 'C:Clear',
- 'combo': '\u00f6:Toggle Combo Keys',
- // decimal point for num pad (optional), change '.' to ',' for European format
- 'dec': '.:Decimal',
- // down, then left arrow - enter symbol
- 'e': '\u23ce:Enter',
- 'empty': '\u00a0',
- 'enter': 'Enter:Enter \u23ce',
- // left arrow (move caret)
- 'left': '\u2190',
- // caps lock
- 'lock': 'Lock:\u21ea Caps Lock',
- 'next': 'Next \u21e8',
- 'prev': '\u21e6 Prev',
- // right arrow (move caret)
- 'right': '\u2192',
- // thick hollow up arrow
- 's': '\u21e7:Shift',
- 'shift': 'Shift:Shift',
- // +/- sign for num pad
- 'sign': '\u00b1:Change Sign',
- 'space': '\u00a0:Space',
- // right arrow to bar (used since this virtual keyboard works with one directional tabs)
- 't': '\u21e5:Tab',
- // \u21b9 is the true tab symbol (left & right arrows)
- 'tab': '\u21e5 Tab:Tab',
- // replaced by an image
- 'toggle': ' ',
- // added to titles of keys
- // accept key status when acceptValid:true
- 'valid': 'valid',
- 'invalid': 'invalid',
- // combo key states
- 'active': 'active',
- 'disabled': 'disabled'
- },
- // Message added to the key title while hovering, if the mousewheel plugin exists
- wheelMessage: 'Use mousewheel to see other keys',
- comboRegex: /([`\'~\^\"ao])([a-z])/mig,
- combos: {
- // grave
- '`': { a: '\u00e0', A: '\u00c0', e: '\u00e8', E: '\u00c8', i: '\u00ec', I: '\u00cc', o: '\u00f2',
- O: '\u00d2', u: '\u00f9', U: '\u00d9', y: '\u1ef3', Y: '\u1ef2' },
- // acute & cedilla
- "'": { a: '\u00e1', A: '\u00c1', e: '\u00e9', E: '\u00c9', i: '\u00ed', I: '\u00cd', o: '\u00f3',
- O: '\u00d3', u: '\u00fa', U: '\u00da', y: '\u00fd', Y: '\u00dd' },
- // umlaut/trema
- '"': { a: '\u00e4', A: '\u00c4', e: '\u00eb', E: '\u00cb', i: '\u00ef', I: '\u00cf', o: '\u00f6',
- O: '\u00d6', u: '\u00fc', U: '\u00dc', y: '\u00ff', Y: '\u0178' },
- // circumflex
- '^': { a: '\u00e2', A: '\u00c2', e: '\u00ea', E: '\u00ca', i: '\u00ee', I: '\u00ce', o: '\u00f4',
- O: '\u00d4', u: '\u00fb', U: '\u00db', y: '\u0177', Y: '\u0176' },
- // tilde
- '~': { a: '\u00e3', A: '\u00c3', e: '\u1ebd', E: '\u1ebc', i: '\u0129', I: '\u0128', o: '\u00f5',
- O: '\u00d5', u: '\u0169', U: '\u0168', y: '\u1ef9', Y: '\u1ef8', n: '\u00f1', N: '\u00d1' }
- }
- }
- };
- $keyboard.defaultOptions = {
- // set this to ISO 639-1 language code to override language set by the layout
- // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
- // language defaults to 'en' if not found
- language: null,
- rtl: false,
- // *** choose layout & positioning ***
- layout: 'qwerty',
- customLayout: null,
- position: {
- // optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
- of: null,
- my: 'center top',
- at: 'center top',
- // used when 'usePreview' is false (centers the keyboard at the bottom of the input/textarea)
- at2: 'center bottom'
- },
- // allow jQuery position utility to reposition the keyboard on window resize
- reposition: true,
- // preview added above keyboard if true, original input/textarea used if false
- usePreview: true,
- // if true, the keyboard will always be visible
- alwaysOpen: false,
- // give the preview initial focus when the keyboard becomes visible
- initialFocus: true,
- // avoid changing the focus (hardware keyboard probably won't work)
- noFocus: false,
- // if true, keyboard will remain open even if the input loses focus, but closes on escape
- // or when another keyboard opens.
- stayOpen: false,
- // Prevents the keyboard from closing when the user clicks or presses outside the keyboard
- // the `autoAccept` option must also be set to true when this option is true or changes are lost
- userClosed: false,
- // if true, keyboard will not close if you press escape.
- ignoreEsc: false,
- // if true, keyboard will only closed on click event instead of mousedown and touchstart
- closeByClickEvent: false,
- css: {
- // input & preview
- input: 'ui-widget-content ui-corner-all',
- // keyboard container
- container: 'ui-widget-content ui-widget ui-corner-all ui-helper-clearfix',
- // keyboard container extra class (same as container, but separate)
- popup: '',
- // default state
- buttonDefault: 'ui-state-default ui-corner-all',
- // hovered button
- buttonHover: 'ui-state-hover',
- // Action keys (e.g. Accept, Cancel, Tab, etc); this replaces 'actionClass' option
- buttonAction: 'ui-state-active',
- // Active keys (e.g. shift down, meta keyset active, combo keys active)
- buttonActive: 'ui-state-active',
- // used when disabling the decimal button {dec} when a decimal exists in the input area
- buttonDisabled: 'ui-state-disabled',
- buttonEmpty: 'ui-keyboard-empty'
- },
- // *** Useability ***
- // Auto-accept content when clicking outside the keyboard (popup will close)
- autoAccept: false,
- // Auto-accept content even if the user presses escape (only works if `autoAccept` is `true`)
- autoAcceptOnEsc: false,
- // Prevents direct input in the preview window when true
- lockInput: false,
- // Prevent keys not in the displayed keyboard from being typed in
- restrictInput: false,
- // Additional allowed characters while restrictInput is true
- restrictInclude: '', // e.g. 'a b foo \ud83d\ude38'
- // Check input against validate function, if valid the accept button gets a class name of
- // 'ui-keyboard-valid-input'. If invalid, the accept button gets a class name of
- // 'ui-keyboard-invalid-input'
- acceptValid: false,
- // Auto-accept when input is valid; requires `acceptValid` set `true` & validate callback
- autoAcceptOnValid: false,
- // Check validation on keyboard initialization. If false, the "Accept" key state (color)
- // will not change to show if the content is valid, or not
- checkValidOnInit: true,
- // if acceptValid is true & the validate function returns a false, this option will cancel
- // a keyboard close only after the accept button is pressed
- cancelClose: true,
- // tab to go to next, shift-tab for previous (default behavior)
- tabNavigation: false,
- // enter for next input; shift+enter accepts content & goes to next
- // shift + 'enterMod' + enter ('enterMod' is the alt as set below) will accept content and go
- // to previous in a textarea
- enterNavigation: false,
- // mod key options: 'ctrlKey', 'shiftKey', 'altKey', 'metaKey' (MAC only)
- enterMod: 'altKey', // alt-enter to go to previous; shift-alt-enter to accept & go to previous
- // if true, the next button will stop on the last keyboard input/textarea; prev button stops at first
- // if false, the next button will wrap to target the first input/textarea; prev will go to the last
- stopAtEnd: true,
- // Set this to append the keyboard after the input/textarea (appended to the input/textarea parent).
- // This option works best when the input container doesn't have a set width & when the 'tabNavigation'
- // option is true.
- appendLocally: false,
- // When appendLocally is false, the keyboard will be appended to this object
- appendTo: 'body',
- // Wrap all <br>s inside of a contenteditable in a div; without wrapping, the caret
- // position will not be accurate
- wrapBRs: true,
- // If false, the shift key will remain active until the next key is (mouse) clicked on; if true it will
- // stay active until pressed again
- stickyShift: true,
- // Prevent pasting content into the area
- preventPaste: false,
- // caret placed at the end of any text when keyboard becomes visible
- caretToEnd: false,
- // caret stays this many pixels from the edge of the input while scrolling left/right;
- // use "c" or "center" to center the caret while scrolling
- scrollAdjustment: 10,
- // Set the max number of characters allowed in the input, setting it to false disables this option
- maxLength: false,
- // allow inserting characters @ caret when maxLength is set
- maxInsert: true,
- // Mouse repeat delay - when clicking/touching a virtual keyboard key, after this delay the key will
- // start repeating
- repeatDelay: 500,
- // Mouse repeat rate - after the repeatDelay, this is the rate (characters per second) at which the
- // key is repeated Added to simulate holding down a real keyboard key and having it repeat. I haven't
- // calculated the upper limit of this rate, but it is limited to how fast the javascript can process
- // the keys. And for me, in Firefox, it's around 20.
- repeatRate: 20,
- // resets the keyboard to the default keyset when visible
- resetDefault: true,
- // Event (namespaced) on the input to reveal the keyboard. To disable it, just set it to ''.
- openOn: 'focus',
- // enable the keyboard on readonly inputs
- activeOnReadonly: false,
- // Event (namepaced) for when the character is added to the input (clicking on the keyboard)
- keyBinding: 'mousedown touchstart',
- // enable/disable mousewheel functionality
- // enabling still depends on the mousewheel plugin
- useWheel: true,
- // combos (emulate dead keys : http://en.wikipedia.org/wiki/Keyboard_layout#US-International)
- // if user inputs `a the script converts it to à, ^o becomes ô, etc.
- useCombos: true,
- /*
- // *** Methods ***
- // commenting these out to reduce the size of the minified version
- // Callbacks - attach a function to any of these callbacks as desired
- initialized : function(e, keyboard, el) {},
- beforeVisible : function(e, keyboard, el) {},
- visible : function(e, keyboard, el) {},
- beforeInsert : function(e, keyboard, el, textToAdd) { return textToAdd; },
- change : function(e, keyboard, el) {},
- beforeClose : function(e, keyboard, el, accepted) {},
- accepted : function(e, keyboard, el) {},
- canceled : function(e, keyboard, el) {},
- restricted : function(e, keyboard, el) {},
- hidden : function(e, keyboard, el) {},
- // called instead of base.switchInput
- switchInput : function(keyboard, goToNext, isAccepted) {},
- // used if you want to create a custom layout or modify the built-in keyboard
- create : function(keyboard) { return keyboard.buildKeyboard(); },
- // build key callback
- buildKey : function( keyboard, data ) {
- / *
- data = {
- // READ ONLY
- isAction : [boolean] true if key is an action key
- name : [string] key class name suffix ( prefix = 'ui-keyboard-' );
- may include decimal ascii value of character
- value : [string] text inserted (non-action keys)
- title : [string] title attribute of key
- action : [string] keyaction name
- html : [string] HTML of the key; it includes a <span> wrapping the text
- // use to modify key HTML
- $key : [object] jQuery selector of key which is already appended to keyboard
- }
- * /
- return data;
- },
- */
- // this callback is called, if the acceptValid is true, and just before the 'beforeClose' to check
- // the value if the value is valid, return true and the keyboard will continue as it should
- // (close if not always open, etc). If the value is not valid, return false and clear the keyboard
- // value ( like this "keyboard.$preview.val('');" ), if desired. The validate function is called after
- // each input, the 'isClosing' value will be false; when the accept button is clicked,
- // 'isClosing' is true
- validate: function (/* keyboard, value, isClosing */) {
- return true;
- }
- };
- // for checking combos
- $keyboard.comboRegex = /([`\'~\^\"ao])([a-z])/mig;
- // store current keyboard element; used by base.isCurrent()
- $keyboard.currentKeyboard = '';
- $('<!--[if lte IE 8]><script>jQuery("body").addClass("oldie");</script><![endif]--><!--[if IE]>' +
- '<script>jQuery("body").addClass("ie");</script><![endif]-->')
- .appendTo('body')
- .remove();
- $keyboard.msie = $('body').hasClass('oldie'); // Old IE flag, used for caret positioning
- $keyboard.allie = $('body').hasClass('ie');
- $keyboard.watermark = (typeof (document.createElement('input').placeholder) !== 'undefined');
- $keyboard.checkCaretSupport = function () {
- if (typeof $keyboard.checkCaret !== 'boolean') {
- // Check if caret position is saved when input is hidden or loses focus
- // (*cough* all versions of IE and I think Opera has/had an issue as well
- var $temp = $('<div style="height:0px;width:0px;overflow:hidden;position:fixed;top:0;left:-100px;">' +
- '<input type="text" value="testing"/></div>').prependTo('body'); // stop page scrolling
- $keyboard.caret($temp.find('input'), 3, 3);
- // Also save caret position of the input if it is locked
- $keyboard.checkCaret = $keyboard.caret($temp.find('input').hide().show()).start !== 3;
- $temp.remove();
- }
- return $keyboard.checkCaret;
- };
- $keyboard.caret = function($el, param1, param2) {
- if (!$el || !$el.length || $el.is(':hidden') || $el.css('visibility') === 'hidden') {
- return {};
- }
- var start, end, txt, pos,
- kb = $el.data( 'keyboard' ),
- noFocus = kb && kb.options.noFocus,
- formEl = /(textarea|input)/i.test($el[0].nodeName);
- if (!noFocus) { $el.focus(); }
- // set caret position
- if (typeof param1 !== 'undefined') {
- // allow setting caret using ( $el, { start: x, end: y } )
- if (typeof param1 === 'object' && 'start' in param1 && 'end' in param1) {
- start = param1.start;
- end = param1.end;
- } else if (typeof param2 === 'undefined') {
- param2 = param1; // set caret using start position
- }
- // set caret using ( $el, start, end );
- if (typeof param1 === 'number' && typeof param2 === 'number') {
- start = param1;
- end = param2;
- } else if ( param1 === 'start' ) {
- start = end = 0;
- } else if ( typeof param1 === 'string' ) {
- // unknown string setting, move caret to end
- start = end = 'end';
- }
- // *** SET CARET POSITION ***
- // modify the line below to adapt to other caret plugins
- return formEl ?
- $el.caret( start, end, noFocus ) :
- $keyboard.setEditableCaret( $el, start, end );
- }
- // *** GET CARET POSITION ***
- // modify the line below to adapt to other caret plugins
- if (formEl) {
- // modify the line below to adapt to other caret plugins
- pos = $el.caret();
- } else {
- // contenteditable
- pos = $keyboard.getEditableCaret($el[0]);
- }
- start = pos.start;
- end = pos.end;
- // *** utilities ***
- txt = formEl && $el[0].value || $el.text() || '';
- return {
- start : start,
- end : end,
- // return selected text
- text : txt.substring( start, end ),
- // return a replace selected string method
- replaceStr : function( str ) {
- return txt.substring( 0, start ) + str + txt.substring( end, txt.length );
- }
- };
- };
- $keyboard.isTextNode = function(el) {
- return el && el.nodeType === 3;
- };
- $keyboard.isBlock = function(el, node) {
- var win = el.ownerDocument.defaultView;
- if (
- node && node.nodeType === 1 && node !== el &&
- win.getComputedStyle(node).display === 'block'
- ) {
- return 1;
- }
- return 0;
- };
- // Wrap all BR's inside of contenteditable
- $keyboard.wrapBRs = function(container) {
- var $el = $(container).find('br:not(.' + $keyboard.css.divWrapperCE + ')');
- if ($el.length) {
- $.each($el, function(i, el) {
- var len = el.parentNode.childNodes.length;
- if (
- // wrap BRs if not solo child
- len !== 1 ||
- // Or if BR is wrapped by a span
- len === 1 && !$keyboard.isBlock(container, el.parentNode)
- ) {
- $(el).addClass($keyboard.css.divWrapperCE).wrap('<div>');
- }
- });
- }
- };
- $keyboard.getEditableCaret = function(container) {
- container = $(container)[0];
- if (!container.isContentEditable) { return {}; }
- var end, text,
- options = ($(container).data('keyboard') || {}).options,
- doc = container.ownerDocument,
- range = doc.getSelection().getRangeAt(0),
- result = pathToNode(range.startContainer, range.startOffset),
- start = result.position;
- if (options.wrapBRs !== false) {
- $keyboard.wrapBRs(container);
- }
- function pathToNode(endNode, offset) {
- var node, adjust,
- txt = '',
- done = false,
- position = 0,
- nodes = $.makeArray(container.childNodes);
- function checkBlock(val) {
- if (val) {
- position += val;
- txt += options && options.replaceCR || '\n';
- }
- }
- while (!done && nodes.length) {
- node = nodes.shift();
- if (node === endNode) {
- done = true;
- }
- // Add one if previous sibling was a block node (div, p, etc)
- adjust = $keyboard.isBlock(container, node.previousSibling);
- checkBlock(adjust);
- if ($keyboard.isTextNode(node)) {
- position += done ? offset : node.length;
- txt += node.textContent;
- if (done) {
- return {position: position, text: txt};
- }
- } else if (!done && node.childNodes) {
- nodes = $.makeArray(node.childNodes).concat(nodes);
- }
- // Add one if we're inside a block node (div, p, etc)
- // and previous sibling was a text node
- adjust = $keyboard.isTextNode(node.previousSibling) && $keyboard.isBlock(container, node);
- checkBlock(adjust);
- }
- return {position: position, text: txt};
- }
- // check of start and end are the same
- if (range.endContainer === range.startContainer && range.endOffset === range.startOffset) {
- end = start;
- text = '';
- } else {
- result = pathToNode(range.endContainer, range.endOffset);
- end = result.position;
- text = result.text.substring(start, end);
- }
- return {
- start: start,
- end: end,
- text: text
- };
- };
- $keyboard.getEditableLength = function(container) {
- var result = $keyboard.setEditableCaret(container, 'getMax');
- // if not a number, the container is not a contenteditable element
- return typeof result === 'number' ? result : null;
- };
- $keyboard.setEditableCaret = function(container, start, end) {
- container = $(container)[0];
- if (!container.isContentEditable) { return {}; }
- var doc = container.ownerDocument,
- range = doc.createRange(),
- sel = doc.getSelection(),
- options = ($(container).data('keyboard') || {}).options,
- s = start,
- e = end,
- text = '',
- result = findNode(start === 'getMax' ? 'end' : start);
- function findNode(offset) {
- if (offset === 'end') {
- // Set some value > content length; but return max
- offset = container.innerHTML.length;
- } else if (offset < 0) {
- offset = 0;
- }
- var node, check,
- txt = '',
- done = false,
- position = 0,
- last = 0,
- max = 0,
- nodes = $.makeArray(container.childNodes);
- function updateText(val) {
- txt += val ? options && options.replaceCR || '\n' : '';
- return val > 0;
- }
- function checkDone(adj) {
- var val = position + adj;
- last = max;
- max += adj;
- if (offset - val >= 0) {
- position = val;
- return offset - position <= 0;
- }
- return offset - val <= 0;
- }
- while (!done && nodes.length) {
- node = nodes.shift();
- // Add one if the previous sibling was a block node (div, p, etc)
- check = $keyboard.isBlock(container, node.previousSibling);
- if (updateText(check) && checkDone(check)) {
- done = true;
- }
- // Add one if we're inside a block node (div, p, etc)
- check = $keyboard.isTextNode(node.previousSibling) && $keyboard.isBlock(container, node);
- if (updateText(check) && checkDone(check)) {
- done = true;
- }
- if ($keyboard.isTextNode(node)) {
- txt += node.textContent;
- if (checkDone(node.length)) {
- check = offset - position === 0 && position - last >= 1 ? node.length : offset - position;
- return {
- node: node,
- offset: check,
- position: offset,
- text: txt
- };
- }
- } else if (!done && node.childNodes) {
- nodes = $.makeArray(node.childNodes).concat(nodes);
- }
- }
- return nodes.length ?
- {node: node, offset: offset - position, position: offset, text: txt} :
- // Offset is larger than content, return max
- {node: node, offset: node && node.length || 0, position: max, text: txt};
- }
- if (result.node) {
- s = result.position; // Adjust if start > content length
- if (start === 'getMax') {
- return s;
- }
- range.setStart(result.node, result.offset);
- // Only find end if > start and is defined... this allows passing
- // setEditableCaret(el, 'end') or setEditableCaret(el, 10, 'end');
- if (typeof end !== 'undefined' && end !== start) {
- result = findNode(end);
- }
- if (result.node) {
- e = result.position; // Adjust if end > content length
- range.setEnd(result.node, result.offset);
- text = s === e ? '' : result.text.substring(s, e);
- }
- sel.removeAllRanges();
- sel.addRange(range);
- }
- return {
- start: s,
- end: e,
- text: text
- };
- };
- $keyboard.replaceContent = function (el, param) {
- el = $(el)[0];
- var node, i, str,
- type = typeof param,
- caret = $keyboard.getEditableCaret(el).start,
- charIndex = 0,
- nodeStack = [el];
- while ((node = nodeStack.pop())) {
- if ($keyboard.isTextNode(node)) {
- if (type === 'function') {
- if (caret >= charIndex && caret <= charIndex + node.length) {
- node.textContent = param(node.textContent);
- }
- } else if (type === 'string') {
- // maybe not the best method, but it works for simple changes
- str = param.substring(charIndex, charIndex + node.length);
- if (str !== node.textContent) {
- node.textContent = str;
- }
- }
- charIndex += node.length;
- } else if (node && node.childNodes) {
- i = node.childNodes.length;
- while (i--) {
- nodeStack.push(node.childNodes[i]);
- }
- }
- }
- i = $keyboard.getEditableCaret(el);
- $keyboard.setEditableCaret(el, i.start, i.start);
- };
- $.fn.keyboard = function (options) {
- return this.each(function () {
- if (!$(this).data('keyboard')) {
- /*jshint nonew:false */
- (new $.keyboard(this, options));
- }
- });
- };
- $.fn.getkeyboard = function () {
- return this.data('keyboard');
- };
- /* Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
- * Licensed under the MIT License:
- * http://www.opensource.org/licenses/mit-license.php
- * Highly modified from the original
- */
- $.fn.caret = function (start, end, noFocus) {
- if (
- typeof this[0] === 'undefined' ||
- this.is(':hidden') ||
- this.css('visibility') === 'hidden' ||
- !/(INPUT|TEXTAREA)/.test(this[0].nodeName)
- ) {
- return this;
- }
- var selRange, range, stored_range, txt, val,
- $el = this,
- el = $el[0],
- selection = el.ownerDocument.selection,
- sTop = el.scrollTop,
- ss = false,
- supportCaret = true;
- try {
- ss = 'selectionStart' in el;
- } catch (err) {
- supportCaret = false;
- }
- if (supportCaret && typeof start !== 'undefined') {
- if (!/(email|number)/i.test(el.type)) {
- if (ss) {
- el.selectionStart = start;
- el.selectionEnd = end;
- } else {
- selRange = el.createTextRange();
- selRange.collapse(true);
- selRange.moveStart('character', start);
- selRange.moveEnd('character', end - start);
- selRange.select();
- }
- }
- // must be visible or IE8 crashes; IE9 in compatibility mode works fine - issue #56
- if (!noFocus && ($el.is(':visible') || $el.css('visibility') !== 'hidden')) {
- el.focus();
- }
- el.scrollTop = sTop;
- return this;
- }
- if (/(email|number)/i.test(el.type)) {
- // fix suggested by raduanastase (https://github.com/Mottie/Keyboard/issues/105#issuecomment-40456535)
- start = end = $el.val().length;
- } else if (ss) {
- start = el.selectionStart;
- end = el.selectionEnd;
- } else if (selection) {
- if (el.nodeName === 'TEXTAREA') {
- val = $el.val();
- range = selection.createRange();
- stored_range = range.duplicate();
- stored_range.moveToElementText(el);
- stored_range.setEndPoint('EndToEnd', range);
- // thanks to the awesome comments in the rangy plugin
- start = stored_range.text.replace(/\r/g, '\n').length;
- end = start + range.text.replace(/\r/g, '\n').length;
- } else {
- val = $el.val().replace(/\r/g, '\n');
- range = selection.createRange().duplicate();
- range.moveEnd('character', val.length);
- start = (range.text === '' ? val.length : val.lastIndexOf(range.text));
- range = selection.createRange().duplicate();
- range.moveStart('character', -val.length);
- end = range.text.length;
- }
- } else {
- // caret positioning not supported
- start = end = (el.value || '').length;
- }
- txt = (el.value || '');
- return {
- start: start,
- end: end,
- text: txt.substring(start, end),
- replace: function (str) {
- return txt.substring(0, start) + str + txt.substring(end, txt.length);
- }
- };
- };
- return $keyboard;
- }));
|