judy.js 115 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562
  1. /**
  2. * @file
  3. * Drupal Judy module
  4. */
  5. /*jslint browser: true, continue: true, indent: 2, newcap: true, nomen: true, plusplus: true, regexp: true, white: true, ass: true*/
  6. /*global alert: false, confirm: false, console: false*/
  7. /*global jQuery: false, Drupal: false, inspect: false, Judy: false*/
  8. (function($) {
  9. 'use strict';
  10. /**
  11. * Judy/Drupal.Judy - Javascript utility library.
  12. *
  13. * General stuff:
  14. * - methods having a selector parameter only work on a single element (not a collection); except {@link Judy.keydown}(), {@link Judy.keyup}(), {@link Judy.disable}(), {@link Judy.enable}(), {@link Judy.scrollTrap}()
  15. * - argument defaults are always falsy
  16. * - complex methods, with notable risk of user error (bad argument) or program error, log errors via Inspect (if exists)
  17. * - Judy is type sensitive, all comparisons are ===; "0" is not 0 (and btw "0" is never falsy in Javascript; only in PHP)
  18. *
  19. * Type:
  20. * • {@link Judy.typeOf}()
  21. * • {@link Judy.isContainer}() • {@link Judy.isArray}()
  22. * • {@link Judy.isNumber}() • {@link Judy.isInt}()
  23. *
  24. * Objects and Arrays:
  25. * • {@link Judy.toArray}()
  26. * • {@link Judy.objectGet}()
  27. * • {@link Judy.objectKeys}()
  28. * • {@link Judy.objectKeyOf}()
  29. * • {@link Judy.arrayIndexOf}()
  30. * • {@link Judy.objectSort}() • {@link Judy.objectKeySort}()
  31. * • {@link Judy.merge}()
  32. * • {@link Judy.containerCopy}()
  33. *
  34. * String:
  35. * • {@link Judy.stripTags}()
  36. * • {@link Judy.toLeading}()
  37. * • {@link Judy.toUpperCaseFirst}()
  38. * • {@link Judy.randName}()
  39. *
  40. * Number:
  41. * • {@link Judy.numberToFormat}() • {@link Judy.numberFromFormat}()
  42. * • {@link Judy.rand}()
  43. *
  44. * Date:
  45. * • {@link Judy.isLeapYear}()
  46. * • {@link Judy.dateISO}() • {@link Judy.dateTime}()
  47. * • {@link Judy.dateFromFormat}() • {@link Judy.dateToFormat}()
  48. * • {@link Judy.timeFormat}()
  49. *
  50. * Form fields:
  51. * • {@link Judy.fieldValue}()
  52. * • {@link Judy.isField}()
  53. * • {@link Judy.fieldType}()
  54. * • {@link Judy.disable}() • {@link Judy.enable}()
  55. *
  56. * Style:
  57. * • {@link Judy.innerWidth}() • {@link Judy.innerHeight}() • {@link Judy.outerWidth}() • {@link Judy.outerHeight}()
  58. * • {@link Judy.scrollTrap}() • {@link Judy.scrollTo}()
  59. *
  60. * DOM:
  61. * • {@link Judy.ancestor}()
  62. *
  63. * Event:
  64. * • {@link Judy.keydown}() • {@link Judy.keyup}()
  65. * • {@link Judy.ajaxcomplete}() • {@link Judy.ajaxcomplete_off}()
  66. *
  67. * UI:
  68. * • {@link Judy.overlay}()
  69. * • {@link Judy.dialog}()
  70. *
  71. * Miscellaneous:
  72. * • {@link Judy.focus}()
  73. * • {@link Judy.timer}()
  74. * • {@link Judy.browserIE}
  75. * • {@link Judy.yduJ} • {@link Judy.yduj}
  76. *
  77. * @constructor
  78. * @namespace
  79. * @name Judy
  80. * @singleton
  81. * @requires jQuery
  82. * @param {jQuery} $
  83. */
  84. var Judy = function($) {
  85. /**
  86. * @ignore
  87. * @private
  88. * @type {State}
  89. */
  90. var self = this,
  91. _name = "Judy",
  92. _nonObj = ["window","document","document.documentElement","element","image","textNode","attributeNode","otherNode","event","date","regexp","jquery"],
  93. _uaIe = 0,
  94. _dateFrmt, _dateTz,
  95. _nonInputFlds = ["textarea", "select"],
  96. _dataName,
  97. _dialEvts = ["beforeClose", "create", "open", "focus", "dragStart", "drag", "dragStop", "resizeStart", "resize", "resizeStop", "close"],
  98. _dialOpts = [
  99. "appendTo", "autoOpen", "buttons", "closeOnEscape", "closeText", "dialogClass", "draggable", "height", "hide",
  100. "maxHeight", "maxWidth", "minHeight", "minWidth", "modal", "position", "resizable", "show", "title", "width"
  101. ],
  102. _dialMthds = ["close", "destroy", "isOpen", "moveToTop", "open", "option", "widget"],
  103. _dialogs = [],
  104. _acInit, _acLstnrs = {}, _acFltrs = [
  105. // These may tear down the browser.
  106. { '!url': /\/inspect\/ajax/ },
  107. { '!url': /\/log_filter\/ajax/ }
  108. ],
  109. _checklist = "checkboxes", _radio = "radios", // Drupal Form API calls a checkbox list 'checkboxes' and a radio list 'radios'
  110. _jqOvrly, _ovrlyRsz, // Overlay.
  111. /**
  112. * Error handler, give it an error or a variable.
  113. *
  114. * Does nothing if no Inspect module, or if Inspect's 'Enable frontend javascript variable/trace inspector' permission is missing for current user.
  115. *
  116. * @see inspect.errorHandler
  117. * @ignore
  118. * @private
  119. * @param {Error} [error]
  120. * @param {mixed} [variable]
  121. * @param {object|integer|boolean|string} [options]
  122. * @return {void}
  123. */
  124. _errorHandler = function(error, variable, options) {
  125. var u = options, o = {}, t;
  126. // Do nothing, if inspect is the 'no action' type.
  127. if(typeof window.inspect === "function" && inspect.tcepsnI) {
  128. if(typeof inspect.errorHandler === "function") {
  129. if(u) {
  130. if((t = typeof u) === "string") {
  131. o.message = u;
  132. o.wrappers = 1; // This function wraps Inspect.errorHandler().
  133. }
  134. else if(t === "object") {
  135. o = u;
  136. o.wrappers = !u.wrappers ? 1 : (u.wrappers + 1);
  137. }
  138. // Otherwise: ignore; use object argument for options if other properties are needed.
  139. }
  140. o.category = "Judy";
  141. inspect.errorHandler(error, variable, o);
  142. }
  143. else {
  144. inspect.console("Please update Inspect.");
  145. }
  146. }
  147. },
  148. /**
  149. * Resolve element(s).
  150. *
  151. * Logs error if failing to establish such element, unless noError.
  152. *
  153. * @ignore
  154. * @param {boolean} list
  155. * - false: return first element
  156. * - true: return list of elements
  157. * @param {string|element|array|jquery} u
  158. * @param {string|object|falsy} [cntxt]
  159. * - like jQuery() context argument
  160. * @param {string} [mthd]
  161. * - method name
  162. * @param {boolean} [noError]
  163. * - do not log error
  164. * @return {element|undefined}
  165. */
  166. _elm = function(list, u, cntxt, mthd, noError) {
  167. var li = !list ? 0 : undefined, t, s = u, jq, le, i, f;
  168. if(u) {
  169. if((t = typeof u) === "object") {
  170. // Element?
  171. if(u === window || u === document || u.getAttributeNode) {
  172. return !list ? u : [u];
  173. }
  174. // jquery object
  175. if(typeof u.jquery === "string") {
  176. if(u.length) {
  177. if(!cntxt) {
  178. return u.get(li);
  179. }
  180. if((jq = $(u, cntxt)).length) {
  181. return jq.get(li);
  182. }
  183. }
  184. s = u.selector;
  185. }
  186. else if(self.isArray(u) && (le = u.length)) {
  187. for(i = 0; i < le; i++) {
  188. if(!(u[i] === window || u[i] === document || u[i].getAttributeNode)) {
  189. f = true;
  190. break;
  191. }
  192. else if(!i && !list) {
  193. return u[0];
  194. }
  195. }
  196. if(!f) {
  197. return u;
  198. }
  199. }
  200. }
  201. else if(t === "string" && (jq = $(u)).length) {
  202. return jq.get(li);
  203. }
  204. }
  205. if(!noError) {
  206. try {
  207. throw new Error("selector[" + s + "], type[" + self.typeOf(u) + "], doesnt resolve to element");
  208. }
  209. catch(er) {
  210. _errorHandler(er, null, _name + "." + mthd + "()");
  211. }
  212. }
  213. return undefined;
  214. },
  215. /**
  216. * object and button elements arent supported, but input button/submit/reset is.
  217. *
  218. * @ignore
  219. * @param {element} r
  220. * @param {boolean} [b]
  221. * - allow button (input type:button|submit|reset)
  222. * @return {string}
  223. * - empty: not a field
  224. */
  225. _fieldType = function(r, b) {
  226. var t = r.tagName.toLowerCase();
  227. if(t === "input") {
  228. if((t = r.getAttribute("type"))) { // Secure against getAttribute returning undefined or other non-string falsy (unlikely, but anyway).
  229. switch(t) {
  230. case "button":
  231. case "submit":
  232. case "reset":
  233. return !b ? "" : t;
  234. case "radio":
  235. return _radio;
  236. case "checkbox":
  237. return self.ancestor(r, "div.form-checkboxes", 3) ? _checklist : "checkbox";
  238. default:
  239. return t;
  240. }
  241. }
  242. return "";
  243. }
  244. return self.arrayIndexOf(_nonInputFlds, t) > -1 ? t : "";
  245. },
  246. /**
  247. * Handles all field types (also checkbox, checkboxes/check list, and radios), and adds/removes css class 'form-button-disabled' to button.
  248. *
  249. * @ignore
  250. * @param {boolean} nbl
  251. * @param {string|element|array|jquery} slctr
  252. * - works on multiple elements
  253. * @param {element|string|falsy} [cntxt]
  254. * - default: document is context
  255. * @param {string} [ttl]
  256. * - update the element's (hover) title attribute
  257. * @return {void}
  258. */
  259. _disable = function(nbl, slctr, cntxt, ttl) {
  260. var a = _elm(true, slctr, cntxt, !nbl ? "disable" : "enable"), le, i, r;
  261. if(a) {
  262. le = a.length;
  263. for(i = 0; i < le; i++) {
  264. (r = a[i]).disabled = !nbl ? "disabled" : false;
  265. if(typeof ttl === "string") {
  266. r.setAttribute("title", ttl);
  267. }
  268. switch(_fieldType(r, true)) { // Allow button.
  269. case "checkbox":
  270. $(r).unbind("click." + _name + ".disabled");
  271. if(!nbl) {
  272. $(r).bind("click." + _name + ".disabled", function() {
  273. return false;
  274. });
  275. }
  276. break;
  277. case _checklist:
  278. $("input[type='checkbox']", r).each(function(){
  279. $(r).unbind("click." + _name + ".disabled");
  280. if(!nbl) {
  281. $(this).bind("click." + _name + ".disabled", function() {
  282. return false;
  283. });
  284. }
  285. });
  286. break;
  287. case _radio:
  288. $("input[name='" + r.getAttribute("name") + "']", cntxt).each(function(){
  289. this.disabled = !nbl ? "disabled" : false;
  290. });
  291. break;
  292. case "button":
  293. case "submit":
  294. case "reset":
  295. $(r)[ !nbl ? "addClass" : "removeClass" ]("form-button-disabled");
  296. break;
  297. }
  298. }
  299. }
  300. },
  301. /**
  302. * Get/set value checkbox field.
  303. *
  304. * Getting means getting the value attribute (if on), or empty string if off.
  305. *
  306. * Setting only means setting checked or not - does not change the value attribute of the field.
  307. *
  308. * @ignore
  309. * @function
  310. * @name Judy._valCheckbox
  311. * @param {element} r
  312. * @param {boolean|undefined} [val]
  313. * - default: undefined (~ get value, dont set)
  314. * - truthy: check it
  315. * @return {string|integer|undefined}
  316. * - empty string if not checked
  317. * - true if setting succeeded
  318. * - undefined if no such field exist
  319. */
  320. _valCheckbox = function(r, val) {
  321. // get
  322. if(val === undefined) {
  323. return r.checked ? r.value : "";
  324. }
  325. // set
  326. r.checked = (val ? "checked" : false);
  327. return true;
  328. },
  329. /**
  330. * Get/set checked value of radio list field.
  331. *
  332. * If arg val is empty string: unchecks all radio options.
  333. *
  334. * If the radio element has no name attribute, then works like a checkbox; returns the value of that element if checked (ignores other radio elements).
  335. *
  336. * @ignore
  337. * @function
  338. * @name Judy._valRadio
  339. * @param {element} r
  340. * @param {element|string|falsy} [context]
  341. * - default: document is context
  342. * @param {string|integer|undefined} [val]
  343. * - default: undefined (~ get value, dont set)
  344. * @return {string|boolean|undefined}
  345. * - empty string (getting only) if none checked
  346. * - true if setting and that value is an option
  347. * - false if setting and that value isnt an option
  348. * - undefined if no such input field exist
  349. */
  350. _valRadio = function(r, context, val) {
  351. var nm = r.getAttribute("name"), a, le, i, v;
  352. if(!nm) { // No name works like a checkbox.
  353. return _valCheckbox(r, val);
  354. }
  355. // get ------------------------------------
  356. if(val === undefined) {
  357. return (v = $("input[name='" + nm + "']:checked", context).val()) !== undefined ? v : "";
  358. }
  359. // set ------------------------------------
  360. if( (le = (a = $().get("input[name='" + nm + "']", context)).length) ) {
  361. // If real empty value, and not "0".
  362. if((v = "" + val) === "") {
  363. for(i = 0; i < le; i++) {
  364. a[i].checked = false; // we dont care which was checked, just uncheck all
  365. }
  366. return true;
  367. }
  368. // non-empty value, check the one that has that particular value (if exists)
  369. for(i = 0; i < le; i++) {
  370. if(a[i].value === v) {
  371. a[i].checked = "checked";
  372. return true;
  373. }
  374. }
  375. return false;
  376. }
  377. return undefined; // Shouldnt be possible, .fieldValue() should catch non-existing; but anyway.
  378. },
  379. /**
  380. * Get/set selected value(s) of select field.
  381. *
  382. * Supports multiple.
  383. *
  384. * When getting:
  385. * - option value "_none" translates to ""
  386. * - multiple select returns array if any option selected, otherwise returns ""
  387. *
  388. * When setting, arg val is:
  389. * - empty string or array, or [""]: un-selects all, no matter if the select is multiple or not
  390. * - array, and select is non-multiple: uses only the first bucket of the array
  391. * - non-empty string, and select is multiple: uses val as bucket in array having a single bucket
  392. *
  393. * Arg val will be stringified before comparison with option values (multiple: the buckets are stringified, in a copy of arg val).
  394. *
  395. * @ignore
  396. * @function
  397. * @name Judy._valSelect
  398. * @param {element} r
  399. * @param {array|string|mixed|undefined} [val]
  400. * - default: undefined (~ get value, dont set)
  401. * @return {string|array|boolean|undefined}
  402. * - array if getting multiple select, unless none (then empty string)
  403. * - empty string (getting only) if none selected
  404. * - true if clearing all options
  405. * - integer if selecting some option(s); zero if none of this/those options exist
  406. * - undefined if no such select field exist
  407. */
  408. _valSelect = function(r, val) {
  409. var multi, ndx = -1, rOpts, nOpts, rOpt, nVals, i, vals = [], v, set = 0;
  410. // get ------------------------------------
  411. if(val === undefined &&
  412. ((ndx = r.selectedIndex) === undefined || ndx < 0)) {
  413. return "";
  414. }
  415. // getting and setting
  416. multi = r.multiple;
  417. nOpts = (rOpts = $("option", r).get()).length;
  418. // get ----------------
  419. // Translating selectedIndex to actual option is weird/error prone, so we use jQuery list of options instead.
  420. if(val === undefined) {
  421. if(!multi) {
  422. return (v = rOpts[ndx].value) !== "_none" ? v : "";
  423. }
  424. // multi
  425. for(i = 0; i < nOpts; i++) {
  426. if((rOpt = rOpts[i]).selected &&
  427. (v = rOpt.value) !== "" && v !== "_none") {
  428. vals.push(v);
  429. }
  430. }
  431. return vals.length ? vals : "";
  432. }
  433. // set ------------------------------------
  434. // start by clearing all
  435. // r.selectedIndex = -1; ...is seriously unhealthy, may effectively ruin the select.
  436. for(i = 0; i < nOpts; i++) {
  437. rOpts[i].selected = false;
  438. }
  439. if(val === "" || val === "_none") {
  440. return true; // all done
  441. }
  442. // secure array
  443. if(!self.isArray(val)) {
  444. v = ["" + val];
  445. }
  446. else {
  447. if(!(nVals = val.length) ||
  448. (nVals === 1 && (val[0] === "" || val[0] === "_none"))
  449. ) {
  450. return true; // all done
  451. }
  452. v = val.concat();
  453. for(i = 0; i < nVals; i++) { // stringify for comparison
  454. v[i] = "" + v[i];
  455. }
  456. }
  457. for(i = 0; i < nOpts; i++) {
  458. if( ( (rOpt = rOpts[i]).selected =
  459. self.arrayIndexOf(v, rOpt.value) > -1 ? "selected" : false)
  460. ) { // set? and count
  461. ++set;
  462. if(!multi) {
  463. return 1;
  464. }
  465. }
  466. }
  467. return set;
  468. },
  469. /**
  470. * Get/set selected values of checkbox list field (Drupal special).
  471. *
  472. * When getting:
  473. * - returns array if any option selected, otherwise returns ""
  474. *
  475. * When setting, arg val is:
  476. * - empty string or array, or [""]: un-selects all
  477. * - non-empty string or not array: sets that single value (if stringified value equals one of the options available)
  478. *
  479. * Setting effectively means 1. resetting the whole list, and then 2. selecting the value(s) passed by the val argument.
  480. * If you dont want to reset, but only make sure to select some value(s) - see the example.
  481. *
  482. * Arg val will be stringified before comparison with option values (array: the buckets are stringified, in a copy of arg val).
  483. *
  484. * Warning - empty value:
  485. * - a check list should ideally not have an empty value (neither "" nor "_none"); instead, a check list is empty when no option is selected
  486. * - if you really want an emptyish value, make it "_none" ("_none" is for this method a normal value, whereas "" means none selected at all)
  487. *
  488. * @example // Checking some option, but not resetting the whole list:
  489. var values = Judy.fieldValue("some_field[und][whatever]"), checkOption = "some_option";
  490. if(values) { // not simply "" ~ empty
  491. values.push(checkOption); // no matter if "some_option" is already checked, no prop if an option appears more than once when setting
  492. }
  493. else {
  494. values = checkOption;
  495. }
  496. Judy.fieldValue("some_field[und][whatever]", null, values);
  497. *
  498. * @ignore
  499. * @function
  500. * @name Judy._valChecklist
  501. * @param {element} r
  502. * @param {array|string|mixed|undefined} [val]
  503. * - default: undefined (~ get value, dont set)
  504. * - empty string or array or [""] translates to clear all options
  505. * - non-empty string or not array: sets that single value (stringified)
  506. * @return {array|string|integer|boolean|undefined}
  507. * - array if getting and any option is selected
  508. * - empty string if getting and no option selected
  509. * - true if clearing all options
  510. * - integer if selecting some option(s); zero if none of this/those options exist
  511. * - undefined if not a checklist field
  512. */
  513. _valChecklist = function(r, val) { // NB: hidden 5th argument used internally
  514. var par, rOpts, nOpts, rOpt, nVals, i, v = [], set = 0;
  515. if((par = self.ancestor(r, "div.form-checkboxes", 3))) {
  516. nOpts = (rOpts = $("input[type='checkbox']", par).get()).length;
  517. // get ------------------------------------
  518. if(val === undefined) {
  519. for(i = 0; i < nOpts; i++) {
  520. if((rOpt = rOpts[i]).checked) {
  521. v.push(rOpt.value);
  522. }
  523. }
  524. return v.length ? v : "";
  525. }
  526. // set ------------------------------------
  527. // let empty be undefined, otherwise secure array
  528. v = !self.isArray(val) ? (
  529. val === "" ? undefined : [val]
  530. ) : (
  531. !(nVals = val.length) || (nVals === 1 && val[0] === "") ? undefined :
  532. val.concat() // do copy array, because we stringify values
  533. );
  534. if(v === undefined) { // unset all
  535. for(i = 0; i < nOpts; i++) {
  536. rOpts[i].checked = false;
  537. }
  538. return true;
  539. }
  540. for(i = 0; i < nVals; i++) { // stringify all buckets, because field values are always strings (~> comparison)
  541. v[i] = "" + v[i];
  542. }
  543. for(i = 0; i < nOpts; i++) {
  544. if( ( (rOpt = rOpts[i]).checked =
  545. self.arrayIndexOf(v, rOpt.value) > -1 ? "checked" : false)
  546. ) { // set? and count
  547. ++set;
  548. }
  549. }
  550. return set;
  551. }
  552. return undefined; // IDE (wrongly) complains otherwise
  553. },
  554. /**
  555. * @ignore
  556. * @param {object} o
  557. * @param {array} fltr
  558. * @return {boolean}
  559. */
  560. _filter = function(o, fltr) {
  561. var le = fltr.length, i, k, x, not, v;
  562. for (i = 0; i < le; i++) {
  563. for (k in fltr[i]) {
  564. v = null; // Clear reference (loop).
  565. if (fltr[i].hasOwnProperty(k)) {
  566. x = k;
  567. if ((not = x.charAt(0) === '!')) {
  568. x = x.substr(1);
  569. }
  570. if (o.hasOwnProperty(x)) {
  571. if ((v = fltr[i][k]) && v instanceof RegExp) {
  572. if (typeof o[x] === 'string') {
  573. if (v.test(o[x])) {
  574. if (not) {
  575. return false;
  576. }
  577. }
  578. else if (!not) {
  579. return false;
  580. }
  581. }
  582. }
  583. else if (o[x] === v) {
  584. if (not) {
  585. return false;
  586. }
  587. }
  588. else if (!not) {
  589. return false;
  590. }
  591. }
  592. }
  593. }
  594. }
  595. return true;
  596. },
  597. /**
  598. * ajaxcomplete.off() helper.
  599. *
  600. * @ignore
  601. * @param {string} u
  602. * @param {string} s
  603. * @param {string} [nm]
  604. * @param {function} [h]
  605. * @return {void}
  606. */
  607. _acOff = function(u, s, nm, h) {
  608. var le, i, rm = [], n, sbtrt;
  609. if (_acLstnrs[u] && _acLstnrs.hasOwnProperty(u)) {
  610. le = _acLstnrs[u].length;
  611. for (i = 0; i < le; i++) {
  612. if ((_acLstnrs[u][i][0] === s || (nm && _acLstnrs[u][i][1] === nm)) &&
  613. (!h || _acLstnrs[u][i][2] === h)
  614. ) {
  615. rm.push(i);
  616. }
  617. }
  618. if ((n = rm.length)) {
  619. if (n === le) {
  620. delete _acLstnrs[u];
  621. }
  622. else {
  623. sbtrt = 0;
  624. for (i = 0; i < n; i++) {
  625. _acLstnrs[u].splice(rm[i] - sbtrt, 1);
  626. ++sbtrt;
  627. }
  628. }
  629. }
  630. }
  631. },
  632. /** Convert human readable keydown_keystroke sequence to _NNNNNNN.
  633. * ctr, meta and cmd count as a single key, because it makes sense across OSes (Windows vs. Apple), and because ctr also fires meta on Windows.
  634. * @ignore
  635. * @private
  636. * @memberOf Judy
  637. * @throws {Error}
  638. * - (UNCAUGHT) if empty or bad sequence (missing plain key, or containing unsupported char) etc.
  639. * - "_1001055", false on error
  640. * @param {string} keystrokes
  641. * - like: "ctr_shift_7" | "7"
  642. * @return {string|false}
  643. */
  644. _keyMask = function(keystrokes) {
  645. var aK = keystrokes.toUpperCase().split(/_/), nK = aK.length, k = 0, ky, cK, i;
  646. for(i = 0; i < nK; i++) {
  647. switch((ky = aK[i])) {
  648. // modifiers ------------------------------------
  649. case "CTR": case "CTRL":
  650. case "CMD": case "META":k += 100000;break;
  651. case "ALT":k += 10000;break;
  652. case "SHIFT":k += 1000;break;
  653. // plain key ------------------------------------
  654. case "ENTER": case "RETURN":k += 13;break;
  655. case "ESC": case "ESCAPE":k += 27;break;
  656. case "TAB":k += 9;break;
  657. case "SPACE":k += 32;break;
  658. case "BACKSPACE":k += 8;break;
  659. case "INS": case "INSERT":k += 45;break;
  660. case "DEL": case "DELETE":k += 46;break;
  661. case "HOME":k += 36;break;
  662. case "END":k += 35;break;
  663. case "PGUP": case "PAGEUP":k += 33;break;
  664. case "PGDN": case "PAGEDOWN":k += 34;break;
  665. case "PAUSE": case "BREAK":k += 19;break;
  666. case "STAR":k += 106;break;
  667. case "-": case "MINUS": case "HYPHEN":k += 109;break;
  668. case "+": case "PLUS":k += 107;break;
  669. case "LEFT":k += 37;break;
  670. case "UP":k += 38;break;
  671. case "RIGHT":k += 39;break;
  672. case "DOWN":k += 40;break;
  673. case "F1":k += 112;break;
  674. case "F2":k += 113;break;
  675. case "F3":k += 114;break;
  676. case "F4":k += 115;break;
  677. case "F5":k += 116;break;
  678. case "F6":k += 117;break;
  679. case "F7":k += 118;break;
  680. case "F8":k += 119;break;
  681. case "F9":k += 120;break;
  682. case "F10":k += 121;break;
  683. case "F11":k += 122;break;
  684. case "F12":k += 123;break;
  685. default:
  686. cK = ky.charCodeAt(0);
  687. if(cK >= 96 && cK <= 105) { // numpad numbers ~> numbers
  688. k += (cK - 48);
  689. }
  690. else if((cK >= 65 && cK <= 90) || (cK >= 48 && cK <= 57)) {
  691. k += cK;
  692. }
  693. else { // skip anything else
  694. throw new Error("unsupported char["+ky+"] in keystrokes["+keystrokes+"]");
  695. }
  696. }
  697. }
  698. if(k && k % 1000 > 0) {
  699. return "_" + k;
  700. }
  701. throw new Error("keystrokes["+keystrokes+"] " + (!k ? "evaluates to nothing" : "all modifiers, no plain keys"));
  702. },
  703. /** Convert keystrokes of an event to key mask.
  704. * @ignore
  705. * @private
  706. * @memberOf Judy
  707. * @throws {Error}
  708. * - (UNCAUGHT) if empty or bad sequence (missing plain key, or containing unsupported char) etc.
  709. * @param {event} e
  710. * @return {integer}
  711. */
  712. _keystrokes = function(e) {
  713. var k = 0, kC;
  714. // all key events are executed, no matter what keystrokes, so here we have to check if the keystrokes
  715. // this method got as argument are the same as the ones pressed by the user
  716. if(e.ctrlKey || e.metaKey) { // command key, not IE
  717. k += 100000;
  718. }
  719. if(e.altKey) {
  720. k += 10000;
  721. }
  722. if(e.shiftKey) {
  723. k += 1000;
  724. }
  725. if((kC = e.keyCode)) {
  726. switch(kC) { // when more keys evaluates to same, they have to be translated to common
  727. case 61: // hyphen ~> numpad minus
  728. k += 107;
  729. break;
  730. case 189: // plus ~> numpad plus
  731. k += 109;
  732. break;
  733. default:
  734. if(kC >= 96 && kC <= 105) { // numpad numbers ~> numbers
  735. k += (kC - 48);
  736. }
  737. else {
  738. k += kC;
  739. }
  740. }
  741. }
  742. return k;
  743. },
  744. /* Un-format keystroke string, for human readable output.
  745. * No error checking, do or die.
  746. * @ignore
  747. * @private
  748. * @memberOf Judy
  749. * @param {string} keyMask
  750. * - like "_1001055"
  751. * @return {string} like "ctr_shift_7"
  752. *
  753. this.keystrokes = function(keyMask) {
  754. if(keyMask.charAt(0) !== "_") { // if not starting with underscore it is not formatted
  755. return keyMask;
  756. }
  757. var k = parseInt(keyMask.substr(1), 10), ks = "";
  758. if(k > 100000) {
  759. ks += "ctr_";
  760. k -= 100000;
  761. }
  762. if(k > 10000) {
  763. ks += "alt_";
  764. k -= 10000;
  765. }
  766. if(k > 1000) {
  767. ks += "shift_";
  768. k -= 1000;
  769. }
  770. switch(k) {
  771. case 13:ks += "enter";break;
  772. case 27:ks += "escape";break;
  773. case 9:ks += "tab";break;
  774. case 32:ks += "space";break;
  775. case 8:ks += "backspace";break;
  776. case 45:ks += "insert";break;
  777. case 46:ks += "delete";break;
  778. case 36:ks += "home";break;
  779. case 35:ks += "end";break;
  780. case 33:ks += "pageup";break;
  781. case 34:ks += "pagedown";break;
  782. case 19:ks += "pause";break;
  783. case 106:ks += "star";break;
  784. case 109:ks += "minus";break;
  785. case 107:ks += "plus";break;
  786. case 37:ks += "left";break;
  787. case 38:ks += "up";break;
  788. case 39:ks += "right";break;
  789. case 40:ks += "down";break;
  790. default:
  791. if(k >= 112 && k <= 123) {
  792. ks += "f" + (k - 111);
  793. } // f keys
  794. else {
  795. ks += String.fromCharCode(k).toLowerCase();
  796. }
  797. }
  798. return ks;
  799. },*/
  800. /**
  801. * @ignore
  802. * @param {string} et
  803. * - keydown|keyup
  804. * @param {array} as
  805. * - caller arguments
  806. * @return {boolean}
  807. */
  808. _bindKeys = function(et, as) {
  809. var jq = $(as[0]), jqMthd = typeof jq.on === "function" ? "on" : "bind",
  810. nAs = as.length, qualifiers = "", nQs, iQ, q, nm, kms = {}, km,
  811. rs = jq.get(), nRs = jq.length, r,
  812. hndlr, dat, pdef = false, i, jq1, d, e, j, le, kyHndlrs, f;
  813. if(nAs < 3) {
  814. throw new Error("requires at least 3 args");
  815. }
  816. if(!nRs) {
  817. throw new Error("No element like selector[" + as[0] + "], type[" + self.typeOf(as[0]) + "]");
  818. }
  819. // Find handler + data (if any) + preventDefault (if any).
  820. for(i = 1; i < 5; i++) {
  821. switch(typeof as[i]) {
  822. case "string":
  823. qualifiers = as[1];
  824. break;
  825. case "function":
  826. hndlr = as[i];
  827. break;
  828. case "object":
  829. dat = as[i];
  830. break;
  831. case "boolean":
  832. pdef = as[i];
  833. break;
  834. }
  835. }
  836. if(!hndlr) {
  837. throw new Error("No handler function arg found");
  838. }
  839. // For every qualifier.
  840. nQs = (qualifiers = qualifiers.split(" ")).length;
  841. for(iQ = 0; iQ < nQs; iQ++) {
  842. // Remove keydown_|keyup_, if given qualifiers arg "keydown_qualifiers" instead of just "qualifiers".
  843. if((q = qualifiers[iQ]).indexOf("key") === 0) {
  844. q = q.replace(/^key[^_]+_(.+)$/, "$1");
  845. }
  846. // If event type not qualified; let normal jQuery.on|bind() do all work.
  847. if(!q || q === "*") {
  848. jq[jqMthd].apply(jq, !dat ? [et, hndlr] : [et, dat, hndlr]);
  849. return true;
  850. }
  851. // Extract namespace.
  852. nm = "";
  853. if(q.indexOf(".") > -1) {
  854. nm = q.replace(/^[^\.]+\.(.+)$/, "$1");
  855. q = q.replace(/^([^\.]+)\..+$/, "$1");
  856. }
  857. // Translate to keymask.
  858. km = _keyMask( // _keymask() throws error upon failure.
  859. q.replace(/[_\+]\+/, "_plus").replace(/\+/g, "_") // Support plus spacers as well as underscore spacers.
  860. );
  861. // Skip if keymask evaluates to already listed keymask (ctr_7 ~ cmd_7 ~ meta_7).
  862. if(iQ && kms[km] && kms.hasOwnProperty(km)) {
  863. continue;
  864. }
  865. kms[km] = {
  866. handler: hndlr,
  867. data: dat,
  868. namespace: nm,
  869. type: q,
  870. preventDefault: pdef
  871. }
  872. }
  873. // For every element of the jQuery object.
  874. for(i = 0; i < nRs; i++) {
  875. // check that key event isnt set on unsupported element type
  876. if((r = rs[i]) !== document.documentElement) { // propably the most usual key event element
  877. if(r === window) {
  878. if(_uaIe) { // ie
  879. throw new Error("IE key event on window illegal, do set it on document.documentElement");
  880. }
  881. }
  882. else if(!_uaIe) { // gecko and webkit; the element must be focusable.
  883. switch(r.tagName.toLowerCase()) {
  884. case "textarea": case "input":
  885. break;
  886. default:
  887. if(!r.hasAttribute("tabindex")) {
  888. throw new Error("non-IE key event on tag-type["+r.tagName+"] without tabindex not possible");
  889. }
  890. }
  891. }
  892. }
  893. // Find common keydown/keyup handler, if exists.
  894. kyHndlrs = null;
  895. if((d = (jq1 = $(r)).data("events")) && (e = d[et]) && d.hasOwnProperty(et)) {
  896. le = e.length;
  897. // For every listener to keydown|keyup.
  898. for(j = 0; j < le; j++) {
  899. if(e[j].namespace === _dataName) {
  900. kyHndlrs = e[j].handler.judy_keyMask_handlers;
  901. break;
  902. }
  903. }
  904. }
  905. // No common keydown/keyup handler; create that.
  906. if(!kyHndlrs) {
  907. f = function(evt) {
  908. var o, k, a, pd, le, i, lstnr, e;
  909. if((a = (o = f.judy_keyMask_handlers)[ k = "_" + _keystrokes(evt) ]) && o.hasOwnProperty(k) && (le = a.length)) {
  910. for(i = 0; i < le; i++) {
  911. lstnr = a[i];
  912. if(!pd && lstnr.preventDefault) {
  913. pd = true;
  914. evt.preventDefault();
  915. }
  916. evt.data = lstnr.data;
  917. evt.keystrokes = lstnr.type;
  918. lstnr.handler.apply(this, [evt]);
  919. }
  920. }
  921. };
  922. kyHndlrs = f.judy_keyMask_handlers = {};
  923. jq1[jqMthd](et + "." + _dataName, f);
  924. }
  925. // For every keymask.
  926. for(km in kms) {
  927. if(kms.hasOwnProperty(km)) {
  928. if(kyHndlrs[km] && kyHndlrs.hasOwnProperty(km)) {
  929. kyHndlrs[km].push( kms[km] );
  930. }
  931. else {
  932. kyHndlrs[km] = [
  933. kms[km]
  934. ];
  935. }
  936. }
  937. }
  938. }
  939. return undefined; // For IDE.
  940. },
  941. /**
  942. * Timezone offset, in positive milliseconds, or as a (hour) string.
  943. * Native method getTimezoneOffset() returns negative (sic!) value, in minutes - alltogether fairly useless.
  944. * @ignore
  945. * @param {Date} dt
  946. * @param {boolean} [asHourStr]
  947. * @return {integer|str} milliseconds | "+/-NN" hours
  948. */
  949. _dateTz = function(dt, asHourStr) {
  950. var z = dt.getTimezoneOffset(), zu;
  951. return !asHourStr ? (-(z * 60 * 1000)) :
  952. (z ? (((zu = z > 0) ? "-" : "+") + ((zu = ((zu ? z : z * -1) / 60)) < 10 ? "0" : "") +
  953. Math.floor(zu)) : "+00");
  954. },
  955. /**
  956. * Helper for iso-8601 formats
  957. * @ignore
  958. * @param {Date} dt
  959. * @param {boolean} d - truthy: YYYY-MM-DD
  960. * @param {boolean} t - truthy: HH:ii:ss
  961. * @param {boolean} m - truthy: mmm
  962. * @param {boolean} UTC - truthy: get in Universal Time
  963. * @param {boolean} iso - use T and Z markers
  964. * @return {string}
  965. */
  966. _dateFrmt = function(dt, d, t, m, UTC, iso) {
  967. var u, f = UTC ? "getUTC" : "get";
  968. return (d ? (
  969. dt[f+"FullYear"]() + "-" +
  970. ((u = dt[f+"Month"]() + 1) < 10 ? ("0" + u) : u) + "-" +
  971. ((u = dt[f+"Date"]()) < 10 ? ("0" + u) : u)
  972. ) : ""
  973. ) +
  974. (d && t ? (iso ? "T" : " ") : "") +
  975. (t ? (
  976. (
  977. ( (u = dt[f+"Hours"]()) < 10 ? ("0" + u) : u) + ":" +
  978. ( (u = dt[f+"Minutes"]()) < 10 ? ("0" + u) : u) + ":" +
  979. ( (u = dt[f+"Seconds"]()) < 10 ? ("0" + u) : u)
  980. ) +
  981. (m ? (
  982. (iso ? "." : " ") +
  983. ( (u = dt[f+"Milliseconds"]()) < 10 ? ("00" + u) : (u < 100 ? ("0" + u) : u) )
  984. ) : "")
  985. ) : ""
  986. ) +
  987. (!iso ? "" : (UTC ? "Z" : (_dateTz(dt, 1) + ":00")));
  988. },
  989. /**
  990. * Measures inner width or height of an element, padding subtracted (unlike jQuery's innerWidth()).
  991. *
  992. * Also usable as alternative to jQuery(window).width/height(), which may give wrong result for mobile browsers.
  993. *
  994. * @ignore
  995. * @param {string} d
  996. * - Width|Height
  997. * @param {string|element|array|jquery} slctr
  998. * - if window, document.documentElement or document.body: the method disregards other args
  999. * @param {boolean} [ignorePadding]
  1000. * - default: false (~ subtract padding, unlike jQuery)
  1001. * @return {integer|undefined}
  1002. */
  1003. _dimInner = function(d, slctr, ignorePadding) {
  1004. var u = slctr, r, dE = document.documentElement, jq, v, p;
  1005. if(u === window) {
  1006. return dE[ "client" + d ]; // clientWidth/clientHeight
  1007. }
  1008. if(u === dE || u === document.body) {
  1009. return dE[ "scroll" + d ]; // scrollWidth/scrollHeight
  1010. }
  1011. if((r = _elm(0, u, 0, "inner" + d))) {
  1012. v = r[ "client" + d ]; // clientWidth/clientHeight
  1013. if(!ignorePadding) {
  1014. if((p = (jq = $(r)).css( "padding-" + (d === "Width" ? "left" : "top") )).indexOf("px") > -1) {
  1015. v -= parseFloat(p);
  1016. }
  1017. if((p = jq.css( "padding-" + (d === "Width" ? "right" : "bottom") )).indexOf("px") > -1) {
  1018. v -= parseFloat(p);
  1019. }
  1020. v = Math.round(v);
  1021. }
  1022. return v;
  1023. }
  1024. return undefined;
  1025. },
  1026. /**
  1027. * Measures or sets effective outer width or height of an element, including padding, border and optionally margin.
  1028. *
  1029. * The width/height will be set on the element itself, in pixels.
  1030. *
  1031. * If selector is window, then window scrollbar is included.
  1032. *
  1033. * @ignore
  1034. * @param {string} d
  1035. * - Width|Height
  1036. * @param {string|element|array|jquery} slctr
  1037. * - if window, document.documentElement or document.body: the method disregards other args and simply measures
  1038. * @param {boolean} [includeMargin]
  1039. * - default: false (~ dont check margin)
  1040. * @param {integer|falsy} [set]
  1041. * - set outer width/height (including padding, and optionally also margin) to that number of pixels
  1042. * @param {boolean|integer|falsy} [max]
  1043. * - default: false (~ set width)
  1044. * - true|one: set max-width/height, not width/height
  1045. * - two: set both
  1046. * @return {integer|undefined}
  1047. */
  1048. _dimOuter = function(d, slctr, includeMargin, set, max) {
  1049. var u = slctr, r, dE = document.documentElement, jq, v;
  1050. if(u === window) {
  1051. return dE[ "inner" + d ] || dE[ "client" + d ]; // innerWidth/innerHeight includes scrollbar
  1052. }
  1053. if(u === dE || u === document.body) {
  1054. return dE[ "scroll" + d ];
  1055. }
  1056. if((r = _elm(0, u, 0, "outer" + d))) {
  1057. v = (jq = $(r))[ "outer" + d ](includeMargin); // Let jQuery do the clientWidth + border (+ margin)
  1058. if(!set || // if only measuring
  1059. set === v) { // or dimension correct
  1060. return v;
  1061. }
  1062. v = _dimInner(d, u) + (set - v);
  1063. if(!max || max === 2) {
  1064. jq.css(d.toLowerCase(), v + "px");
  1065. }
  1066. if(max) {
  1067. jq.css("max-" + d.toLowerCase(), v + "px");
  1068. }
  1069. return set;
  1070. }
  1071. return undefined;
  1072. },
  1073. /**
  1074. * Resizes the overlay to fill whole window/document; handler for window resize event.
  1075. *
  1076. * @ignore
  1077. * @return {void}
  1078. */
  1079. _ovrlyRsz = function() {
  1080. var w = window, d = document.documentElement, dW, dD;
  1081. _jqOvrly.css({
  1082. width: ((dD = self.innerWidth(d)) > (dW = self.innerWidth(w)) ? dD : dW) + "px",
  1083. height: ((dD = self.innerHeight(d)) > (dW = self.innerHeight(w)) ? dD : dW) + "px"
  1084. });
  1085. };
  1086. /**
  1087. * Use for checking if that window.Judy is actually the one we are looking for (see example).
  1088. * @example
  1089. if(typeof window.Judy === "object" && Judy.yduj) {
  1090. ...
  1091. }
  1092. * @name Judy.yduj
  1093. * @type boolean
  1094. */
  1095. this.yduj = true;
  1096. /**
  1097. * Use for checking if that window.Judy is actually the one we are looking for (see example).
  1098. * @example
  1099. if(typeof window.Judy === "object" && Judy.yduJ) {
  1100. ...
  1101. }
  1102. * @name Judy.yduJ
  1103. * @type boolean
  1104. */
  1105. this.yduJ = true;
  1106. /**
  1107. * @name Judy.version
  1108. * @type float
  1109. */
  1110. this.version = 2.1;
  1111. /**
  1112. * Is the browser Internet Explorer, and if so, the version as float.
  1113. *
  1114. * @name Judy.browserIE
  1115. * @type integer|float
  1116. * - zero if not IE
  1117. */
  1118. this.browserIE = _uaIe = (function() {
  1119. var u;
  1120. if ((u = window.navigator) && (u = u.userAgent)) {
  1121. if (/; MSIE \d{1,2}\.\d/.test(u)) {
  1122. return (u = parseFloat(u.replace(/^.+; MSIE (\d{1,2}\.\d).+/, '$1'))) ? u : 0;
  1123. }
  1124. if (/; Trident\/\d+\.\d+;/.test(u) && /; rv:\d+\.\d+[;\)]/.test(u)) {
  1125. return (u = parseFloat(u.replace(/^.+; rv:(\d+\.\d+)[;\)].+$/, '$1'))) ? u : 0;
  1126. }
  1127. }
  1128. return 0;
  1129. }());
  1130. /**
  1131. * @ignore
  1132. * @return {void}
  1133. */
  1134. this.setup = function() {
  1135. /** @ignore */
  1136. self.setup = function() {}; // Prevent second call.
  1137. _dataName = "judy_" + self.randName();
  1138. };
  1139. // Type.
  1140. /**
  1141. * All native types are reported in lowercase (like native typeof does).
  1142. *
  1143. * If given no arguments: returns "Judy".
  1144. * Types are:
  1145. * - native, typeof: object string number
  1146. * - native, corrected: function array date regexp image
  1147. * - window, document, document.documentElement (not lowercase)
  1148. * - element, checked via .getAttributeNode
  1149. * - text node: textNode
  1150. * - attribute node: attributeNode
  1151. * - event: event (native and prototyped W3C Event and native IE event)
  1152. * - jquery
  1153. * - emptyish and bad: undefined, null, NaN, infinite
  1154. * - custom or prototyped native: all classes having a typeOf() method.
  1155. *
  1156. * RegExp is an object of type regexp (not a function - gecko/webkit/chromium).
  1157. * Does not check if Date object is NaN.
  1158. *
  1159. * Is same as Inspect.typeOf().
  1160. * @function
  1161. * @name Judy.typeOf
  1162. * @param {mixed} u
  1163. * @return {string}
  1164. */
  1165. this.typeOf = function(u) {
  1166. var t = typeof u;
  1167. if(!arguments.length) {
  1168. return "Judy";
  1169. }
  1170. switch(t) {
  1171. case "boolean":
  1172. case "string":
  1173. return t;
  1174. case "number":
  1175. return isFinite(u) ? t : (isNaN(u) ? "NaN" : "infinite");
  1176. case "object":
  1177. if(u === null) {
  1178. return "null";
  1179. }
  1180. // Accessing properties of object may err for various reasons, like missing permission (Gecko).
  1181. try {
  1182. if(u.typeOf && typeof u.typeOf === "function") {
  1183. return u.typeOf();
  1184. }
  1185. else if(typeof u.length === "number" && !(u.propertyIsEnumerable("length")) && typeof u.splice === "function") {
  1186. return "array";
  1187. }
  1188. else if(u === window) {
  1189. return "window";
  1190. }
  1191. else if(u === document) {
  1192. return "document";
  1193. }
  1194. else if(u === document.documentElement) {
  1195. return "document.documentElement";
  1196. }
  1197. else if(u.getAttributeNode) { // element
  1198. // document has getElementsByTagName, but not getAttributeNode - document.documentElement has both
  1199. return u.tagName.toLowerCase === "img" ? "image" : "element";
  1200. }
  1201. else if(u.nodeType) {
  1202. switch(u.nodeType) {
  1203. case 3:return "textNode";
  1204. case 2:return "attributeNode";
  1205. }
  1206. return "otherNode";
  1207. }
  1208. else if(typeof u.stopPropagation === "function" ||
  1209. (u.cancelBubble !== undefined && typeof u.cancelBubble !== "function" &&
  1210. typeof u.boundElements === "object")) {
  1211. return "event";
  1212. }
  1213. else if(typeof u.getUTCMilliseconds === "function") {
  1214. return "date";
  1215. }
  1216. else if(typeof u.exec === "function" && typeof u.test === "function") {
  1217. return "regexp";
  1218. }
  1219. else if(u.hspace && typeof u.hspace !== "function") {
  1220. return "image";
  1221. }
  1222. else if(u.jquery && typeof u.jquery === "string" && !u.hasOwnProperty("jquery")) {
  1223. return "jquery";
  1224. }
  1225. }
  1226. catch(er) {
  1227. }
  1228. return t;
  1229. case "function":
  1230. // gecko and webkit reports RegExp as function instead of object
  1231. return (u.constructor === RegExp || (typeof u.exec === "function" && typeof u.test === "function")) ?
  1232. "regexp" : t;
  1233. }
  1234. return t;
  1235. };
  1236. /**
  1237. * Is container Object or Array (if arg orArray), and not a built-in type or jquery.
  1238. *
  1239. * Non-containers; built-in types and jquery:
  1240. * - window, document, document.documentElement, element
  1241. * - textNode, attributeNode, otherNode
  1242. * - image
  1243. * - event
  1244. * - date
  1245. * - regexp
  1246. * - jquery
  1247. *
  1248. * @function
  1249. * @name Judy.isContainer
  1250. * @param {mixed} u
  1251. * @param {boolean} [orArray]
  1252. * - allow array
  1253. * @return {string|boolean}
  1254. * - string: 'object' (any kind of non-array container) or 'array'
  1255. * - false: not a container
  1256. */
  1257. this.isContainer = function(u, orArray) {
  1258. var t;
  1259. return u && typeof u === "object" &&
  1260. (
  1261. (t = self.typeOf(u)) === "object" || (orArray && t === "array") || (
  1262. t !== "array" &&
  1263. self.arrayIndexOf(_nonObj, t) === -1 )
  1264. ) ? (!orArray || t !== "array" ? "object" : t) : false;
  1265. };
  1266. /**
  1267. * @function
  1268. * @name Judy.isArray
  1269. * @param {mixed} u
  1270. * @return {boolean}
  1271. */
  1272. this.isArray = function(u) {
  1273. // Douglas Crockford's expression:
  1274. return (u && typeof u === "object" &&
  1275. typeof u.length === "number" && !(u.propertyIsEnumerable("length")) && typeof u.splice === "function");
  1276. };
  1277. /**
  1278. * A "number" is not a number, use jQuery.isNumeric() for more lenient check.
  1279. * @function
  1280. * @name Judy.isNumber
  1281. * @param {mixed} u
  1282. * @return {boolean}
  1283. */
  1284. this.isNumber = function(u) {
  1285. return typeof u === "number" && isFinite(u);
  1286. };
  1287. /**
  1288. * @function
  1289. * @name Judy.isInt
  1290. * @param {mixed} u
  1291. * @param {boolean} [nonNegative]
  1292. * - default: false (~ allow negative integer)
  1293. * @return {boolean}
  1294. */
  1295. this.isInt = function(u, nonNegative) {
  1296. return typeof u === "number" && isFinite(u) && (u % 1 === 0) && (!nonNegative || u > -1);
  1297. };
  1298. // Containers.
  1299. /**
  1300. * Alternative to clone, when arg u is a simple Object container or array.
  1301. *
  1302. * Optionally copies child objects|arrays instead of referring them.
  1303. * Checks self-references in depth 1.
  1304. *
  1305. * No support for arguments collection in old browsers; see {@link Judy.toArray}().
  1306. * @function
  1307. * @name Judy.containerCopy
  1308. * @see Judy.toArray()
  1309. * @param {object|arr} oa
  1310. * @param {boolean} [shallow]
  1311. * - default: false (~ recursive, also child objects will be copies)
  1312. * - truthy: child objects are references
  1313. * @return {mixed}
  1314. */
  1315. this.containerCopy = function(oa, shallow) {
  1316. var t, c = {}, p, v;
  1317. if(!oa || !(t = self.isContainer(oa, true))) {
  1318. return oa;
  1319. }
  1320. if(t === "array") {
  1321. if(shallow) {
  1322. return oa.concat();
  1323. }
  1324. c = [];
  1325. }
  1326. for(p in oa) {
  1327. if(oa.hasOwnProperty(p)) {
  1328. c[p] = ((v = oa[p]) && typeof v === "object") ?
  1329. (v === oa ? c : (!shallow ? self.containerCopy(v, false) : v)) :
  1330. v;
  1331. }
  1332. }
  1333. return c;
  1334. };
  1335. /**
  1336. * Get property of simple or multidimensional object/array.
  1337. *
  1338. * Doesnt check for bad number key args; infinite, NaN.
  1339. * @example // Get value of o.some.deep[3].bucket, if exists:
  1340. Judy.objectGet(o, some, deep, 3, bucket);
  1341. * @function
  1342. * @name Judy.objectGet
  1343. * @throws Error
  1344. * - (caught) if bad arg(s): only one arg | first arg not object | a later arg not integer or non-empty string
  1345. * @param {object} o
  1346. * @param {string|integer} anyNumberOfKeys
  1347. * @return {mixed|undefined}
  1348. */
  1349. this.objectGet = function(o, anyNumberOfKeys) {
  1350. var a = arguments, le = a.length, u = o, p, i;
  1351. try {
  1352. if(!u || typeof u !== "object") {
  1353. throw new Error("arg o isnt object");
  1354. }
  1355. if(le < 2) {
  1356. throw new Error("no key arg");
  1357. }
  1358. for(i = 1; i < le; i++) {
  1359. if(i > 1 && (!u || typeof u !== "object")) {
  1360. return undefined;
  1361. }
  1362. if((!(p = a[i]) && p !== 0) || !(p = "" + p)) { // try stringing it, to make it err at right place
  1363. throw new Error("arg #"+i+"["+p+"] type[" + self.typeOf(p) + "] isnt integer or non-empty string");
  1364. }
  1365. if(u.hasOwnProperty(p)) {
  1366. u = u[p];
  1367. }
  1368. else {
  1369. return undefined;
  1370. }
  1371. }
  1372. return u;
  1373. }
  1374. catch(er) {
  1375. _errorHandler(er, null, _name + ".objectGet()");
  1376. }
  1377. return undefined;
  1378. };
  1379. /**
  1380. * Like Object.keys(), which may not be implemented by current browser (ECMAScript 5).
  1381. * @function
  1382. * @name Judy.objectKeys
  1383. * @param {object} o
  1384. * @return {array|null}
  1385. * - null if not object
  1386. */
  1387. this.objectKeys = function(o) {
  1388. var a, k;
  1389. if(!o || typeof o !== "object") {
  1390. return null;
  1391. }
  1392. if(typeof Object.keys === "function") {
  1393. return Object.keys(o);
  1394. }
  1395. a = [];
  1396. for(k in o) {
  1397. if(o.hasOwnProperty(k)) {
  1398. a.push(k);
  1399. }
  1400. }
  1401. return a;
  1402. };
  1403. /**
  1404. * Value-to-key mapper - String.indexOf() for objects.
  1405. * @function
  1406. * @name Judy.objectKeyOf
  1407. * @param {object} o
  1408. * @param {mixed} v
  1409. * @return {mixed}
  1410. * - undefined if arg v is undefined, or arg o isnt object, or arg o doesnt contain arg v value
  1411. */
  1412. this.objectKeyOf = function(o, v) {
  1413. var k;
  1414. if(v !== undefined && o || typeof o === "object") {
  1415. for(k in o) {
  1416. if(o.hasOwnProperty(k) && o[k] === v) {
  1417. return k;
  1418. }
  1419. }
  1420. }
  1421. return undefined;
  1422. }
  1423. /**
  1424. * Get copy of object, sorted by value.
  1425. *
  1426. * If two or more buckets have the same value, the last bucket will overwrite the previous.
  1427. *
  1428. * Will not sort right if a bucket is a string whose first char is DEL (ascii 127).
  1429. *
  1430. * @function
  1431. * @name Judy.objectSort
  1432. * @param {object} o
  1433. * @return {object}
  1434. */
  1435. this.objectSort = function(o) {
  1436. var a = [], oByVal = {}, os = {}, k, v, cNum = String.fromCharCode(127), le, i = 0;
  1437. if(!o || typeof o !== "object") {
  1438. return o;
  1439. }
  1440. // make object mapping value to key, and array of values
  1441. for(k in o) {
  1442. if(o.hasOwnProperty(k)) {
  1443. ++i;
  1444. // prefix DEL if number
  1445. oByVal[ (typeof (v = o[k]) !== "number" ? "" : cNum) + v ] = k;
  1446. a.push(v);
  1447. }
  1448. }
  1449. if(!i) {
  1450. return o;
  1451. }
  1452. le = i;
  1453. a.sort();
  1454. for(i = 0; i < le; i++) {
  1455. os[ oByVal[ (typeof (v = a[i]) !== "number" ? "" : cNum) + v ] ] = v;
  1456. }
  1457. return os;
  1458. };
  1459. /**
  1460. * Get copy of object, sorted by key.
  1461. * @function
  1462. * @name Judy.objectKeySort
  1463. * @param {object} o
  1464. * @return {o|null}
  1465. */
  1466. this.objectKeySort = function(o) {
  1467. var a, os = {}, le, i;
  1468. if(!(a = self.objectKeys(o)) || (le = a.length) < 2) {
  1469. return a ? o : null;
  1470. }
  1471. a.sort();
  1472. for(i = 0; i < le; i++) {
  1473. os[ a[i] ] = o[ a[i] ];
  1474. }
  1475. return os;
  1476. };
  1477. /**
  1478. * Copy object's public properties to an array.
  1479. *
  1480. * Particularly handy for function arguments.
  1481. * Arguments is a collection, not an array, and in older browsers (IE<9) it may not even have .hasOwnProperty().
  1482. * @function
  1483. * @name Judy.toArray
  1484. * @param {object} o
  1485. * @return {array|null}
  1486. * - null if arg o isnt an object
  1487. */
  1488. this.toArray = function(o) {
  1489. var a, le, i;
  1490. if(o && typeof o === "object") {
  1491. if(typeof o.hasOwnProperty === "function") { // Should catch rubbish IE<9 arguments collection.
  1492. return Array.prototype.slice.call(o);
  1493. }
  1494. a = [];
  1495. le = o.length;
  1496. for(i = 0; i < le; i++) {
  1497. a.push(o[i]);
  1498. }
  1499. return a;
  1500. }
  1501. return null;
  1502. // When IE<9 is history:
  1503. // return o && typeof o === "object" ? Array.prototype.slice.call(o) : null;
  1504. };
  1505. /**
  1506. * String.indexOf for Array.
  1507. *
  1508. * Values are === checked; i.e. type sensitive ("0" is not 0).
  1509. * And for objects and arrays - as value - requiring identity; one {} does not === equal another {} in Javascript.
  1510. *
  1511. * No argument error checking; this method has to be as fast as possible.
  1512. *
  1513. * @example // Looking for an 'inArray' method?
  1514. if (Judy.arrayIndexOf(arr, val) > -1) { ...
  1515. * @function
  1516. * @name Judy.arrayIndexOf
  1517. * @param {array} a
  1518. * @param {mixed} v
  1519. * @return {integer}
  1520. * - minus 1 if not found
  1521. */
  1522. this.arrayIndexOf = function(a, v) {
  1523. var le = a.length, i;
  1524. for(i = 0; i < le; i++) {
  1525. if(a[i] === v) {
  1526. return i;
  1527. }
  1528. }
  1529. return -1;
  1530. };
  1531. /**
  1532. * Merge two objects or two arrays recursive, let second object|array's attributes overwrite first object|array's attributes.
  1533. *
  1534. * The first arg object/array will be changed (return value is boolean), but sub objects/arrays are mostly copies (not references).
  1535. *
  1536. * Skips overriding when:
  1537. * - overwriter bucket is undefined (but exists anyways)
  1538. * - overwriter bucket is null, and original bucket isnt undefined (a concession to PHP; which has no undefined, only null)
  1539. *
  1540. * Which object types arent considered 'object': see {@link Judy.isContainer}().
  1541. *
  1542. * Max recursion depth: 10.
  1543. * @function
  1544. * @name Judy.merge
  1545. * @throws {TypeError}
  1546. * - (caught) if oa and overrider arent both object or both array
  1547. * @throws {Error}
  1548. * - (caught) if recursing deeper than 10
  1549. * @param {object|arr} oa
  1550. * @param {object|arr} oa1
  1551. * @param {integer|undefined} [isContainer]
  1552. * - falsy: dont know
  1553. * - 1: args oa and overrider are both know to be objects (and not built-in types {@link Judy.isContainer} or jQuery)
  1554. * - 2: args oa and overrider are both know to be arrays
  1555. * @return {boolean}
  1556. * - success/error; doesnt return object/array, changes arg oa
  1557. */
  1558. /*
  1559. this.merge = function(oa, oa1, isContainer, _depth) {
  1560. var tc = isContainer !== true ? isContainer : 0, // fix fairly obvious arg error
  1561. t = tc || self.isContainer(oa), t1 = tc || self.isContainer(oa1),
  1562. d = arguments[3] || 0, // depth
  1563. p, le, le1, v, v1, tSub;
  1564. try {
  1565. if(d < 10) {
  1566. if(t === 1) {
  1567. if(t1 === 1) {
  1568. for(p in oa1) {
  1569. if(oa1.hasOwnProperty(p) &&
  1570. (v1 = oa1[p]) !== undefined) { // undefined must never overwrite anything
  1571. // if original doesnt have any (or it is undefined, sic), its simple
  1572. if((v = oa[p]) === undefined || !oa.hasOwnProperty(p)) {
  1573. oa[p] = v1; // null might overwrite undefined
  1574. }
  1575. else if(v1 !== null) { // null must never overwrite anything but undefined
  1576. if(!(tSub = self.isContainer(v)) || self.isContainer(v1) !== tSub) {
  1577. oa[p] = v1;
  1578. }
  1579. else {
  1580. self.merge(v, v1, tSub, d + 1);
  1581. }
  1582. }
  1583. }
  1584. }
  1585. return true;
  1586. }
  1587. throw new TypeError("Second arg object/array isnt object, but " + self.typeOf(oa1));
  1588. }
  1589. else if(t === 2) {
  1590. if(t1 === 2) {
  1591. if((le1 = oa1.length)) { // does overwriter contain anything at all?
  1592. if(!(le = oa.length)) {
  1593. oa = oa1.concat(); // copy
  1594. }
  1595. else {
  1596. for(p = 0; p < le1; p++) {
  1597. if((v1 = oa1[p]) !== undefined) { // undefined must never overwrite anything
  1598. if(p >= le) { // if original isnt that long, append
  1599. oa.push(v1);
  1600. }
  1601. else if((v = oa[p]) === undefined) { // if original's is undefined, overwrite
  1602. oa[p] = v1;
  1603. }
  1604. else if(v1 !== null) { // null must never overwrite anything but undefined
  1605. if(!(tSub = self.isContainer(v)) || self.isContainer(v1) !== tSub) {
  1606. oa[p] = v1;
  1607. }
  1608. else {
  1609. self.merge(v, v1, tSub, d + 1);
  1610. }
  1611. }
  1612. }
  1613. }
  1614. }
  1615. }
  1616. return true;
  1617. }
  1618. throw new TypeError("Second arg object/array isnt array, but " + self.typeOf(oa1));
  1619. }
  1620. throw new TypeError("First arg object/array is " + self.typeOf(oa1));
  1621. }
  1622. throw new Error("Cant recurse > 10, circular ref?");
  1623. }
  1624. catch(er) {
  1625. _errorHandler(er, null, _name + ".merge()");
  1626. }
  1627. return false;
  1628. };
  1629. */
  1630. this.merge = function(oa, oa1, isContainer, _depth) {
  1631. var tBoth = isContainer !== true ? isContainer : 0, // fix fairly obvious arg error
  1632. t = tBoth || self.isContainer(oa, true), t1 = tBoth || self.isContainer(oa1, true),
  1633. d = _depth || 0,
  1634. p, le, le1, v, v1;
  1635. try {
  1636. if(d < 10) {
  1637. if(t && t1) {
  1638. if(t === "object") {
  1639. if(t1 === "object") { // Both object.
  1640. for(p in oa1) {
  1641. if(oa1.hasOwnProperty(p) &&
  1642. (v1 = oa1[p]) !== undefined) { // undefined must never overwrite anything
  1643. // if original doesnt have any (or it is undefined, sic), its simple
  1644. if((v = oa[p]) === undefined || !oa.hasOwnProperty(p)) {
  1645. oa[p] = v1; // null might overwrite undefined
  1646. }
  1647. else if(v1 !== null) { // null must never overwrite anything but undefined
  1648. if(!(t = self.isContainer(v, true)) || self.isContainer(v1, true) !== t) {
  1649. oa[p] = v1;
  1650. }
  1651. else {
  1652. self.merge(v, v1, t, d + 1);
  1653. }
  1654. }
  1655. }
  1656. }
  1657. return true;
  1658. }
  1659. throw new TypeError("Type mismatch, first is object type[" + self.typeOf(oa) + "], second is array");
  1660. }
  1661. else if(t1 === "array") { // Both array.
  1662. if((le1 = oa1.length)) { // does overwriter contain anything at all?
  1663. if(!(le = oa.length)) {
  1664. oa = oa1.concat(); // copy
  1665. }
  1666. else {
  1667. for(p = 0; p < le1; p++) {
  1668. if((v1 = oa1[p]) !== undefined) { // undefined must never overwrite anything
  1669. if(p >= le) { // if original isnt that long, append
  1670. oa.push(v1);
  1671. }
  1672. else if((v = oa[p]) === undefined) { // if original's is undefined, overwrite
  1673. oa[p] = v1;
  1674. }
  1675. else if(v1 !== null) { // null must never overwrite anything but undefined
  1676. if(!(t = self.isContainer(v, true)) || self.isContainer(v1, true) !== t) {
  1677. oa[p] = v1;
  1678. }
  1679. else {
  1680. self.merge(v, v1, t, d + 1);
  1681. }
  1682. }
  1683. }
  1684. }
  1685. }
  1686. }
  1687. return true;
  1688. }
  1689. throw new TypeError("Type mismatch, first is array, second is type[" + self.typeOf(oa1) + "]");
  1690. }
  1691. throw new TypeError("First arg is type[" + self.typeOf(oa) + "], second is type[" + self.typeOf(oa1) + "]");
  1692. }
  1693. throw new Error("Cant recurse > 10, circular ref?");
  1694. }
  1695. catch(er) {
  1696. _errorHandler(er, null, _name + ".merge()");
  1697. }
  1698. return false;
  1699. };
  1700. // DOM.
  1701. /**
  1702. * Get an ancestor element, of a particular type and/or having id and/or having css class(es).
  1703. *
  1704. * No support for selector name attribute.
  1705. *
  1706. * Dont look for body element as ancestor; returns when reaching body (or 100th ancestor) and doesnt check whether body matches arg selector.
  1707. *
  1708. * @function
  1709. * @name Judy.ancestor
  1710. * @param {string|element|array|jquery} selector
  1711. * - jQuery/css selector or element (not window or document.documentElement)
  1712. * @param {string} [parentSelector]
  1713. * - if falsy: returns immediate parent (except if arg element is window)
  1714. * - like jQuery() selector arg: tagName and/or id and/or css class(es), name name attribute not supported, and class(es) cant go before #id
  1715. * @param {integer} [max]
  1716. * - default: no maximum
  1717. * - positive number: dont look any further, 1 ~ parent | 2 ~ grand parent | etc.
  1718. * @return {element|undefined|false}
  1719. * - false if arg element isnt an element or window
  1720. * - undefined if no such parent, or arg element is window
  1721. * - undefined if reaches the body element, and selector doesnt suggest the the body element
  1722. */
  1723. this.ancestor = function(selector, parentSelector, max) {
  1724. var u, r = _elm(0, selector, null, "ancestor"), tt = parentSelector, lim = max && max > 0 ? (max + 1) : 101, id, aCls, tn, cls, le, i;
  1725. if(!r || r === window || r === document.documentElement) {
  1726. return undefined;
  1727. }
  1728. if(!tt || !(tt = $.trim(""+tt))) {
  1729. return r.parentNode;
  1730. }
  1731. if(tt.indexOf("#") > -1) {
  1732. u = tt.replace(/^([^\#]+)?\#([^\.]+)(\..+)?$/, "$2,$1$3").split(",");
  1733. id = u[0];
  1734. tt = u[1] || "";
  1735. }
  1736. if(tt.indexOf(".") > -1) {
  1737. aCls = tt.split(".");
  1738. tt = aCls[0];
  1739. aCls.splice(0, 1);
  1740. le = aCls.length;
  1741. }
  1742. tt = tt.toLowerCase();
  1743. while((--lim) && (r = r.parentNode)) {
  1744. if(r.nodeType !== 1 ||
  1745. tn === "body") { // check from last level (first time tn is falsy)
  1746. return undefined;
  1747. }
  1748. tn = r.tagName.toLowerCase();
  1749. if( (tt && tn !== tt) ||
  1750. (id && r.id !== id) ) {
  1751. continue;
  1752. }
  1753. if(le) {
  1754. if(!(cls = r.className).length) {
  1755. continue;
  1756. }
  1757. cls = " " + cls + " ";
  1758. u = 0;
  1759. for(i = 0; i < le; i++) {
  1760. if(cls.indexOf(aCls[i]) === -1) {
  1761. continue;
  1762. }
  1763. ++u;
  1764. }
  1765. if(u < le) {
  1766. continue;
  1767. }
  1768. }
  1769. return r;
  1770. }
  1771. return undefined;
  1772. };
  1773. // Event.
  1774. /**
  1775. * Establishes a single CSS/jQuery selector string.
  1776. *
  1777. * (string) selector:
  1778. * - doesnt check existance of such element(s), because must be usable for future elements as well
  1779. * - doesnt check validity of the CSS expression
  1780. *
  1781. * window, document, document.documentElement translate to _win_, _doc_, _docElm_.
  1782. *
  1783. * @function
  1784. * @name Judy.selector
  1785. * @param {string|element} selector
  1786. * @param {boolean} [findName]
  1787. * - look for name attribute, and return array
  1788. * @return {string|Array|null}
  1789. * - array: if findName; [ selector ] or [ selector, name ]
  1790. * - null on error
  1791. */
  1792. this.selector = function(selector, findName) {
  1793. var s = selector, f = findName, t = typeof s, x, v, tg;
  1794. try {
  1795. if (!s) {
  1796. throw new Error('Falsy selector, type[' + t + ']');
  1797. }
  1798. if (t === 'string') {
  1799. // Test name attribute.
  1800. return !f ? s : (s.indexOf('[name=') === -1 ? [s] : [s, s.replace(/^.*\[name=['\"]([^'\"]+)['\"]\].*$/, '$1') ]);
  1801. }
  1802. else if (t === 'object') {
  1803. if ($.isWindow(s)) {
  1804. x = '_win_';
  1805. }
  1806. else if (s === document) {
  1807. x = '_doc_';
  1808. }
  1809. else if (s === document.documentElement) {
  1810. x = '_docElm_';
  1811. }
  1812. if (x) {
  1813. return !f ? x : [x];
  1814. }
  1815. if (typeof s.getAttributeNode !== 'function' || typeof s.getAttribute !== 'function') {
  1816. throw new Error('Selector, type[' + t + '], isnt non-empty string|element');
  1817. }
  1818. tg = s.tagName.toLowerCase();
  1819. if ((v = s.getAttribute('name'))) {
  1820. x = tg + '[name="' + v + '"]';
  1821. return !f ? x : [ x, v ];
  1822. }
  1823. if ((v = s.id)) {
  1824. x = '#' + v;
  1825. }
  1826. else if ((v = s.className)) {
  1827. x = tg + '.' + v.replace(/ +/g, '.');
  1828. }
  1829. else if ((v = s.getAttribute('type'))) {
  1830. x = tg + '[type="' + v + '"]';
  1831. }
  1832. else {
  1833. x = tg;
  1834. }
  1835. return !f ? x : [x];
  1836. }
  1837. else {
  1838. throw new Error('Selector, type[' + t + '], isnt non-empty string|element');
  1839. }
  1840. }
  1841. catch (er) {
  1842. _errorHandler(er, null, _name + '.selector()')
  1843. }
  1844. return null;
  1845. };
  1846. /**
  1847. * Like jQuery().delegate and .on() the listener will apply now and in the future, no matter if such element(s) exist when calling this method.
  1848. *
  1849. * @example
  1850. Judy.ajaxcomplete(slctr, '/some/url', oData, fHandler, oFilter);
  1851. Judy.ajaxcomplete(slctr, '/some/url', oData, fHandler);
  1852. Judy.ajaxcomplete(slctr, '/some/url', fHandler, oFilter);
  1853. Judy.ajaxcomplete(slctr, '/some/url', fHandler);
  1854. * @function
  1855. * @name Judy.ajaxcomplete
  1856. * @param {string|element|array|jquery} selector
  1857. * @param {string} url
  1858. * - '*' means all responses
  1859. * - use '/system/ajax' for Drupal Form API AJAX
  1860. * - protocol and domain gets stripped off, and full path isnt necessary (is being matched against start of any AJAX url)
  1861. * @param {object} [data]
  1862. * - or (function) handler
  1863. * @param {function} [handler]
  1864. * - or (object) filter
  1865. * @param {object|array} [filter]
  1866. * - object keying properties of ajax settings object ('!key's mean exclude), values may be simple variables and regexes
  1867. * - or an array of such
  1868. * @return {void}
  1869. */
  1870. this.ajaxcomplete = function(selector, url, data, handler, filter) {
  1871. var s = selector, t = typeof s, nm, u = url, d = data, h = handler, f = filter, a, le, i, v;
  1872. try {
  1873. if (!s) {
  1874. throw new Error('Falsy selector, type[' + t + ']');
  1875. }
  1876. if (t === 'object') {
  1877. if (s instanceof $) {
  1878. s = s.selector || s.get();
  1879. }
  1880. if ($.isArray(s)) {
  1881. if (!(le = s.length)) {
  1882. throw new Error('Empty selector, type array or jquery');
  1883. }
  1884. for (i = 0; i < le; i++) {
  1885. self.ajaxcomplete(s[i], u, d, h, f);
  1886. return;
  1887. }
  1888. }
  1889. }
  1890. if (!(a = self.selector(s, true))) {
  1891. throw new Error('Bad selector, see previous error');
  1892. }
  1893. nm = a[1]; // name attribute (if any), for matching against Drupal Form API ajax.settings._triggering_element_name.
  1894. s = a[0];
  1895. // Resolve other arguments.
  1896. if (!u || typeof u !== 'string') {
  1897. throw new Error('Url type[' + self.typeOf(v) + '] isnt non-empty string');
  1898. }
  1899. if (u !== '*') {
  1900. if (u.indexOf('http') === 0) {
  1901. u = u.replace(/^https?:\/\/[^\/]+(\/.+)$/, '$1');
  1902. }
  1903. else if (u.charAt(0) !== '/') {
  1904. u = '/' + u;
  1905. }
  1906. }
  1907. if (h) {
  1908. if (typeof h === 'object') {
  1909. f = h;
  1910. h = null;
  1911. }
  1912. }
  1913. if (d && typeof d === 'function') {
  1914. h = d;
  1915. d = null;
  1916. }
  1917. if (!h) {
  1918. throw new Error('Cant resolve a handler');
  1919. }
  1920. // Initialise jQuery ajaxComplete listening.
  1921. if (!_acInit) {
  1922. $(document).ajaxComplete(function(event, xhr, settings) {
  1923. var url = self.objectGet(settings, 'url'), all = [], nm, val, le, i, n, j, k, $jq, nElms, fElms, elms, elm, lstnr, h, d, evt;
  1924. if (url) {
  1925. if (url.indexOf('http') === 0) { // Non-Form API urls apparently include protocol and domain.
  1926. url = url.replace(/^https?:\/\/[^\/]+(\/.+)$/, '$1');
  1927. }
  1928. for (k in _acLstnrs) {
  1929. if (_acLstnrs.hasOwnProperty(k) && url.indexOf(k) === 0) {
  1930. all.push(_acLstnrs[k]);
  1931. }
  1932. }
  1933. if (_acLstnrs['*'] && _acLstnrs.hasOwnProperty('*')) {
  1934. all.push(_acLstnrs['*']);
  1935. }
  1936. if ((le = all.length)) {
  1937. // If Drupal Form API _triggering_element_name we will only go for that particular selector.
  1938. if ((nm = self.objectGet(settings, 'extraData', '_triggering_element_name'))) { // Drupal Form API property.
  1939. if (!($jq = $('[name="' + nm + '"]')).length) {
  1940. return;
  1941. }
  1942. if ((val = self.objectGet(settings, 'extraData', '_triggering_element_value')) !== undefined) { // Drupal Form API property.
  1943. if (!($jq = $jq.filter('[value="' + val + '"]')).length) {
  1944. return;
  1945. }
  1946. }
  1947. nElms = (fElms = $jq.get()).length;
  1948. }
  1949. for (i = 0; i < le; i++) {
  1950. n = all[i].length;
  1951. for (j = 0; j < n; j++) {
  1952. lstnr = $jq = elms = h = d = evt = null; // Clear references (loop).
  1953. lstnr = all[i][j];
  1954. if (nm) {
  1955. if (lstnr[1] !== nm || (lstnr[4] && !_filter(settings, lstnr[4]))) {
  1956. continue;
  1957. }
  1958. elms = fElms;
  1959. }
  1960. else {
  1961. nElms = 1;
  1962. switch (lstnr[0]) {
  1963. case '_win_':
  1964. elms = [window];
  1965. break;
  1966. case '_doc_':
  1967. elms = [document];
  1968. break;
  1969. case '_docElm_':
  1970. elms = [document.documentElement];
  1971. break;
  1972. default:
  1973. nElms = (elms = $(lstnr[0]).get()).length;
  1974. }
  1975. if (!nElms || (lstnr[4] && !_filter(settings, lstnr[4]))) {
  1976. continue;
  1977. }
  1978. }
  1979. h = lstnr[2];
  1980. d = lstnr[3];
  1981. evt = !d ? {
  1982. type: 'ajaxcomplete'
  1983. } : {
  1984. type: 'ajaxcomplete',
  1985. data: d
  1986. };
  1987. evt.ajax = settings;
  1988. for (k = 0; k < nElms; k++) {
  1989. elm = null; // Clear references (loop);
  1990. elm = elms[k];
  1991. h.apply(
  1992. elm,
  1993. [evt]
  1994. );
  1995. }
  1996. }
  1997. }
  1998. }
  1999. }
  2000. });
  2001. _acInit = true;
  2002. }
  2003. // Resolve filter.
  2004. if (f) {
  2005. if (!$.isArray(f)) {
  2006. f = [f];
  2007. }
  2008. if (u === '*') { // Add safe filters if responding to wildcard url, to prevent risk of perpetual logging etc.
  2009. f = f.concat(_acFltrs);
  2010. }
  2011. }
  2012. else if (u === '*') { // Add safe filters if responding to wildcard url, to prevent risk of perpetual logging etc.
  2013. f = _acFltrs;
  2014. }
  2015. // Add listeners, keyed by url.
  2016. if (!_acLstnrs[u]) {
  2017. _acLstnrs[u] = [
  2018. [s, nm, h, d, f]
  2019. ];
  2020. }
  2021. else {
  2022. _acLstnrs[u].push(
  2023. [s, nm, h, d, f]
  2024. );
  2025. }
  2026. }
  2027. catch (er) {
  2028. _errorHandler(er, null, _name + '.ajaxcomplete()')
  2029. }
  2030. };
  2031. /**
  2032. * NB: .ajaxcomplete.off (not .ajaxcomplete_off); jsDoc failure.
  2033. *
  2034. * @example
  2035. Judy.ajaxcomplete.off(slctr, sUrl, fHandler);
  2036. Judy.ajaxcomplete.off(slctr, sUrl);
  2037. Judy.ajaxcomplete.off(slctr, fHandler);
  2038. Judy.ajaxcomplete.off(slctr);
  2039. * @function
  2040. * @name Judy.ajaxcomplete_off
  2041. * @param {string|element|array|jquery} selector
  2042. * @param {string|falsy} [url]
  2043. * @param {function|falsy} [handler]
  2044. * @return {void}
  2045. */
  2046. this.ajaxcomplete.off = function(selector, url, handler) {
  2047. var s = selector, t = typeof s, u = url, h = handler, nm, lstnrs, a, le, i, rm = [], sbtrt;
  2048. try {
  2049. if (!_acInit) {
  2050. return;
  2051. }
  2052. if (!s) {
  2053. throw new Error('Falsy selector, type[' + t + ']');
  2054. }
  2055. if (t === 'object') {
  2056. if (s instanceof $) {
  2057. s = s.selector || s.get();
  2058. }
  2059. if ($.isArray(s)) {
  2060. if (!(le = s.length)) {
  2061. throw new Error('Empty selector, type array or jquery');
  2062. }
  2063. for (i = 0; i < le; i++) {
  2064. self.ajaxcomplete.off(s[i], u, h);
  2065. return;
  2066. }
  2067. }
  2068. }
  2069. if (!(a = self.selector(s, true))) {
  2070. throw new Error('Bad selector, see previous error');
  2071. }
  2072. nm = a[1]; // name attribute (if any), for matching against Drupal Form API ajax.settings._triggering_element_name.
  2073. s = a[0];
  2074. // Resolve other arguments.
  2075. if (u) {
  2076. if ((t = typeof u) === 'function') {
  2077. h = u;
  2078. u = null;
  2079. }
  2080. else {
  2081. if (t !== 'string') {
  2082. throw new Error('Url type[' + self.typeOf(v) + '] isnt non-empty string');
  2083. }
  2084. if (u.indexOf('http') === 0) {
  2085. u = u.replace(/^https?:\/\/[^\/]+(\/.+)$/, '$1');
  2086. }
  2087. else if (u.charAt(0) !== '/') {
  2088. u = '/' + u;
  2089. }
  2090. }
  2091. }
  2092. // Remove.
  2093. if (u) {
  2094. _acOff(u, s, nm, h);
  2095. }
  2096. else {
  2097. for (u in _acLstnrs) {
  2098. _acOff(u, s, nm, h);
  2099. }
  2100. }
  2101. }
  2102. catch (er) {
  2103. _errorHandler(er, null, _name + '.ajaxcomplete.off()')
  2104. }
  2105. };
  2106. /**
  2107. * Add keystrokes qualified keydown event handler to one or more elements.
  2108. *
  2109. * The preventDefault arg is ignored for unqualifed event types.
  2110. * Any order of parameters data, handler and preventDefault will do (finds args via type check; object vs. function vs. boolean).
  2111. * Uses jQuery().on() if exists, otherwise .bind().
  2112. *
  2113. * @function
  2114. * @name Judy.keydown
  2115. * @example
  2116. // Qualified event type, e.g. specific keystroke combination:
  2117. Judy.keydown(selector, "ctr_shift_7"|"keydown_ctr_shift_7"|"ctr_shift_7 ctr_s", handler);
  2118. Judy.keydown(selector, "ctr_shift_7"|"keydown_ctr_shift_7", data, handler);
  2119. Judy.keydown(selector, "ctr_shift_7"|"keydown_ctr_shift_7", data, handler, preventDefault);
  2120. Judy.keydown(selector, "ctr_shift_7"|"keydown_ctr_shift_7", handler, preventDefault);
  2121. // Unqualified event type (no specific keystrokes) is handled by jQuery(selector).keydown(...) directly:
  2122. Judy.keydown(selector, handler);
  2123. Judy.keydown(selector, data, handler);
  2124. Judy.keydown(selector, ""|"*"|"keydown", handler);
  2125. Judy.keydown(selector, ""|"*"|"keydown", data, handler);
  2126. * @param {string|element|array|jquery} selector
  2127. * - works on multiple elements
  2128. * @param {string} [events]
  2129. * - see example
  2130. * - default: * (~ any keystroke)
  2131. * @param {object} [data]
  2132. * @param {func} handler
  2133. * @param {boolean} [preventDefault]
  2134. * @return {boolean}
  2135. */
  2136. this.keydown = function() {
  2137. try {
  2138. _bindKeys("keydown", arguments);
  2139. return true;
  2140. }
  2141. catch(er) {
  2142. _errorHandler(er, null, _name + ".keydown()");
  2143. }
  2144. return false;
  2145. };
  2146. /**
  2147. * Add keystrokes qualified keyup event handler to one or more elements.
  2148. *
  2149. * @see Judy.keydown()
  2150. * @function
  2151. * @name Judy.keyup
  2152. * @param {string|element|array|jquery} selector
  2153. * - works on multiple elements
  2154. * @param {string} [events]
  2155. * - see .keydown() example
  2156. * - default: * (~ any keystroke)
  2157. * @param {object} [data]
  2158. * @param {func} handler
  2159. * @param {boolean} [preventDefault]
  2160. * @return {boolean}
  2161. */
  2162. this.keyup = function() {
  2163. try {
  2164. _bindKeys("keyup", arguments);
  2165. return true;
  2166. }
  2167. catch(er) {
  2168. _errorHandler(er, null, _name + ".keyup()");
  2169. }
  2170. return false;
  2171. };
  2172. /**
  2173. * List event handlers added via jQuery.
  2174. *
  2175. * @function
  2176. * @name Judy.eventList
  2177. * @throws {Error}
  2178. * - (caught) if bad arg, or jQuery .data("Judy") isnt object or undefined
  2179. * @param {string|element|array|jquery} selector
  2180. * - only works on a single (first) element
  2181. * @param {string} [type]
  2182. * @return {object|array|null|undefined}
  2183. * - object: all event types
  2184. * - arr: single event type
  2185. * - null: no events/no events of that type
  2186. * - undefined: selector matches no element
  2187. */
  2188. this.eventList = function(selector, type) {
  2189. var r = _elm(0, selector, null, "eventList"), jq, o, k;
  2190. if(!r) {
  2191. return undefined;
  2192. }
  2193. jq = $(r);
  2194. if((o = jq.data())) {
  2195. // Sometimes jQuery's data, like events, arent residing in the object root, but in a sub-object keyed jquery[lots hex chars]???
  2196. if(!o.hasOwnProperty("events")) {
  2197. for(k in o) {
  2198. if(k.length > 7 && o.hasOwnProperty(k) && k.indexOf("jQuery") === 0) {
  2199. if(!(o = o[k]) || !o.events || !o.hasOwnProperty("events")) {
  2200. return null;
  2201. }
  2202. }
  2203. }
  2204. }
  2205. }
  2206. if(!o) {
  2207. return null;
  2208. }
  2209. if(!type) {
  2210. return o.events;
  2211. }
  2212. o = o.events;
  2213. for(k in o) {
  2214. if(k === type && o.hasOwnProperty(k)) {
  2215. return o[k];
  2216. }
  2217. }
  2218. return null;
  2219. };
  2220. // Fields.
  2221. /**
  2222. * Check if element is a form field.
  2223. *
  2224. * Usable when setting key event on document.documentElement or another container, and action on a form field isnt desired.
  2225. *
  2226. * object and button elements arent supported, but input button/submit/reset is.
  2227. *
  2228. * @function
  2229. * @name Judy.isField
  2230. * @param {element} elm
  2231. * - element, not css-selector
  2232. * @param {boolean} [button]
  2233. * - allow button (input type:button|submit|reset)
  2234. * @return {boolean|undefined}
  2235. * - undefined: arg element isnt an element
  2236. */
  2237. this.isField = function(elm, button) {
  2238. return typeof elm === "object" && elm.tagName ? (_fieldType(elm, button) ? true : false) : undefined;
  2239. };
  2240. /**
  2241. * Get type of a field or input button element.
  2242. *
  2243. * object and button elements arent supported, but input button/submit/reset is.
  2244. *
  2245. * @function
  2246. * @name Judy.fieldType
  2247. * @param {string|element|array|jquery} selector
  2248. * - only works on a single (first) element
  2249. * @param {string|element|jquery} [context]
  2250. * - default: document is context
  2251. * @param {boolean} [button]
  2252. * - allow button (input type:button|submit|reset)
  2253. * @return {string|undefined}
  2254. * - undefined: no such element
  2255. */
  2256. this.fieldType = function(selector, context, button) {
  2257. var r = _elm(0, selector, context, "fieldType");
  2258. return r ? _fieldType(r, button) : undefined;
  2259. };
  2260. /**
  2261. * Get/set value of any kind of field or button, even radios and Drupal checkbox list.
  2262. *
  2263. * Do not use "" as empty option for select or check list ('checkboxes'); use "_none" instead.
  2264. *
  2265. * If arg type, then the method trusts that; doesnt check if it's correct.
  2266. *
  2267. * object and button elements arent supported, but input button/submit/reset is.
  2268. *
  2269. * @function
  2270. * @name Judy.fieldValue
  2271. * @param {string|element|array|jquery} selector
  2272. * - only works on a single (first) element
  2273. * @param {element|string|falsy} [context]
  2274. * - default: document is context
  2275. * @param {string|number|array|undefined} [val]
  2276. * - default: undefined (~ get value, dont set)
  2277. * @param {string|undefined} [type]
  2278. * - optional field type hint (text|textarea|checkbox|checkboxes|radios|select|other input type)
  2279. * @return {string|array|boolean|undefined}
  2280. * - empty string (getting only) if empty value or none checked|selected
  2281. * - array (getting only) if check list or multiple select, and some option(s) checked/selected
  2282. * - true if setting succeeded
  2283. * - false if setting failed
  2284. * - undefined if no such field exist, or this method doesnt support the field type
  2285. */
  2286. this.fieldValue = function(selector, context, val, type) {
  2287. var r = _elm(0, selector, context, "fieldValue"), t;
  2288. if(r && (t = type || _fieldType(r, true))) {
  2289. switch(t) {
  2290. case "select":
  2291. return _valSelect(r, val);
  2292. case "checkbox":
  2293. return _valCheckbox(r, val);
  2294. case "checkboxes":
  2295. case "checklist":
  2296. return _valChecklist(r, val);
  2297. case "radio":
  2298. case "radios":
  2299. return _valRadio(r, context, val);
  2300. case "image":
  2301. t = "src";
  2302. default:
  2303. t = "";
  2304. }
  2305. if(val === undefined) {
  2306. return !t ? r.value : r.getAttribute(t);
  2307. }
  2308. if(!t) {
  2309. r.value = "" + val;
  2310. }
  2311. else {
  2312. r.setAttribute(t, "" + val);
  2313. }
  2314. return true;
  2315. }
  2316. return undefined;
  2317. };
  2318. /**
  2319. * Handles all field types (also checkbox, checkboxes/check list, and radios), and adds css class 'form-button-disabled' to button.
  2320. *
  2321. * @function
  2322. * @name Judy.disable
  2323. * @param {string|element|array|jquery} selector
  2324. * - works on multiple elements
  2325. * @param {element|string|falsy} [context]
  2326. * - default: document is context
  2327. * @param {string} [hoverTitle]
  2328. * - update the element's (hover) title attribute
  2329. * @return {void}
  2330. */
  2331. this.disable = function(selector, context, hoverTitle) {
  2332. _disable(0, selector, context, hoverTitle);
  2333. };
  2334. /**
  2335. * Handles all field types (also checkbox, checkboxes/check list, and radios), and removes css class 'form-button-disabled' to button.
  2336. *
  2337. * @function
  2338. * @name Judy.enable
  2339. * @param {string|element|array|jquery} selector
  2340. * - works on multiple elements
  2341. * @param {element|string|falsy} [context]
  2342. * - default: document is context
  2343. * @param {string} [hoverTitle]
  2344. * - update the element's (hover) title attribute
  2345. * @return {void}
  2346. */
  2347. this.enable = function(selector, context, hoverTitle) {
  2348. _disable(1, selector, context, hoverTitle);
  2349. };
  2350. /**
  2351. * Confine vertical scrolling of a container to that container; prevent from escalating to enclosing elements.
  2352. *
  2353. * Wraps child elements in div, unless there's only a single child element.
  2354. * Adds css class 'scroll-trapped' to the container.
  2355. *
  2356. * Does nothing if the container is empty, or if the container already has the 'scroll-trapped' css class.
  2357. *
  2358. * @function
  2359. * @name Judy.scrollTrap
  2360. * @param {string|element|array|jquery} selector
  2361. * - works on multiple elements
  2362. * @param {element|string|falsy} [context]
  2363. * - default: document is context
  2364. * @param {string} [eventName]
  2365. * - default: 'Judy.scrollTrap'
  2366. * @return {void}
  2367. */
  2368. this.scrollTrap = function(selector, context, eventName) {
  2369. var a = _elm(true, selector, context, "scrollTrap"), nm = eventName || (_name + ".scrollTrap");
  2370. if(a) {
  2371. $(a).each(function () {
  2372. var preventZone = 100, halfZone, s = this.scrollTop, $self = $(this), $chlds, le, $chld, h;
  2373. if (!$self.hasClass("scroll-trapped")) {
  2374. // If contains a single element, set scroll-back zone on that element.
  2375. if ((le = ($chlds = $self.children()).get().length) === 1) {
  2376. $chld = $($chlds.get(0));
  2377. }
  2378. else if (le) { // Contains more elements, wrap and set scroll-back zone on the wrapper.
  2379. $chld = $chlds.wrapAll("<div />").parent();
  2380. }
  2381. else { // No children at all, cannot do anything, has to called again (upon insertion of something into the .scrollable).
  2382. return;
  2383. }
  2384. // Dont do this again.
  2385. $self.addClass("scroll-trapped");
  2386. // Add scroll-back zone to top and bottom.
  2387. if ((h = this.clientHeight) < 1.5 * preventZone) { // The scroll-back zone shan"t be more than 2/3 of .scrollable"s height.
  2388. preventZone = Math.floor(h / 1.5);
  2389. }
  2390. halfZone = Math.floor(preventZone / 2);
  2391. $chld.css({
  2392. "margin-top": preventZone + "px",
  2393. "margin-bottom": preventZone + "px"
  2394. });
  2395. // Reset current scroll.
  2396. this.scrollTop = s + preventZone;
  2397. // Add scroll-back handler.
  2398. $self.bind("scroll." + nm, function() {
  2399. var that = this, s = that.scrollTop, h;
  2400. // if (s < preventZone) {
  2401. // this.scrollTop = preventZone;
  2402. // }
  2403. // else if (s > (h = that.scrollHeight - that.clientHeight - preventZone)) {
  2404. // this.scrollTop = h;
  2405. // }
  2406. if (s < halfZone) { // Top.
  2407. that.scrollTop = halfZone; // Scroll half way now.
  2408. setTimeout(function() { // Scroll all the way later.
  2409. that.scrollTop = preventZone;
  2410. }, 100);
  2411. }
  2412. else if (s > (h = that.scrollHeight - that.clientHeight) - halfZone) { // Bottom.
  2413. that.scrollTop = h - halfZone;
  2414. setTimeout(function() {
  2415. that.scrollTop = h - preventZone;
  2416. }, 100);
  2417. }
  2418. });
  2419. }
  2420. });
  2421. }
  2422. };
  2423. /**
  2424. * Make a scrollable element scroll vertically to a numeric offset or the offset of one of it's child elements.
  2425. *
  2426. * @function
  2427. * @name Judy.scrollTo
  2428. * @param {string|element|array|jquery} selector
  2429. * - only works on a single (first) element
  2430. * @param {element|string|falsy} [context]
  2431. * - default: document is context
  2432. * @param {number|string|element|array|jquery} offset
  2433. * - default: zero (~ scroll to top)
  2434. * - number: scroll to that offset
  2435. * - string|element|array|jquery: scroll to first matching child element
  2436. * @param {number|undefined} [pad]
  2437. * - default: zero (~ scroll to exact offset)
  2438. * @return {void}
  2439. */
  2440. this.scrollTo = function(selector, context, offset, pad) {
  2441. var u, par, r, to = offset, p = pad || 0, num, $par, chld, prvntZn = 0, max = -1;
  2442. if((par = _elm(0, selector, context, "scrollTo"))) {
  2443. // Number or no such child element.
  2444. if (!to || typeof to === "number" || !(r = _elm(0, to, par, "", true))) {
  2445. num = true;
  2446. to = !to || !isFinite(to) || to < 0 ? 0 : to;
  2447. }
  2448. // Find scroll-back zone, if confined scroll.
  2449. if(($par = $(par)).hasClass("scroll-trapped") && (chld = $par.children().get(0))) {
  2450. prvntZn = parseInt($(chld).css("margin-top").replace(/px/, ""), 10);
  2451. max = par.scrollHeight - par.clientHeight - Math.floor(prvntZn * 0.75); // Stop at a quarter instead half of scroll-back zone.
  2452. }
  2453. // Numeric; simple.
  2454. if(num) {
  2455. to += prvntZn;
  2456. }
  2457. // Offset of child element.
  2458. else {
  2459. par.scrollTop = prvntZn; // Scroll to enable measuring position of parent and child relative to document.
  2460. to = (r.offsetTop - par.offsetTop);
  2461. }
  2462. // Add padding, but prevent negative padding from being larger than a quarter of the scroll-back zone.
  2463. if(p && prvntZn && p < 0 && (p * -1) > (u = Math.floor(prvntZn / 4))) {
  2464. p = -u;
  2465. }
  2466. to += p;
  2467. // Make sure we dont scroll to far for scroll-trapped parent; scrolling to half-zone (or more) would provoke scroll-back.
  2468. if(max > 0 && to > max) {
  2469. to = max;
  2470. }
  2471. par.scrollTop = to;
  2472. }
  2473. };
  2474. /**
  2475. * Try setting focus on an element, slightly delayed.
  2476. *
  2477. * Attempting to set focus on element may prove fatal, and it is often desirable to postpone focusing until some current procedure has run to its end.
  2478. * This method handles both issues.
  2479. *
  2480. * @function
  2481. * @name Judy.focus
  2482. * @param {string|element|array|jquery} selector
  2483. * - only works on a single (first) element
  2484. * @param {element|string|falsy} [context]
  2485. * - default: document is context
  2486. * @param {integer|undefined} [delay]
  2487. * - default: 20 milliseconds
  2488. * @return {boolean|undefined}
  2489. * - undefined if no such field exists
  2490. */
  2491. this.focus = function(selector, context, delay) {
  2492. var d = delay || 0, to;
  2493. if(selector) {
  2494. to = setTimeout(function(){ // jslint doesnt like instantiation without a reference to hold the instance.
  2495. var r;
  2496. if((r = _elm(0, selector, context, "", true))) { // No error.
  2497. try {
  2498. r.focus();
  2499. }
  2500. catch(er) {}
  2501. }
  2502. }, d >= 0 ? d : 20);
  2503. }
  2504. };
  2505. // Style.
  2506. /**
  2507. * Measures inner width of an element, padding subtracted (unlike jQuery's innerWidth()).
  2508. *
  2509. * Also usable as alternative to jQuery(window).width(), which may give wrong result for mobile browsers.
  2510. *
  2511. * @function
  2512. * @name Judy.innerWidth
  2513. * @param {string|element|array|jquery} selector
  2514. * - only works on a single (first) element
  2515. * - if window, document.documentElement or document.body: the method disregards other args
  2516. * @param {boolean} [ignorePadding]
  2517. * - default: false (~ subtract padding, unlike jQuery)
  2518. * @return {integer|undefined}
  2519. */
  2520. this.innerWidth = function(selector, ignorePadding) {
  2521. return _dimInner("Width", selector, ignorePadding);
  2522. };
  2523. /**
  2524. * Measures inner height of an element, padding subtracted (unlike jQuery's innerHeight()).
  2525. *
  2526. * Also usable as alternative to jQuery(window).height(), which may give wrong result for mobile browsers.
  2527. *
  2528. * @function
  2529. * @name Judy.innerHeight
  2530. * @param {string|element|array|jquery} selector
  2531. * - only works on a single (first) element
  2532. * - if window, document.documentElement or document.body: the method disregards other args
  2533. * @param {boolean} [ignorePadding]
  2534. * - default: false (~ exclude padding, unlike jQuery)
  2535. * - ignored if element is window
  2536. * @return {integer|undefined}
  2537. */
  2538. this.innerHeight = function(selector, ignorePadding) {
  2539. return _dimInner("Height", selector, ignorePadding);
  2540. };
  2541. /**
  2542. * Measures or sets effective outer width of an element, including padding, border and optionally margin.
  2543. *
  2544. * The width will be set on the element itself, in pixels.
  2545. *
  2546. * If selector is window, then window scrollbar is included.
  2547. *
  2548. * @function
  2549. * @name Judy.outerWidth
  2550. * @param {string|element|array|jquery} selector
  2551. * - only works on a single (first) element
  2552. * - if window, document.documentElement or document.body: the method disregards other args and simply measures
  2553. * @param {boolean} [includeMargin]
  2554. * - default: false (~ dont check margin)
  2555. * @param {integer|falsy} [set]
  2556. * - set outer width (including padding, and optionally also margin) to that number of pixels
  2557. * @param {boolean|integer|falsy} [max]
  2558. * - default: false (~ set width)
  2559. * - true|one: set max-width, not width
  2560. * - two: set both
  2561. * @return {integer|undefined}
  2562. */
  2563. this.outerWidth = function(selector, includeMargin, set, max) {
  2564. return _dimOuter("Width", selector, includeMargin, set, max);
  2565. };
  2566. /**
  2567. * Measures or sets effective outer height of an element, including padding, border and optionally margin.
  2568. *
  2569. * The height will be set on the element itself, in pixels.
  2570. *
  2571. * If selector is window, then window scrollbar is included.
  2572. *
  2573. * @function
  2574. * @name Judy.outerHeight
  2575. * @param {string|element|array|jquery} selector
  2576. * - only works on a single (first) element
  2577. * - if window, document.documentElement or document.body: the method disregards other args and simply measures
  2578. * @param {boolean} [includeMargin]
  2579. * - default: false (~ dont check margin)
  2580. * @param {integer|falsy} [set]
  2581. * - set outer width (including padding, and optionally also margin) to that number of pixels
  2582. * @param {boolean|integer|falsy} [max]
  2583. * - default: false (~ set height)
  2584. * - true|one: set max-height, not height
  2585. * - two: set both
  2586. * @return {integer|undefined}
  2587. */
  2588. this.outerHeight = function(selector, includeMargin, set, max) {
  2589. return _dimOuter("Height", selector, includeMargin, set, max);
  2590. };
  2591. // String.
  2592. /**
  2593. * Strip tags, reduce consecutive spaces, and trim spaces.
  2594. *
  2595. * @function
  2596. * @name Judy.stripTags
  2597. * @param {mixed} u
  2598. * - will be stringed
  2599. * @return {string}
  2600. */
  2601. this.stripTags = function(u) {
  2602. return $.trim(("" + u).replace(/<[^<>]+>/g, " ").replace(/[ ]+/g, " "));
  2603. };
  2604. /**
  2605. * Prepends zero(s) to arg length.
  2606. *
  2607. * @example // converting a newline to \uNNNN format
  2608. var s = "\\"+"u" + Judy.toLeading("\n".charCodeAt(0).toString(16), 4); // -> "\u000a"
  2609. * @function
  2610. * @name Judy.toLeading
  2611. * @param {mixed} u - will be stringed
  2612. * @param {integer} [length] default: one
  2613. * @return {string}
  2614. */
  2615. this.toLeading = function(u, length) {
  2616. var le = length || 1;
  2617. return (new Array(le).join("0") + u).substr(-le, le);
  2618. };
  2619. /**
  2620. * @function
  2621. * @name Judy.toUpperCaseFirst
  2622. * @param {mixed} u
  2623. * - anything stringable
  2624. * @return {string}
  2625. */
  2626. this.toUpperCaseFirst = function(u) {
  2627. var s = ""+u, le = s.length;
  2628. return !le ? "" : (s.charAt(0).toUpperCase() + (le < 2 ? "" : s.substr(1)));
  2629. };
  2630. // Date.
  2631. /**
  2632. * @function
  2633. * @name Judy.isLeapYear
  2634. * @param {Date|integer|str} u
  2635. * @return {boolean|null}
  2636. * - null if bad arg
  2637. */
  2638. this.isLeapYear = function(u) {
  2639. var y;
  2640. switch(self.typeOf(u)) {
  2641. case "date":
  2642. y = u.getFullYear();
  2643. break;
  2644. case "number":
  2645. y = u;
  2646. break;
  2647. case "string":
  2648. y = parseInt(u, 10);
  2649. break;
  2650. default:
  2651. return null;
  2652. }
  2653. if(isFinite(y) && u > -1 && u % 1 === 0) {
  2654. return (!(y % 4) && (y % 100)) || !(y % 400);
  2655. }
  2656. return null;
  2657. };
  2658. /**
  2659. * Get Date as iso-8601 string, including milliseconds.
  2660. *
  2661. * @function
  2662. * @name Judy.dateISO
  2663. * @param {Date|falsy} [date]
  2664. * - default: now
  2665. * @param {boolean} [UTC]
  2666. * - default: false (~ local time, 1970-01-01T01:00:00.001+01:00)
  2667. * - truthy: 1970-01-01T00:00:00.001Z
  2668. * @return {string}
  2669. */
  2670. this.dateISO = function(date, UTC) {
  2671. var d = date || new Date();
  2672. return UTC && Date.prototype.toISOString ? d.toISOString() : _dateFrmt(d, 1, 1, 1, UTC, 1);
  2673. };
  2674. /**
  2675. * Get Date as iso-8601 string without milliseconds, T and timezone.
  2676. *
  2677. * @function
  2678. * @name Judy.dateTime
  2679. * @param {Date|falsy} [date]
  2680. * - default: now
  2681. * @param {boolean} [UTC]
  2682. * - default: false (~ local time, 1970-01-01 01:00:00)
  2683. * - truthy: 1970-01-01 00:00:00
  2684. * @return {string}
  2685. */
  2686. this.dateTime = function(date, UTC) {
  2687. var d = date || new Date();
  2688. return UTC && Date.prototype.toISOString ? d.toISOString().replace(/T/, " ").replace(/\.\d{3}Z$/, "") : _dateFrmt(d, 1, 1, 0, UTC);
  2689. };
  2690. /**
  2691. * Translate a Date into a string - like the value of a text field.
  2692. *
  2693. * Supported formats, dot means any (non-YMD) character:
  2694. * - YYYY.MM.DD [HH][:II][:SS][ mmm]
  2695. * - MM.DD.YYYY [HH][:II][:SS][ mmm]
  2696. * - DD.MM.YYYY [HH][:II][:SS][ mmm]
  2697. *
  2698. * @function
  2699. * @name Judy.dateToFormat
  2700. * @param {Date} date
  2701. * - no default, because empty/wrong arg must be detectable
  2702. * @param {string} [sFormat]
  2703. * - default: YYYY-MM-DD, omitting hours etc.
  2704. * @return {string}
  2705. * - empty if arg dt isnt Date object, or unsupported format
  2706. */
  2707. this.dateToFormat = function(date, sFormat) {
  2708. var u = date, fmt = sFormat || "YYYY-MM-DD", le, y, m, d, s, a, b;
  2709. if(u && typeof u === "object" && u.getFullYear) {
  2710. y = u.getFullYear();
  2711. m = self.toLeading(u.getMonth() + 1, 2);
  2712. d = self.toLeading(u.getDate(), 2);
  2713. if((a = (s = fmt.substr(0, 10)).replace(/[MDY]/g, "")).length < 2) {
  2714. return "";
  2715. }
  2716. b = a.charAt(1);
  2717. a = a.charAt(0);
  2718. switch(s.replace(/[^MDY]/g, "")) {
  2719. case "YYYYMMDD":
  2720. s = y + a + m + b + d;
  2721. break;
  2722. case "MMDDYYYY":
  2723. s = m + a + d + b + y;
  2724. break;
  2725. case "DDMMYYYY":
  2726. s = d + a + m + b + y;
  2727. break;
  2728. default:
  2729. return "";
  2730. }
  2731. if((le = fmt.length) > 11) {
  2732. s += " " + self.toLeading(u.getHours(), 2);
  2733. if(le > 14) {
  2734. s += ":" + self.toLeading(u.getMinutes(), 2);
  2735. if(le > 17) {
  2736. s += ":" + self.toLeading(u.getSeconds(), 2);
  2737. if(le > 20) {
  2738. s += " " + self.toLeading(u.getMilliseconds(), 3);
  2739. }
  2740. }
  2741. }
  2742. }
  2743. return s;
  2744. }
  2745. else {
  2746. try {
  2747. throw new Error("date[" + u + "] type[" + self.typeOf(u) + "] is not a non-empty Date");
  2748. }
  2749. catch(er) {
  2750. _errorHandler(er, null, _name + ".dateToFormat()");
  2751. }
  2752. return "";
  2753. }
  2754. };
  2755. /**
  2756. * Translate string - like the value of a text field - to Date.
  2757. *
  2758. * Supported formats, dot means any (non-YMD) character:
  2759. * - YYYY.MM.DD
  2760. * - MM.DD.YYYY
  2761. * - DD.MM.YYYY
  2762. *
  2763. * No support for hours etc.
  2764. * @function
  2765. * @name Judy.dateFromFormat
  2766. * @param {string} s
  2767. * @param {string} [sFormat]
  2768. * - default: YYYY-MM-DD
  2769. * - delimiters are ignored, only looks for the position of YYYY, MM and DD in the format string
  2770. * @return {Date|null}
  2771. * - null if arg str isnt non-empty string, or impossible month or day, or unsupported format
  2772. */
  2773. this.dateFromFormat = function(sDate, sFormat) {
  2774. var s = sDate, dt = new Date(), fmt = sFormat || "YYYY-MM-DD", y, m, d;
  2775. if(s && typeof s === "string") {
  2776. if(/^YYYY.MM.DD$/.test(fmt)) { // iso
  2777. y = s.substr(0, 4);
  2778. m = s.substr(5, 2);
  2779. d = s.substr(8, 2);
  2780. }
  2781. else if(/^MM.DD.YYYY$/.test(fmt)) { // English
  2782. y = s.substr(6, 4);
  2783. m = s.substr(0, 2);
  2784. d = s.substr(3, 2);
  2785. }
  2786. else if(/^DD.MM.YYYY$/.test(fmt)) { // continental
  2787. y = s.substr(6, 4);
  2788. m = s.substr(3, 2);
  2789. d = s.substr(0, 2);
  2790. }
  2791. else {
  2792. return null;
  2793. }
  2794. y = parseInt(y, 10);
  2795. d = parseInt(d, 10);
  2796. switch((m = parseInt(m, 10))) {
  2797. case 1:
  2798. case 3:
  2799. case 5:
  2800. case 7:
  2801. case 8:
  2802. case 10:
  2803. case 12:
  2804. if(d > 31) {
  2805. return null;
  2806. }
  2807. break;
  2808. case 4:
  2809. case 6:
  2810. case 9:
  2811. case 11:
  2812. if(d > 30) {
  2813. return null;
  2814. }
  2815. break;
  2816. case 2:
  2817. if(d > 29 || (d === 29 && !self.isLeapYear(y))) {
  2818. return null;
  2819. }
  2820. break;
  2821. default:
  2822. return null;
  2823. }
  2824. dt.setFullYear(y, m - 1, d );
  2825. dt.setHours(0, 0, 0);
  2826. dt.setMilliseconds(0);
  2827. return dt;
  2828. }
  2829. else {
  2830. try {
  2831. throw new Error("date[" + s + "] type[" + self.typeOf(s) + "] is not non-empty string");
  2832. }
  2833. catch(er) {
  2834. _errorHandler(er, null, _name + ".dateFromFormat()");
  2835. }
  2836. return null;
  2837. }
  2838. };
  2839. /**
  2840. * Modifies a date with evaluated value of a time string, or creates time string based upon the date.
  2841. *
  2842. * If hours evaluate to 24:
  2843. * - if minutes and seconds are zero, then converts to 23:59:59; because 00:00:00 is today, whereas 24:00:00 is tomorrow
  2844. * - otherwise sets hours as zero
  2845. *
  2846. * @example
  2847. // Get time of a date:
  2848. Judy.timeFormat(date);
  2849. // Modify time of a date:
  2850. Judy.timeFormat(date, "17:30");
  2851. * @function
  2852. * @name Judy.timeFormat
  2853. * @param {Date} date
  2854. * - by reference
  2855. * - now default, logs error if falsy
  2856. * @param {string|falsy} [sTime]
  2857. * - empty: creates time string according to arg date
  2858. * - non-empty: sets time of arg date
  2859. * - any kinds of delimiters are supported; only looks for integers
  2860. * - N, NN, NNNN and NNNNNN are also supported
  2861. * @return {string}
  2862. * - time NN:NN:NN
  2863. */
  2864. this.timeFormat = function(date, sTime) {
  2865. var d = date, t = sTime ? $.trim(sTime) : 0, h = 0, i = 0, s = 0, le, v;
  2866. if(d && typeof d === "object" && d.getFullYear) {
  2867. // Modify date.
  2868. if(t) {
  2869. if(/^\d+$/.test(t)) {
  2870. h = t.substr(0, 2);
  2871. if((le = t.length) > 3) {
  2872. i = t.substr(2, 2);
  2873. if(le > 5) {
  2874. s = t.substr(4, 2);
  2875. }
  2876. }
  2877. }
  2878. else if( (le = (t = t.split(/[^\d]/)).length) ) {
  2879. h = t[0];
  2880. if(le > 1) {
  2881. i = t[1];
  2882. if(le > 2) {
  2883. s = t[2];
  2884. }
  2885. }
  2886. }
  2887. if(h) {
  2888. h = isFinite(v = parseInt(h, 10)) && v < 25 ? v : 0;
  2889. if(i) {
  2890. i = isFinite(v = parseInt(i, 10)) && v < 60 ? v : 0;
  2891. }
  2892. if(s) {
  2893. s = isFinite(v = parseInt(s, 10)) && v < 60 ? v : 0;
  2894. }
  2895. if(h === 24) {
  2896. if(!i && !s) {
  2897. h = 23;
  2898. i = s = 59;
  2899. }
  2900. else {
  2901. h = 0;
  2902. }
  2903. }
  2904. }
  2905. d.setHours(h, i, s);
  2906. }
  2907. // Create time string from date.
  2908. else {
  2909. h = d.getHours();
  2910. i = d.getMinutes();
  2911. s = d.getSeconds();
  2912. }
  2913. return "" + (h < 10 ? "0" : "") + h + ":" + (i < 10 ? "0" : "") + i + ":" + (s < 10 ? "0" : "") + s;
  2914. }
  2915. else {
  2916. try {
  2917. throw new Error("date[" + d + "] type[" + self.typeOf(d) + "] is not a non-empty Date");
  2918. }
  2919. catch(er) {
  2920. _errorHandler(er, null, _name + ".timeFormat()");
  2921. }
  2922. return "00:00:00";
  2923. }
  2924. };
  2925. /**
  2926. * Converts a number to formatted string.
  2927. *
  2928. * oFormat:
  2929. * - (str) type, default integer; values integer|float|decimal
  2930. * - (str) thousand_separator, default space
  2931. * - (str) decimal_separator, default dot
  2932. * - (int) scale, default 2
  2933. *
  2934. * @function
  2935. * @name Judy.numberToFormat
  2936. * @param {number} num
  2937. * @param {object} [oFormat]
  2938. * @return {number}
  2939. */
  2940. this.numberToFormat = function(num, oFormat) {
  2941. var n = num || 0, s, sgn = "", o, isInt, kSep, scale, u, le, d, i;
  2942. if(!n) {
  2943. return "0";
  2944. }
  2945. if(n < 0) {
  2946. n *= -1;
  2947. sgn = "-";
  2948. }
  2949. isInt = !(u = (o = oFormat || {}).type) || u === "integer";
  2950. kSep = (u = o.thousand_separator) || u === "" ? u : " ";
  2951. // Extract decimals.
  2952. if((d = n % 1)) {
  2953. n = Math.round(n);
  2954. }
  2955. s = "" + n;
  2956. // Thousand separation.
  2957. if(kSep && (le = s.length) > 3) {
  2958. n = s;
  2959. s = n.substr(0, i = le % 3);
  2960. while(i < le) {
  2961. s += (i ? kSep : "") + n.substr(i, 3);
  2962. i += 3;
  2963. }
  2964. }
  2965. scale = o.scale || 2;
  2966. return sgn + s +
  2967. (isInt ? "" : (
  2968. ((u = o.decimal_separator) || u === "" ? u : ".") +
  2969. ( (d ? ("" + Math.round(d * Math.pow(10, scale))) : "") + // Round decimals.
  2970. new Array( scale + 1 ).join("0") ).substr(0, scale)
  2971. ) );
  2972. };
  2973. /**
  2974. * Converts a numberish string containing thousand separators and/or decimal marker to number.
  2975. *
  2976. * Validates that the string matches the format; detects if there's a non-number somewhere after a decimal marker.
  2977. *
  2978. * Also handles currency slash dash (and equivalent) endings; like 15/- or 15,-
  2979. *
  2980. * oFormat:
  2981. * - (str) type, default integer; values integer|float|decimal
  2982. * - (str) thousand_separator, default space
  2983. * - (str) decimal_separator, default dot
  2984. *
  2985. * @function
  2986. * @name Judy.numberFromFormat
  2987. * @param {string} str
  2988. * @param {object} [oFormat]
  2989. * @return {number|boolean}
  2990. * - false: arg str doesnt match the format
  2991. */
  2992. this.numberFromFormat = function(str, oFormat) {
  2993. var s = $.trim(str), sgn = 1, o, isInt, dSep, u, p, d, n;
  2994. if(!s || s === "0" || s === "-0") {
  2995. return 0;
  2996. }
  2997. if(s.charAt(0) === "-") {
  2998. sgn = -1;
  2999. s = s.substr(1);
  3000. }
  3001. // Remove trailing decimal marker or currency slash. Remove leading separator and leading zeros.
  3002. if((s = s.replace(/^(.*\d)\D+$/, "$1").
  3003. replace(/^[^1-9]+([1-9].*)$/, "$1"))
  3004. ) {
  3005. // Prepare format.
  3006. isInt = !(u = (o = oFormat || {}).type) || u === "integer";
  3007. dSep = o.decimal_separator || ".";
  3008. // Validate - check if there's a non-number somewhere after decimal marker.
  3009. if(new RegExp("\\" + dSep + "\\d*\\D").test(s)) {
  3010. return false;
  3011. }
  3012. // Extract decimals.
  3013. if((p = s.indexOf(dSep)) > -1) {
  3014. d = s.substr(p).replace(/\D/g, "");
  3015. s = s.substr(0, p);
  3016. }
  3017. // Remove thousand separators.
  3018. n = parseInt(
  3019. s.replace(/\D/g, ""),
  3020. 10
  3021. );
  3022. if(d) {
  3023. n += parseInt(d, 10) / Math.pow(10, d.length);
  3024. }
  3025. return sgn * (!isInt ? n : Math.round(n));
  3026. }
  3027. return 0;
  3028. };
  3029. // Miscellaneous.
  3030. /**
  3031. * Random number.
  3032. *
  3033. * @function
  3034. * @name Judy.rand
  3035. * @param {integer} [min]
  3036. * - default: zero
  3037. * @param {integer} [max]
  3038. * - default: 9e15 (~ almost 9007199254740992 aka 2^53, the largest representable integer in Javascript)
  3039. * @return {integer}
  3040. */
  3041. this.rand = function(min, max) {
  3042. var m = min || 0;
  3043. return m + Math.floor( ( Math.random() * ( ((max || 9e15) - m) + 1 ) ) + 1 ) - 1;
  3044. };
  3045. /**
  3046. * Random name.
  3047. *
  3048. * Default length 20 chars, starts with a letter, the rest is a base 36 string.
  3049. *
  3050. * Slight performance hit when passing lengths 12, 23, 34 etc. ~ (n*11)+1
  3051. * - because iterates for approximately every 11 char ~ (n*11)+1
  3052. * - the most economical lengths are probably one less (11, 21, 31)
  3053. *
  3054. * Approximate bit-size when using length:
  3055. * - 12 ~ 54-bit (53-bit plus sqrt(26)~4.5 minus 3.5 for always making large numbers (filling up all digits))
  3056. * - 20 ~ 90-bit (estimated)
  3057. * - 23 ~ 107-bit
  3058. * - 34 ~ 160-bit
  3059. * - a-z0-9, first character is always a letter
  3060. * @function
  3061. * @name Judy.randName
  3062. * @param {integer} [length]
  3063. * - default: 20
  3064. * @return {string}
  3065. */
  3066. this.randName = function(length) {
  3067. var al = length || 20, l, s = String.fromCharCode(Math.floor(Math.random()*26)+97); // first char letter
  3068. while((l = s.length) < al) {
  3069. s += Math.floor(Math.random()*9e15).toString(36); // convert to base 36 for shorter string length
  3070. }
  3071. return l > al ? s.substr(0, al) : s;
  3072. };
  3073. /**
  3074. * NOT relevant in Drupal context, because GET parameters arent used that way in Drupal.
  3075. *
  3076. * Set url parameter.
  3077. *
  3078. * @ignore
  3079. * @function
  3080. * @name Judy.setUrlParam
  3081. * @param {string} url
  3082. * - full url, or just url query (~ window.location.search)
  3083. * @param {string|object} name
  3084. * @param {string|number|falsy} [value]
  3085. * - ignored if arg name is object
  3086. * - falsy and not zero: unsets the parameter
  3087. * @return {string}
  3088. *
  3089. this.setUrlParam = function(url, name, value) {
  3090. var u = url || "", a = u, h = "", o = name, oS = {}, p, le, i, k, v;
  3091. if(u && (p = u.indexOf("#")) > -1) {
  3092. h = u.substr(p);
  3093. u = u.substr(0, p);
  3094. }
  3095. if(u && (p = u.indexOf("?")) > -1) {
  3096. a = u.substr(p + 1);
  3097. u = u.substr(0, p);
  3098. }
  3099. else {
  3100. a = "";
  3101. }
  3102. if(typeof o !== "object") {
  3103. o = {};
  3104. o[name] = value;
  3105. }
  3106. if(a) {
  3107. le = (a = a.split(/&/g)).length;
  3108. for(i = 0; i < le; i++) {
  3109. if((p = a[i].indexOf("=")) > 0) {
  3110. oS[ a[i].substr(0, p) ] = a[i].substr(p + 1);
  3111. }
  3112. else if(p) { // Dont use it if starts with =.
  3113. oS[ a[i] ] = "";
  3114. }
  3115. }
  3116. }
  3117. a = [];
  3118. for(k in oS) {
  3119. if(oS.hasOwnProperty(k)) {
  3120. if(o.hasOwnProperty(k)) {
  3121. if((v = o[k]) || v === 0) { // Falsy and not zero: unsets the parameter.
  3122. a.push(k + "=" + encodeURIComponent(v));
  3123. }
  3124. delete o[k];
  3125. }
  3126. else {
  3127. a.push(k + "=" + oS[k]);
  3128. }
  3129. }
  3130. }
  3131. for(k in o) {
  3132. if(o.hasOwnProperty(k) && (v = o[k]) || v === 0) {
  3133. a.push(k + "=" + v);
  3134. }
  3135. }
  3136. return u + (a.length ? ("?" + a.join("&")) : "") + h;
  3137. };*/
  3138. // UI.
  3139. /**
  3140. * Show/hide overlay.
  3141. *
  3142. * Adds an overlay element to DOM, if not yet done and arg show evaluates to show.
  3143. *
  3144. * @function
  3145. * @name Judy.overlay
  3146. * @param {boolean|integer|Event} [show]
  3147. * - default: falsy (~ hide)
  3148. * - Event|object: hide (because may be used as event handler)
  3149. * @param {boolean} [opaque]
  3150. * - default: false
  3151. * - truthy: add css class 'module-judy-overlay-opaque'
  3152. * @param {string|falsy} [hoverTitle]
  3153. * - default: falsy (~ no hover title)
  3154. * - truthy: set the overlay's title attribute, and add css class 'module-judy-overlay-hovertitled'
  3155. * @return {void}
  3156. */
  3157. this.overlay = function(show, opaque, hoverTitle) {
  3158. var hide = !show || typeof show === "object", ttl = hoverTitle || "",
  3159. clsO = "module-judy-overlay-opaque", clsT = "module-judy-overlay-hovertitled";
  3160. if(!_jqOvrly) {
  3161. if(hide) { // Dont want to build it until it's actually gonna be used.
  3162. return;
  3163. }
  3164. $(document.body).append(
  3165. "<div id=\"module_judy_overlay\" class=\"" +
  3166. (opaque ? clsO : "") + (opaque && ttl ? " " : "") + (ttl ? clsT : "") +
  3167. "\"></div>"
  3168. );
  3169. _jqOvrly = $("div#module_judy_overlay");
  3170. _ovrlyRsz();
  3171. $(window).resize(function() {
  3172. _ovrlyRsz();
  3173. });
  3174. }
  3175. else if(hide) {
  3176. _jqOvrly.hide();
  3177. return;
  3178. }
  3179. else {
  3180. _jqOvrly[ opaque ? "addClass" : "removeClass" ](clsO)[ ttl ? "addClass" : "removeClass" ](clsT).get(0).setAttribute("title", ttl);
  3181. }
  3182. _jqOvrly.show();
  3183. };
  3184. /**
  3185. * jQuery ui dialog wrapper/factory, which makes it easier to create a dialog and maintain a reference to it.
  3186. *
  3187. * The reference is the dialog's name, which is returned on creation and usable as selector arg when calling the dialog later.
  3188. *
  3189. * Creates or re-uses existing content element, and creates or re-uses related jQuery ui dialog box.
  3190. *
  3191. * Non-standard options/methods:
  3192. * - (str) content: sets the HTML content of the selector (dialog content element)
  3193. * - (bool) fixed: css-positions the dialog to fixed relative to document body; ignored after dialog creation
  3194. * - (str) contentClass: sets that/these css class(es) on the content element
  3195. * - getContent(): get dialog content, excluding buttons
  3196. *
  3197. * Supported standard options:
  3198. * - appendTo
  3199. * - autoOpen: default true
  3200. * - buttons
  3201. * - closeOnEscape
  3202. * - closeText
  3203. * - dialogClass
  3204. * - draggable
  3205. * - height
  3206. * - hide
  3207. * - maxHeight
  3208. * - maxWidth
  3209. * - minHeight
  3210. * - minWidth
  3211. * - modal: only works on creation (make more dedicated modal/non-modal dialogs)
  3212. * - position
  3213. * - resizable
  3214. * - show
  3215. * - title
  3216. * - width
  3217. *
  3218. * Standard events:
  3219. * - beforeClose
  3220. * - create
  3221. * - open
  3222. * - focus
  3223. * - dragStart
  3224. * - drag
  3225. * - dragStop
  3226. * - resizeStart
  3227. * - resize
  3228. * - resizeStop
  3229. * - close
  3230. *
  3231. * Supported standard methods:
  3232. * - close
  3233. * - destroy
  3234. * - isOpen
  3235. * - moveToTop
  3236. * - open
  3237. * - option
  3238. * - widget
  3239. *
  3240. * Open/close (show/hide) will always be applied in a manner so as to prevent display of other changes.
  3241. *
  3242. * @example
  3243. // Add new element to the DOM and attach new dialog to it; and then modify the dialog:
  3244. var random_name = Judy.dialog("", { title: "Randomly named", content: "The content", fixed: true } );
  3245. Judy.dialog(random_name, "content", "Changed content of existing dialog named " + random_name);
  3246. // Find existing element - or create new having that id and/or class - and attach new dialog:
  3247. var name_as_element_id = Judy.dialog("#some_element.some-class", { title: "Named by element id", content: "The content" });
  3248. Judy.dialog(name_as_element_id, "content", "Changed content of new or existing dialog named " + name_as_element_id);
  3249. Judy.dialog("#" + name_as_element_id, "title", "Doesnt matter if using # when calling existing");
  3250. // Get content of existing dialog:
  3251. console.log(Judy.dialog(name_as_element_id, "getContent"));
  3252. * @function
  3253. * @name Judy.dialog
  3254. * @param {string|element|array|jquery} selector
  3255. * - only works on a single (first) element
  3256. * - default, empty: new random html id
  3257. * - default, non-empty having no . or #: dialog content element's html id
  3258. * - other non-empty: like jQuery() selector arg: tagName and/or id and/or css class(es), but name no attribute, and class(es) cannot go before #id
  3259. * @param {string|object} [option]
  3260. * - string: single option or method
  3261. * - object: list of options/methods
  3262. * @param {mixed} [value]
  3263. * - value of option, if arg option is string; otherwise ignored
  3264. * @return {string|mixed|bool}
  3265. * - string: at dialog creation, element id of the dialog box (at dialog creation, and when called later with option object)
  3266. * - mixed: when called later using one of the methods; return value of that method call
  3267. * - false if no jQuery ui dialog support
  3268. */
  3269. this.dialog = function(selector, option, value) {
  3270. var sl = selector, u = option, t, s, o, v = value, keys, a, tg = "", id = "", cls = "module-judy-dialog", cls1 = "", elm, jq, dialExists, fxd,
  3271. title, doOpen, autoOpenLater, to;
  3272. if($.ui && typeof $.ui.dialog === "function") {
  3273. if(u) {
  3274. if((t = typeof u) === "string") {
  3275. if(u !== "option") {
  3276. s = u;
  3277. }
  3278. else if(v && typeof v === "object") { // silly: option "option", value {option:value}
  3279. o = self.containerCopy(v); // because we later delete from it
  3280. }
  3281. }
  3282. else if(t === "object") {
  3283. o = self.containerCopy(u); // because we later delete from it
  3284. }
  3285. }
  3286. if(sl) {
  3287. if(typeof sl === "string") {
  3288. if(sl.indexOf("#") === -1 && sl.indexOf(".") === -1) {
  3289. if((elm = document.getElementById(id = sl)) &&
  3290. self.arrayIndexOf(_dialogs, id) > -1) {
  3291. dialExists = true;
  3292. }
  3293. }
  3294. else if((elm = $(sl).get(0))) { // jQuery selector
  3295. if((id = elm.id)) {
  3296. if(self.arrayIndexOf(_dialogs, id) > -1) {
  3297. dialExists = true;
  3298. }
  3299. }
  3300. else {
  3301. id = elm.id = self.randName();
  3302. }
  3303. }
  3304. else { // Create new element; having same element, name and class.
  3305. a = sl.replace(/^([a-z\d_\-]+)?(\#[a-z\d_\-]+)?(\.[a-z\d_\-]+)?$/, "$1,$2,$3").split(",");
  3306. tg = a[0];
  3307. id = a[1] ? a[1].substr(1) : self.randName();
  3308. if(a[2]) {
  3309. cls1 = " " + a[2].split(/\./).join(" ")
  3310. }
  3311. }
  3312. }
  3313. else if(typeof sl === "object" && sl.getAttributeNode) {
  3314. if((id = elm.id)) {
  3315. if(self.arrayIndexOf(_dialogs, id) > -1) {
  3316. dialExists = true;
  3317. }
  3318. }
  3319. else {
  3320. id = elm.id = self.randName();
  3321. }
  3322. }
  3323. }
  3324. else {
  3325. id = self.randName();
  3326. }
  3327. // existing dialog box --------------------
  3328. if(dialExists) {
  3329. doOpen = false; // Default; dont change current open/close state.
  3330. jq = $(elm);
  3331. if(o) {
  3332. delete o.fixed; // Only usable at instantiation.
  3333. if((keys = self.objectKeys(o)).length === 1) { // Extract that single option; may be method, and then we want to return it.
  3334. v = o[ s = keys[0] ];
  3335. }
  3336. }
  3337. if(s) {
  3338. if(s === "content") {
  3339. if(jq.dialog("isOpen")) {
  3340. doOpen = true;
  3341. }
  3342. jq.html(v);
  3343. if(doOpen) {
  3344. if(doOpen) { // give the browser a sec to re-render
  3345. to = setTimeout(function(){
  3346. jq.dialog("open");
  3347. }, 100);
  3348. }
  3349. }
  3350. return id;
  3351. }
  3352. if(s === "getContent") {
  3353. return jq.html();
  3354. }
  3355. // There is no native option title (when updating), and we furthermore want to allow HTML (not textnode).
  3356. if (s === 'title') {
  3357. $('.ui-dialog-title', $(elm.parentNode)).html(v);
  3358. return id;
  3359. }
  3360. else if(self.arrayIndexOf(_dialOpts, s) > -1 || self.arrayIndexOf(_dialEvts, s) > -1) {
  3361. jq.dialog("option", s, v);
  3362. }
  3363. if(self.arrayIndexOf(_dialMthds, s) > -1) {
  3364. return jq.dialog(s);
  3365. }
  3366. jq.dialog(s, v);
  3367. return id;
  3368. }
  3369. else if(o) {
  3370. if(jq.dialog("isOpen")) {
  3371. doOpen = true;
  3372. jq.dialog("close"); // do always close before changing anything else
  3373. }
  3374. if(o.close && o.hasOwnProperty("close") && typeof o.close !== "function") {
  3375. doOpen = false;
  3376. delete o.close;
  3377. }
  3378. if(o.content !== undefined && o.hasOwnProperty("content")) {
  3379. jq.html(v);
  3380. delete o.content;
  3381. }
  3382. if(o.open && o.hasOwnProperty("open")) {
  3383. doOpen = true;
  3384. delete o.open;
  3385. }
  3386. jq.dialog(o);
  3387. if(doOpen) { // give the browser a sec to re-render
  3388. to = setTimeout(function(){
  3389. jq.dialog("open");
  3390. }, 100);
  3391. }
  3392. }
  3393. return id;
  3394. }
  3395. // new dialog box -------------------------
  3396. doOpen = true; // Default for new; autoOpen default for jQuery UI Dialog is true.
  3397. if(!o) {
  3398. o = {};
  3399. if(s) {
  3400. o[s] = v;
  3401. }
  3402. }
  3403. if(!elm) {
  3404. $(document.body).append("<" + (tg || "div") + " id=\"" + id + "\" class=\"" + cls + cls1 +
  3405. (!o.contentClass ? '' : (' ' + o.contentClass)) + "\"></" + (tg || "div") + ">");
  3406. elm = document.getElementById(id);
  3407. }
  3408. jq = $(elm);
  3409. if(o.open && o.hasOwnProperty("open")) {
  3410. delete o.open; // We want to do it ourselves, a bit later.
  3411. }
  3412. if(!o.autoOpen && o.hasOwnProperty("autoOpen")) { // autoOpen:true is the default of jQuery UI Dialog.
  3413. doOpen = false;
  3414. }
  3415. else {
  3416. autoOpenLater = true; // We want to do it ourselves, a bit later.
  3417. o.autoOpen = false;
  3418. }
  3419. if((u = self.objectGet(o, "content"))) {
  3420. jq.html(u);
  3421. delete o.content;
  3422. }
  3423. if(o.fixed && o.hasOwnProperty("fixed")) {
  3424. fxd = true;
  3425. delete o.fixed;
  3426. }
  3427. // Allow HTML title (not textnode).
  3428. if(o.title && o.hasOwnProperty("title")) {
  3429. title = o.title;
  3430. delete o.title;
  3431. }
  3432. // Instantiate jQuery UI dialog, and fix properties of that container which the jQuery UI dialog wraps around the content element.
  3433. jq.dialog(o);
  3434. u = $(elm.parentNode);
  3435. if(fxd) {
  3436. u.css("position", "fixed");
  3437. }
  3438. if (title !== undefined) {
  3439. $('.ui-dialog-title', u).html(title);
  3440. }
  3441. u.addClass(cls + "-container");
  3442. // Register.
  3443. _dialogs.push(id);
  3444. if(doOpen) { // give the browser a sec to re-render
  3445. to = setTimeout(function(){
  3446. jq.dialog("open");
  3447. if(autoOpenLater) {
  3448. jq.dialog("autoOpen", true);
  3449. }
  3450. }, 100);
  3451. }
  3452. return id;
  3453. }
  3454. try {
  3455. throw new Error("jQuery UI Dialog not included");
  3456. }
  3457. catch(er) {
  3458. _errorHandler(er, null, _name + ".dialog()");
  3459. }
  3460. return false;
  3461. };
  3462. // Timer.
  3463. /**
  3464. * setTimeout alternative - executes the function in try-catch, and supports checking if the function has been executed yet.
  3465. *
  3466. * Convenience method for new Judy.Timer().
  3467. * @example
  3468. var doooh = function(ms) { jQuery(this).html("&lt;h1&gt;"+ms+"&lt;/h1&gt;"); };
  3469. Judy.timer(document.body, doooh, ["Doooh!"], 1000);
  3470. * @function
  3471. * @name Judy.timer
  3472. * @param {object|falsy} o
  3473. * - object to apply() arg func on, if desired
  3474. * @param {func} func
  3475. * @param {array|falsy} [args]
  3476. * - arguments to apply() on arg func, if arg o is object (truthy)
  3477. * @param {integer} [delay]
  3478. * - default: zero milliseconds
  3479. */
  3480. this.timer = function(o, func, args, delay) {
  3481. return new self.Timer(o, func, args, delay);
  3482. };
  3483. /**
  3484. * setTimeout alternative - executes the function in try-catch, and supports checking if the function has been executed yet.
  3485. *
  3486. * @example
  3487. var doooh = function(ms) { jQuery(this).html("&lt;h1&gt;"+ms+"&lt;/h1&gt;"); },
  3488. t = new Judy.Timer(document.body, doooh, ["Doooh!"], 1000);
  3489. * @constructor
  3490. * @namespace
  3491. * @name Judy.Timer
  3492. * @param {object|falsy} o
  3493. * - object to apply() arg func on, if desired
  3494. * @param {func} func
  3495. * @param {array|falsy} [args]
  3496. * - arguments to apply() on arg func, if arg o is object (truthy)
  3497. * @param {integer} [delay]
  3498. * - default: zero milliseconds
  3499. */
  3500. this.Timer = function(o, func, args, delay) {
  3501. var a = args || [], fired = false,
  3502. f = o ? function() {
  3503. fired = true;
  3504. try {
  3505. func.apply(o, a);
  3506. }
  3507. catch(er) {}
  3508. } : function() {
  3509. fired = true;
  3510. try {
  3511. func();
  3512. }
  3513. catch(er) {}
  3514. },
  3515. t = window.setTimeout(f, delay || 0);
  3516. /**
  3517. * Check if the function has been executed yet.
  3518. * @function
  3519. * @memberOf Judy.Timer
  3520. * @name Judy.Timer#fired
  3521. * @return {boolean}
  3522. */
  3523. this.fired = function() {
  3524. return fired;
  3525. }
  3526. /**
  3527. * Cancel execution of the function.
  3528. * @function
  3529. * @memberOf Judy.Timer
  3530. * @name Judy.Timer#cancel
  3531. * @return {void}
  3532. */
  3533. this.cancel = function() {
  3534. window.clearTimeout(t);
  3535. };
  3536. };
  3537. };
  3538. (Drupal.Judy = window.Judy = window.judy = new Judy($)).setup();
  3539. })(jQuery);