form.js 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013
  1. /*!
  2. * # Fomantic-UI - Form Validation
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isFunction = $.isFunction || function(obj) {
  13. return typeof obj === "function" && typeof obj.nodeType !== "number";
  14. };
  15. window = (typeof window != 'undefined' && window.Math == Math)
  16. ? window
  17. : (typeof self != 'undefined' && self.Math == Math)
  18. ? self
  19. : Function('return this')()
  20. ;
  21. $.fn.form = function(parameters) {
  22. var
  23. $allModules = $(this),
  24. moduleSelector = $allModules.selector || '',
  25. time = new Date().getTime(),
  26. performance = [],
  27. query = arguments[0],
  28. legacyParameters = arguments[1],
  29. methodInvoked = (typeof query == 'string'),
  30. queryArguments = [].slice.call(arguments, 1),
  31. returnedValue
  32. ;
  33. $allModules
  34. .each(function() {
  35. var
  36. $module = $(this),
  37. element = this,
  38. formErrors = [],
  39. keyHeldDown = false,
  40. // set at run-time
  41. $field,
  42. $group,
  43. $message,
  44. $prompt,
  45. $submit,
  46. $clear,
  47. $reset,
  48. settings,
  49. validation,
  50. metadata,
  51. selector,
  52. className,
  53. regExp,
  54. error,
  55. namespace,
  56. moduleNamespace,
  57. eventNamespace,
  58. submitting = false,
  59. dirty = false,
  60. history = ['clean', 'clean'],
  61. instance,
  62. module
  63. ;
  64. module = {
  65. initialize: function() {
  66. // settings grabbed at run time
  67. module.get.settings();
  68. if(methodInvoked) {
  69. if(instance === undefined) {
  70. module.instantiate();
  71. }
  72. module.invoke(query);
  73. }
  74. else {
  75. if(instance !== undefined) {
  76. instance.invoke('destroy');
  77. }
  78. module.verbose('Initializing form validation', $module, settings);
  79. module.bindEvents();
  80. module.set.defaults();
  81. if (settings.autoCheckRequired) {
  82. module.set.autoCheck();
  83. }
  84. module.instantiate();
  85. }
  86. },
  87. instantiate: function() {
  88. module.verbose('Storing instance of module', module);
  89. instance = module;
  90. $module
  91. .data(moduleNamespace, module)
  92. ;
  93. },
  94. destroy: function() {
  95. module.verbose('Destroying previous module', instance);
  96. module.removeEvents();
  97. $module
  98. .removeData(moduleNamespace)
  99. ;
  100. },
  101. refresh: function() {
  102. module.verbose('Refreshing selector cache');
  103. $field = $module.find(selector.field);
  104. $group = $module.find(selector.group);
  105. $message = $module.find(selector.message);
  106. $prompt = $module.find(selector.prompt);
  107. $submit = $module.find(selector.submit);
  108. $clear = $module.find(selector.clear);
  109. $reset = $module.find(selector.reset);
  110. },
  111. submit: function() {
  112. module.verbose('Submitting form', $module);
  113. submitting = true;
  114. $module.submit();
  115. },
  116. attachEvents: function(selector, action) {
  117. action = action || 'submit';
  118. $(selector).on('click' + eventNamespace, function(event) {
  119. module[action]();
  120. event.preventDefault();
  121. });
  122. },
  123. bindEvents: function() {
  124. module.verbose('Attaching form events');
  125. $module
  126. .on('submit' + eventNamespace, module.validate.form)
  127. .on('blur' + eventNamespace, selector.field, module.event.field.blur)
  128. .on('click' + eventNamespace, selector.submit, module.submit)
  129. .on('click' + eventNamespace, selector.reset, module.reset)
  130. .on('click' + eventNamespace, selector.clear, module.clear)
  131. ;
  132. if(settings.keyboardShortcuts) {
  133. $module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown);
  134. }
  135. $field.each(function(index, el) {
  136. var
  137. $input = $(el),
  138. type = $input.prop('type'),
  139. inputEvent = module.get.changeEvent(type, $input)
  140. ;
  141. $input.on(inputEvent + eventNamespace, module.event.field.change);
  142. });
  143. // Dirty events
  144. if (settings.preventLeaving) {
  145. $(window).on('beforeunload' + eventNamespace, module.event.beforeUnload);
  146. }
  147. $field.on('change click keyup keydown blur', function(e) {
  148. $(this).triggerHandler(e.type + ".dirty");
  149. });
  150. $field.on('change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty', module.determine.isDirty);
  151. $module.on('dirty' + eventNamespace, function(e) {
  152. settings.onDirty.call();
  153. });
  154. $module.on('clean' + eventNamespace, function(e) {
  155. settings.onClean.call();
  156. })
  157. },
  158. clear: function() {
  159. $field.each(function (index, el) {
  160. var
  161. $field = $(el),
  162. $element = $field.parent(),
  163. $fieldGroup = $field.closest($group),
  164. $prompt = $fieldGroup.find(selector.prompt),
  165. $calendar = $field.closest(selector.uiCalendar),
  166. defaultValue = $field.data(metadata.defaultValue) || '',
  167. isCheckbox = $element.is(selector.uiCheckbox),
  168. isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'),
  169. isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')),
  170. isErrored = $fieldGroup.hasClass(className.error)
  171. ;
  172. if(isErrored) {
  173. module.verbose('Resetting error on field', $fieldGroup);
  174. $fieldGroup.removeClass(className.error);
  175. $prompt.remove();
  176. }
  177. if(isDropdown) {
  178. module.verbose('Resetting dropdown value', $element, defaultValue);
  179. $element.dropdown('clear', true);
  180. }
  181. else if(isCheckbox) {
  182. $field.prop('checked', false);
  183. }
  184. else if (isCalendar) {
  185. $calendar.calendar('clear');
  186. }
  187. else {
  188. module.verbose('Resetting field value', $field, defaultValue);
  189. $field.val('');
  190. }
  191. });
  192. },
  193. reset: function() {
  194. $field.each(function (index, el) {
  195. var
  196. $field = $(el),
  197. $element = $field.parent(),
  198. $fieldGroup = $field.closest($group),
  199. $calendar = $field.closest(selector.uiCalendar),
  200. $prompt = $fieldGroup.find(selector.prompt),
  201. defaultValue = $field.data(metadata.defaultValue),
  202. isCheckbox = $element.is(selector.uiCheckbox),
  203. isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'),
  204. isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')),
  205. isErrored = $fieldGroup.hasClass(className.error)
  206. ;
  207. if(defaultValue === undefined) {
  208. return;
  209. }
  210. if(isErrored) {
  211. module.verbose('Resetting error on field', $fieldGroup);
  212. $fieldGroup.removeClass(className.error);
  213. $prompt.remove();
  214. }
  215. if(isDropdown) {
  216. module.verbose('Resetting dropdown value', $element, defaultValue);
  217. $element.dropdown('restore defaults', true);
  218. }
  219. else if(isCheckbox) {
  220. module.verbose('Resetting checkbox value', $element, defaultValue);
  221. $field.prop('checked', defaultValue);
  222. }
  223. else if (isCalendar) {
  224. $calendar.calendar('set date', defaultValue);
  225. }
  226. else {
  227. module.verbose('Resetting field value', $field, defaultValue);
  228. $field.val(defaultValue);
  229. }
  230. });
  231. module.determine.isDirty();
  232. },
  233. determine: {
  234. isValid: function() {
  235. var
  236. allValid = true
  237. ;
  238. $.each(validation, function(fieldName, field) {
  239. if( !( module.validate.field(field, fieldName, true) ) ) {
  240. allValid = false;
  241. }
  242. });
  243. return allValid;
  244. },
  245. isDirty: function(e) {
  246. var formIsDirty = false;
  247. $field.each(function(index, el) {
  248. var
  249. $el = $(el),
  250. isCheckbox = ($el.filter(selector.checkbox).length > 0),
  251. isDirty
  252. ;
  253. if (isCheckbox) {
  254. isDirty = module.is.checkboxDirty($el);
  255. } else {
  256. isDirty = module.is.fieldDirty($el);
  257. }
  258. $el.data(settings.metadata.isDirty, isDirty);
  259. formIsDirty |= isDirty;
  260. });
  261. if (formIsDirty) {
  262. module.set.dirty();
  263. } else {
  264. module.set.clean();
  265. }
  266. if (e && e.namespace === 'dirty') {
  267. e.stopImmediatePropagation();
  268. e.preventDefault();
  269. }
  270. }
  271. },
  272. is: {
  273. bracketedRule: function(rule) {
  274. return (rule.type && rule.type.match(settings.regExp.bracket));
  275. },
  276. shorthandFields: function(fields) {
  277. var
  278. fieldKeys = Object.keys(fields),
  279. firstRule = fields[fieldKeys[0]]
  280. ;
  281. return module.is.shorthandRules(firstRule);
  282. },
  283. // duck type rule test
  284. shorthandRules: function(rules) {
  285. return (typeof rules == 'string' || Array.isArray(rules));
  286. },
  287. empty: function($field) {
  288. if(!$field || $field.length === 0) {
  289. return true;
  290. }
  291. else if($field.is(selector.checkbox)) {
  292. return !$field.is(':checked');
  293. }
  294. else {
  295. return module.is.blank($field);
  296. }
  297. },
  298. blank: function($field) {
  299. return $.trim($field.val()) === '';
  300. },
  301. valid: function(field) {
  302. var
  303. allValid = true
  304. ;
  305. if(field) {
  306. module.verbose('Checking if field is valid', field);
  307. return module.validate.field(validation[field], field, false);
  308. }
  309. else {
  310. module.verbose('Checking if form is valid');
  311. $.each(validation, function(fieldName, field) {
  312. if( !module.is.valid(fieldName) ) {
  313. allValid = false;
  314. }
  315. });
  316. return allValid;
  317. }
  318. },
  319. dirty: function() {
  320. return dirty;
  321. },
  322. clean: function() {
  323. return !dirty;
  324. },
  325. fieldDirty: function($el) {
  326. var initialValue = $el.data(metadata.defaultValue);
  327. // Explicitly check for null/undefined here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work
  328. if (initialValue == null) { initialValue = ''; }
  329. var currentValue = $el.val();
  330. if (currentValue == null) { currentValue = ''; }
  331. // Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison
  332. var boolRegex = /^(true|false)$/i;
  333. var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue);
  334. if (isBoolValue) {
  335. var regex = new RegExp("^" + initialValue + "$", "i");
  336. return !regex.test(currentValue);
  337. }
  338. return currentValue !== initialValue;
  339. },
  340. checkboxDirty: function($el) {
  341. var initialValue = $el.data(metadata.defaultValue);
  342. var currentValue = $el.is(":checked");
  343. return initialValue !== currentValue;
  344. },
  345. justDirty: function() {
  346. return (history[0] === 'dirty');
  347. },
  348. justClean: function() {
  349. return (history[0] === 'clean');
  350. }
  351. },
  352. removeEvents: function() {
  353. $module.off(eventNamespace);
  354. $field.off(eventNamespace);
  355. $submit.off(eventNamespace);
  356. $field.off(eventNamespace);
  357. },
  358. event: {
  359. field: {
  360. keydown: function(event) {
  361. var
  362. $field = $(this),
  363. key = event.which,
  364. isInput = $field.is(selector.input),
  365. isCheckbox = $field.is(selector.checkbox),
  366. isInDropdown = ($field.closest(selector.uiDropdown).length > 0),
  367. keyCode = {
  368. enter : 13,
  369. escape : 27
  370. }
  371. ;
  372. if( key == keyCode.escape) {
  373. module.verbose('Escape key pressed blurring field');
  374. $field
  375. .blur()
  376. ;
  377. }
  378. if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) {
  379. if(!keyHeldDown) {
  380. $field.one('keyup' + eventNamespace, module.event.field.keyup);
  381. module.submit();
  382. module.debug('Enter pressed on input submitting form');
  383. }
  384. keyHeldDown = true;
  385. }
  386. },
  387. keyup: function() {
  388. keyHeldDown = false;
  389. },
  390. blur: function(event) {
  391. var
  392. $field = $(this),
  393. $fieldGroup = $field.closest($group),
  394. validationRules = module.get.validation($field)
  395. ;
  396. if( $fieldGroup.hasClass(className.error) ) {
  397. module.debug('Revalidating field', $field, validationRules);
  398. if(validationRules) {
  399. module.validate.field( validationRules );
  400. }
  401. }
  402. else if(settings.on == 'blur') {
  403. if(validationRules) {
  404. module.validate.field( validationRules );
  405. }
  406. }
  407. },
  408. change: function(event) {
  409. var
  410. $field = $(this),
  411. $fieldGroup = $field.closest($group),
  412. validationRules = module.get.validation($field)
  413. ;
  414. if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) {
  415. clearTimeout(module.timer);
  416. module.timer = setTimeout(function() {
  417. module.debug('Revalidating field', $field, module.get.validation($field));
  418. module.validate.field( validationRules );
  419. }, settings.delay);
  420. }
  421. }
  422. },
  423. beforeUnload: function(event) {
  424. if (module.is.dirty() && !submitting) {
  425. var event = event || window.event;
  426. // For modern browsers
  427. if (event) {
  428. event.returnValue = settings.text.leavingMessage;
  429. }
  430. // For olders...
  431. return settings.text.leavingMessage;
  432. }
  433. }
  434. },
  435. get: {
  436. ancillaryValue: function(rule) {
  437. if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) {
  438. return false;
  439. }
  440. return (rule.value !== undefined)
  441. ? rule.value
  442. : rule.type.match(settings.regExp.bracket)[1] + ''
  443. ;
  444. },
  445. ruleName: function(rule) {
  446. if( module.is.bracketedRule(rule) ) {
  447. return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], '');
  448. }
  449. return rule.type;
  450. },
  451. changeEvent: function(type, $input) {
  452. if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) {
  453. return 'change';
  454. }
  455. else {
  456. return module.get.inputEvent();
  457. }
  458. },
  459. inputEvent: function() {
  460. return (document.createElement('input').oninput !== undefined)
  461. ? 'input'
  462. : (document.createElement('input').onpropertychange !== undefined)
  463. ? 'propertychange'
  464. : 'keyup'
  465. ;
  466. },
  467. fieldsFromShorthand: function(fields) {
  468. var
  469. fullFields = {}
  470. ;
  471. $.each(fields, function(name, rules) {
  472. if(typeof rules == 'string') {
  473. rules = [rules];
  474. }
  475. fullFields[name] = {
  476. rules: []
  477. };
  478. $.each(rules, function(index, rule) {
  479. fullFields[name].rules.push({ type: rule });
  480. });
  481. });
  482. return fullFields;
  483. },
  484. prompt: function(rule, field) {
  485. var
  486. ruleName = module.get.ruleName(rule),
  487. ancillary = module.get.ancillaryValue(rule),
  488. $field = module.get.field(field.identifier),
  489. value = $field.val(),
  490. prompt = $.isFunction(rule.prompt)
  491. ? rule.prompt(value)
  492. : rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule,
  493. requiresValue = (prompt.search('{value}') !== -1),
  494. requiresName = (prompt.search('{name}') !== -1),
  495. $label,
  496. name
  497. ;
  498. if(requiresValue) {
  499. prompt = prompt.replace(/\{value\}/g, $field.val());
  500. }
  501. if(requiresName) {
  502. $label = $field.closest(selector.group).find('label').eq(0);
  503. name = ($label.length == 1)
  504. ? $label.text()
  505. : $field.prop('placeholder') || settings.text.unspecifiedField
  506. ;
  507. prompt = prompt.replace(/\{name\}/g, name);
  508. }
  509. prompt = prompt.replace(/\{identifier\}/g, field.identifier);
  510. prompt = prompt.replace(/\{ruleValue\}/g, ancillary);
  511. if(!rule.prompt) {
  512. module.verbose('Using default validation prompt for type', prompt, ruleName);
  513. }
  514. return prompt;
  515. },
  516. settings: function() {
  517. if($.isPlainObject(parameters)) {
  518. var
  519. keys = Object.keys(parameters),
  520. isLegacySettings = (keys.length > 0)
  521. ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined)
  522. : false
  523. ;
  524. if(isLegacySettings) {
  525. // 1.x (ducktyped)
  526. settings = $.extend(true, {}, $.fn.form.settings, legacyParameters);
  527. validation = $.extend({}, $.fn.form.settings.defaults, parameters);
  528. module.error(settings.error.oldSyntax, element);
  529. module.verbose('Extending settings from legacy parameters', validation, settings);
  530. }
  531. else {
  532. // 2.x
  533. if(parameters.fields && module.is.shorthandFields(parameters.fields)) {
  534. parameters.fields = module.get.fieldsFromShorthand(parameters.fields);
  535. }
  536. settings = $.extend(true, {}, $.fn.form.settings, parameters);
  537. validation = $.extend({}, $.fn.form.settings.defaults, settings.fields);
  538. module.verbose('Extending settings', validation, settings);
  539. }
  540. }
  541. else {
  542. settings = $.fn.form.settings;
  543. validation = $.fn.form.settings.defaults;
  544. module.verbose('Using default form validation', validation, settings);
  545. }
  546. // shorthand
  547. namespace = settings.namespace;
  548. metadata = settings.metadata;
  549. selector = settings.selector;
  550. className = settings.className;
  551. regExp = settings.regExp;
  552. error = settings.error;
  553. moduleNamespace = 'module-' + namespace;
  554. eventNamespace = '.' + namespace;
  555. // grab instance
  556. instance = $module.data(moduleNamespace);
  557. // refresh selector cache
  558. module.refresh();
  559. },
  560. field: function(identifier) {
  561. module.verbose('Finding field with identifier', identifier);
  562. identifier = module.escape.string(identifier);
  563. var t;
  564. if((t=$field.filter('#' + identifier)).length > 0 ) {
  565. return t;
  566. }
  567. if((t=$field.filter('[name="' + identifier +'"]')).length > 0 ) {
  568. return t;
  569. }
  570. if((t=$field.filter('[name="' + identifier +'[]"]')).length > 0 ) {
  571. return t;
  572. }
  573. if((t=$field.filter('[data-' + metadata.validate + '="'+ identifier +'"]')).length > 0 ) {
  574. return t;
  575. }
  576. return $('<input/>');
  577. },
  578. fields: function(fields) {
  579. var
  580. $fields = $()
  581. ;
  582. $.each(fields, function(index, name) {
  583. $fields = $fields.add( module.get.field(name) );
  584. });
  585. return $fields;
  586. },
  587. validation: function($field) {
  588. var
  589. fieldValidation,
  590. identifier
  591. ;
  592. if(!validation) {
  593. return false;
  594. }
  595. $.each(validation, function(fieldName, field) {
  596. identifier = field.identifier || fieldName;
  597. $.each(module.get.field(identifier), function(index, groupField) {
  598. if(groupField == $field[0]) {
  599. field.identifier = identifier;
  600. fieldValidation = field;
  601. return false;
  602. }
  603. });
  604. });
  605. return fieldValidation || false;
  606. },
  607. value: function (field) {
  608. var
  609. fields = [],
  610. results
  611. ;
  612. fields.push(field);
  613. results = module.get.values.call(element, fields);
  614. return results[field];
  615. },
  616. values: function (fields) {
  617. var
  618. $fields = Array.isArray(fields)
  619. ? module.get.fields(fields)
  620. : $field,
  621. values = {}
  622. ;
  623. $fields.each(function(index, field) {
  624. var
  625. $field = $(field),
  626. $calendar = $field.closest(selector.uiCalendar),
  627. name = $field.prop('name'),
  628. value = $field.val(),
  629. isCheckbox = $field.is(selector.checkbox),
  630. isRadio = $field.is(selector.radio),
  631. isMultiple = (name.indexOf('[]') !== -1),
  632. isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')),
  633. isChecked = (isCheckbox)
  634. ? $field.is(':checked')
  635. : false
  636. ;
  637. if(name) {
  638. if(isMultiple) {
  639. name = name.replace('[]', '');
  640. if(!values[name]) {
  641. values[name] = [];
  642. }
  643. if(isCheckbox) {
  644. if(isChecked) {
  645. values[name].push(value || true);
  646. }
  647. else {
  648. values[name].push(false);
  649. }
  650. }
  651. else {
  652. values[name].push(value);
  653. }
  654. }
  655. else {
  656. if(isRadio) {
  657. if(values[name] === undefined || values[name] === false) {
  658. values[name] = (isChecked)
  659. ? value || true
  660. : false
  661. ;
  662. }
  663. }
  664. else if(isCheckbox) {
  665. if(isChecked) {
  666. values[name] = value || true;
  667. }
  668. else {
  669. values[name] = false;
  670. }
  671. }
  672. else if(isCalendar) {
  673. var date = $calendar.calendar('get date');
  674. if (date !== null) {
  675. if (settings.dateHandling == 'date') {
  676. values[name] = date;
  677. } else if(settings.dateHandling == 'input') {
  678. values[name] = $calendar.calendar('get input date')
  679. } else if (settings.dateHandling == 'formatter') {
  680. var type = $calendar.calendar('setting', 'type');
  681. switch(type) {
  682. case 'date':
  683. values[name] = settings.formatter.date(date);
  684. break;
  685. case 'datetime':
  686. values[name] = settings.formatter.datetime(date);
  687. break;
  688. case 'time':
  689. values[name] = settings.formatter.time(date);
  690. break;
  691. case 'month':
  692. values[name] = settings.formatter.month(date);
  693. break;
  694. case 'year':
  695. values[name] = settings.formatter.year(date);
  696. break;
  697. default:
  698. module.debug('Wrong calendar mode', $calendar, type);
  699. values[name] = '';
  700. }
  701. }
  702. } else {
  703. values[name] = '';
  704. }
  705. } else {
  706. values[name] = value;
  707. }
  708. }
  709. }
  710. });
  711. return values;
  712. },
  713. dirtyFields: function() {
  714. return $field.filter(function(index, e) {
  715. return $(e).data(metadata.isDirty);
  716. });
  717. }
  718. },
  719. has: {
  720. field: function(identifier) {
  721. module.verbose('Checking for existence of a field with identifier', identifier);
  722. identifier = module.escape.string(identifier);
  723. if(typeof identifier !== 'string') {
  724. module.error(error.identifier, identifier);
  725. }
  726. if($field.filter('#' + identifier).length > 0 ) {
  727. return true;
  728. }
  729. else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
  730. return true;
  731. }
  732. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
  733. return true;
  734. }
  735. return false;
  736. }
  737. },
  738. can: {
  739. useElement: function(element){
  740. if ($.fn[element] !== undefined) {
  741. return true;
  742. }
  743. module.error(error.noElement.replace('{element}',element));
  744. return false;
  745. }
  746. },
  747. escape: {
  748. string: function(text) {
  749. text = String(text);
  750. return text.replace(regExp.escape, '\\$&');
  751. }
  752. },
  753. add: {
  754. // alias
  755. rule: function(name, rules) {
  756. module.add.field(name, rules);
  757. },
  758. field: function(name, rules) {
  759. // Validation should have at least a standard format
  760. if(validation[name] === undefined || validation[name].rules === undefined) {
  761. validation[name] = {
  762. rules: []
  763. };
  764. }
  765. var
  766. newValidation = {
  767. rules: []
  768. }
  769. ;
  770. if(module.is.shorthandRules(rules)) {
  771. rules = Array.isArray(rules)
  772. ? rules
  773. : [rules]
  774. ;
  775. $.each(rules, function(_index, rule) {
  776. newValidation.rules.push({ type: rule });
  777. });
  778. }
  779. else {
  780. newValidation.rules = rules.rules;
  781. }
  782. // For each new rule, check if there's not already one with the same type
  783. $.each(newValidation.rules, function (_index, rule) {
  784. if ($.grep(validation[name].rules, function(item){ return item.type == rule.type; }).length == 0) {
  785. validation[name].rules.push(rule);
  786. }
  787. });
  788. module.debug('Adding rules', newValidation.rules, validation);
  789. },
  790. fields: function(fields) {
  791. var
  792. newValidation
  793. ;
  794. if(fields && module.is.shorthandFields(fields)) {
  795. newValidation = module.get.fieldsFromShorthand(fields);
  796. }
  797. else {
  798. newValidation = fields;
  799. }
  800. validation = $.extend({}, validation, newValidation);
  801. },
  802. prompt: function(identifier, errors, internal) {
  803. var
  804. $field = module.get.field(identifier),
  805. $fieldGroup = $field.closest($group),
  806. $prompt = $fieldGroup.children(selector.prompt),
  807. promptExists = ($prompt.length !== 0)
  808. ;
  809. errors = (typeof errors == 'string')
  810. ? [errors]
  811. : errors
  812. ;
  813. module.verbose('Adding field error state', identifier);
  814. if(!internal) {
  815. $fieldGroup
  816. .addClass(className.error)
  817. ;
  818. }
  819. if(settings.inline) {
  820. if(!promptExists) {
  821. $prompt = settings.templates.prompt(errors, className.label);
  822. $prompt
  823. .appendTo($fieldGroup)
  824. ;
  825. }
  826. $prompt
  827. .html(errors[0])
  828. ;
  829. if(!promptExists) {
  830. if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) {
  831. module.verbose('Displaying error with css transition', settings.transition);
  832. $prompt.transition(settings.transition + ' in', settings.duration);
  833. }
  834. else {
  835. module.verbose('Displaying error with fallback javascript animation');
  836. $prompt
  837. .fadeIn(settings.duration)
  838. ;
  839. }
  840. }
  841. else {
  842. module.verbose('Inline errors are disabled, no inline error added', identifier);
  843. }
  844. }
  845. },
  846. errors: function(errors) {
  847. module.debug('Adding form error messages', errors);
  848. module.set.error();
  849. $message
  850. .html( settings.templates.error(errors) )
  851. ;
  852. }
  853. },
  854. remove: {
  855. rule: function(field, rule) {
  856. var
  857. rules = Array.isArray(rule)
  858. ? rule
  859. : [rule]
  860. ;
  861. if(validation[field] === undefined || !Array.isArray(validation[field].rules)) {
  862. return;
  863. }
  864. if(rule === undefined) {
  865. module.debug('Removed all rules');
  866. validation[field].rules = [];
  867. return;
  868. }
  869. $.each(validation[field].rules, function(index, rule) {
  870. if(rule && rules.indexOf(rule.type) !== -1) {
  871. module.debug('Removed rule', rule.type);
  872. validation[field].rules.splice(index, 1);
  873. }
  874. });
  875. },
  876. field: function(field) {
  877. var
  878. fields = Array.isArray(field)
  879. ? field
  880. : [field]
  881. ;
  882. $.each(fields, function(index, field) {
  883. module.remove.rule(field);
  884. });
  885. },
  886. // alias
  887. rules: function(field, rules) {
  888. if(Array.isArray(field)) {
  889. $.each(field, function(index, field) {
  890. module.remove.rule(field, rules);
  891. });
  892. }
  893. else {
  894. module.remove.rule(field, rules);
  895. }
  896. },
  897. fields: function(fields) {
  898. module.remove.field(fields);
  899. },
  900. prompt: function(identifier) {
  901. var
  902. $field = module.get.field(identifier),
  903. $fieldGroup = $field.closest($group),
  904. $prompt = $fieldGroup.children(selector.prompt)
  905. ;
  906. $fieldGroup
  907. .removeClass(className.error)
  908. ;
  909. if(settings.inline && $prompt.is(':visible')) {
  910. module.verbose('Removing prompt for field', identifier);
  911. if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) {
  912. $prompt.transition(settings.transition + ' out', settings.duration, function() {
  913. $prompt.remove();
  914. });
  915. }
  916. else {
  917. $prompt
  918. .fadeOut(settings.duration, function(){
  919. $prompt.remove();
  920. })
  921. ;
  922. }
  923. }
  924. }
  925. },
  926. set: {
  927. success: function() {
  928. $module
  929. .removeClass(className.error)
  930. .addClass(className.success)
  931. ;
  932. },
  933. defaults: function () {
  934. $field.each(function (index, el) {
  935. var
  936. $el = $(el),
  937. $parent = $el.parent(),
  938. isCheckbox = ($el.filter(selector.checkbox).length > 0),
  939. isDropdown = $parent.is(selector.uiDropdown) && module.can.useElement('dropdown'),
  940. $calendar = $el.closest(selector.uiCalendar),
  941. isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')),
  942. value = (isCheckbox)
  943. ? $el.is(':checked')
  944. : $el.val()
  945. ;
  946. if (isDropdown) {
  947. $parent.dropdown('save defaults');
  948. }
  949. else if (isCalendar) {
  950. $calendar.calendar('refresh');
  951. }
  952. $el.data(metadata.defaultValue, value);
  953. $el.data(metadata.isDirty, false);
  954. });
  955. },
  956. error: function() {
  957. $module
  958. .removeClass(className.success)
  959. .addClass(className.error)
  960. ;
  961. },
  962. value: function (field, value) {
  963. var
  964. fields = {}
  965. ;
  966. fields[field] = value;
  967. return module.set.values.call(element, fields);
  968. },
  969. values: function (fields) {
  970. if($.isEmptyObject(fields)) {
  971. return;
  972. }
  973. $.each(fields, function(key, value) {
  974. var
  975. $field = module.get.field(key),
  976. $element = $field.parent(),
  977. $calendar = $field.closest(selector.uiCalendar),
  978. isMultiple = Array.isArray(value),
  979. isCheckbox = $element.is(selector.uiCheckbox) && module.can.useElement('checkbox'),
  980. isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'),
  981. isRadio = ($field.is(selector.radio) && isCheckbox),
  982. isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')),
  983. fieldExists = ($field.length > 0),
  984. $multipleField
  985. ;
  986. if(fieldExists) {
  987. if(isMultiple && isCheckbox) {
  988. module.verbose('Selecting multiple', value, $field);
  989. $element.checkbox('uncheck');
  990. $.each(value, function(index, value) {
  991. $multipleField = $field.filter('[value="' + value + '"]');
  992. $element = $multipleField.parent();
  993. if($multipleField.length > 0) {
  994. $element.checkbox('check');
  995. }
  996. });
  997. }
  998. else if(isRadio) {
  999. module.verbose('Selecting radio value', value, $field);
  1000. $field.filter('[value="' + value + '"]')
  1001. .parent(selector.uiCheckbox)
  1002. .checkbox('check')
  1003. ;
  1004. }
  1005. else if(isCheckbox) {
  1006. module.verbose('Setting checkbox value', value, $element);
  1007. if(value === true || value === 1) {
  1008. $element.checkbox('check');
  1009. }
  1010. else {
  1011. $element.checkbox('uncheck');
  1012. }
  1013. }
  1014. else if(isDropdown) {
  1015. module.verbose('Setting dropdown value', value, $element);
  1016. $element.dropdown('set selected', value);
  1017. }
  1018. else if (isCalendar) {
  1019. $calendar.calendar('set date',value);
  1020. }
  1021. else {
  1022. module.verbose('Setting field value', value, $field);
  1023. $field.val(value);
  1024. }
  1025. }
  1026. });
  1027. },
  1028. dirty: function() {
  1029. module.verbose('Setting state dirty');
  1030. dirty = true;
  1031. history[0] = history[1];
  1032. history[1] = 'dirty';
  1033. if (module.is.justClean()) {
  1034. $module.trigger('dirty');
  1035. }
  1036. },
  1037. clean: function() {
  1038. module.verbose('Setting state clean');
  1039. dirty = false;
  1040. history[0] = history[1];
  1041. history[1] = 'clean';
  1042. if (module.is.justDirty()) {
  1043. $module.trigger('clean');
  1044. }
  1045. },
  1046. asClean: function() {
  1047. module.set.defaults();
  1048. module.set.clean();
  1049. },
  1050. asDirty: function() {
  1051. module.set.defaults();
  1052. module.set.dirty();
  1053. },
  1054. autoCheck: function() {
  1055. module.debug('Enabling auto check on required fields');
  1056. $field.each(function (_index, el) {
  1057. var
  1058. $el = $(el),
  1059. $elGroup = $(el).closest($group),
  1060. isCheckbox = ($el.filter(selector.checkbox).length > 0),
  1061. isRequired = $el.prop('required') || $elGroup.hasClass(className.required) || $elGroup.parent().hasClass(className.required),
  1062. isDisabled = $el.prop('disabled') || $elGroup.hasClass(className.disabled) || $elGroup.parent().hasClass(className.disabled),
  1063. validation = module.get.validation($el),
  1064. hasEmptyRule = validation
  1065. ? $.grep(validation.rules, function(rule) { return rule.type == "empty" }) !== 0
  1066. : false,
  1067. identifier = validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate)
  1068. ;
  1069. if (isRequired && !isDisabled && !hasEmptyRule && identifier !== undefined) {
  1070. if (isCheckbox) {
  1071. module.verbose("Adding 'checked' rule on field", identifier);
  1072. module.add.rule(identifier, "checked");
  1073. } else {
  1074. module.verbose("Adding 'empty' rule on field", identifier);
  1075. module.add.rule(identifier, "empty");
  1076. }
  1077. }
  1078. });
  1079. }
  1080. },
  1081. validate: {
  1082. form: function(event, ignoreCallbacks) {
  1083. var values = module.get.values();
  1084. // input keydown event will fire submit repeatedly by browser default
  1085. if(keyHeldDown) {
  1086. return false;
  1087. }
  1088. // reset errors
  1089. formErrors = [];
  1090. if( module.determine.isValid() ) {
  1091. module.debug('Form has no validation errors, submitting');
  1092. module.set.success();
  1093. if(ignoreCallbacks !== true) {
  1094. return settings.onSuccess.call(element, event, values);
  1095. }
  1096. }
  1097. else {
  1098. module.debug('Form has errors');
  1099. module.set.error();
  1100. if(!settings.inline) {
  1101. module.add.errors(formErrors);
  1102. }
  1103. // prevent ajax submit
  1104. if(event && $module.data('moduleApi') !== undefined) {
  1105. event.stopImmediatePropagation();
  1106. }
  1107. if(ignoreCallbacks !== true) {
  1108. return settings.onFailure.call(element, formErrors, values);
  1109. }
  1110. }
  1111. },
  1112. // takes a validation object and returns whether field passes validation
  1113. field: function(field, fieldName, showErrors) {
  1114. showErrors = (showErrors !== undefined)
  1115. ? showErrors
  1116. : true
  1117. ;
  1118. if(typeof field == 'string') {
  1119. module.verbose('Validating field', field);
  1120. fieldName = field;
  1121. field = validation[field];
  1122. }
  1123. var
  1124. identifier = field.identifier || fieldName,
  1125. $field = module.get.field(identifier),
  1126. $dependsField = (field.depends)
  1127. ? module.get.field(field.depends)
  1128. : false,
  1129. fieldValid = true,
  1130. fieldErrors = []
  1131. ;
  1132. if(!field.identifier) {
  1133. module.debug('Using field name as identifier', identifier);
  1134. field.identifier = identifier;
  1135. }
  1136. var isDisabled = true;
  1137. $.each($field, function(){
  1138. if(!$(this).prop('disabled')) {
  1139. isDisabled = false;
  1140. return false;
  1141. }
  1142. });
  1143. if(isDisabled) {
  1144. module.debug('Field is disabled. Skipping', identifier);
  1145. }
  1146. else if(field.optional && module.is.blank($field)){
  1147. module.debug('Field is optional and blank. Skipping', identifier);
  1148. }
  1149. else if(field.depends && module.is.empty($dependsField)) {
  1150. module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField);
  1151. }
  1152. else if(field.rules !== undefined) {
  1153. $field.closest($group).removeClass(className.error);
  1154. $.each(field.rules, function(index, rule) {
  1155. if( module.has.field(identifier)) {
  1156. var invalidFields = module.validate.rule(field, rule,true) || [];
  1157. if (invalidFields.length>0){
  1158. module.debug('Field is invalid', identifier, rule.type);
  1159. fieldErrors.push(module.get.prompt(rule, field));
  1160. fieldValid = false;
  1161. if(showErrors){
  1162. $(invalidFields).closest($group).addClass(className.error);
  1163. }
  1164. }
  1165. }
  1166. });
  1167. }
  1168. if(fieldValid) {
  1169. if(showErrors) {
  1170. module.remove.prompt(identifier, fieldErrors);
  1171. settings.onValid.call($field);
  1172. }
  1173. }
  1174. else {
  1175. if(showErrors) {
  1176. formErrors = formErrors.concat(fieldErrors);
  1177. module.add.prompt(identifier, fieldErrors, true);
  1178. settings.onInvalid.call($field, fieldErrors);
  1179. }
  1180. return false;
  1181. }
  1182. return true;
  1183. },
  1184. // takes validation rule and returns whether field passes rule
  1185. rule: function(field, rule, internal) {
  1186. var
  1187. $field = module.get.field(field.identifier),
  1188. ancillary = module.get.ancillaryValue(rule),
  1189. ruleName = module.get.ruleName(rule),
  1190. ruleFunction = settings.rules[ruleName],
  1191. invalidFields = [],
  1192. isCheckbox = $field.is(selector.checkbox),
  1193. isValid = function(field){
  1194. var value = (isCheckbox ? $(field).filter(':checked').val() : $(field).val());
  1195. // cast to string avoiding encoding special values
  1196. value = (value === undefined || value === '' || value === null)
  1197. ? ''
  1198. : (settings.shouldTrim) ? $.trim(value + '') : String(value + '')
  1199. ;
  1200. return ruleFunction.call(field, value, ancillary, $module);
  1201. }
  1202. ;
  1203. if( !$.isFunction(ruleFunction) ) {
  1204. module.error(error.noRule, ruleName);
  1205. return;
  1206. }
  1207. if(isCheckbox) {
  1208. if (!isValid($field)) {
  1209. invalidFields = $field;
  1210. }
  1211. } else {
  1212. $.each($field, function (index, field) {
  1213. if (!isValid(field)) {
  1214. invalidFields.push(field);
  1215. }
  1216. });
  1217. }
  1218. return internal ? invalidFields : !(invalidFields.length>0);
  1219. }
  1220. },
  1221. setting: function(name, value) {
  1222. if( $.isPlainObject(name) ) {
  1223. $.extend(true, settings, name);
  1224. }
  1225. else if(value !== undefined) {
  1226. settings[name] = value;
  1227. }
  1228. else {
  1229. return settings[name];
  1230. }
  1231. },
  1232. internal: function(name, value) {
  1233. if( $.isPlainObject(name) ) {
  1234. $.extend(true, module, name);
  1235. }
  1236. else if(value !== undefined) {
  1237. module[name] = value;
  1238. }
  1239. else {
  1240. return module[name];
  1241. }
  1242. },
  1243. debug: function() {
  1244. if(!settings.silent && settings.debug) {
  1245. if(settings.performance) {
  1246. module.performance.log(arguments);
  1247. }
  1248. else {
  1249. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1250. module.debug.apply(console, arguments);
  1251. }
  1252. }
  1253. },
  1254. verbose: function() {
  1255. if(!settings.silent && settings.verbose && settings.debug) {
  1256. if(settings.performance) {
  1257. module.performance.log(arguments);
  1258. }
  1259. else {
  1260. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1261. module.verbose.apply(console, arguments);
  1262. }
  1263. }
  1264. },
  1265. error: function() {
  1266. if(!settings.silent) {
  1267. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1268. module.error.apply(console, arguments);
  1269. }
  1270. },
  1271. performance: {
  1272. log: function(message) {
  1273. var
  1274. currentTime,
  1275. executionTime,
  1276. previousTime
  1277. ;
  1278. if(settings.performance) {
  1279. currentTime = new Date().getTime();
  1280. previousTime = time || currentTime;
  1281. executionTime = currentTime - previousTime;
  1282. time = currentTime;
  1283. performance.push({
  1284. 'Name' : message[0],
  1285. 'Arguments' : [].slice.call(message, 1) || '',
  1286. 'Element' : element,
  1287. 'Execution Time' : executionTime
  1288. });
  1289. }
  1290. clearTimeout(module.performance.timer);
  1291. module.performance.timer = setTimeout(module.performance.display, 500);
  1292. },
  1293. display: function() {
  1294. var
  1295. title = settings.name + ':',
  1296. totalTime = 0
  1297. ;
  1298. time = false;
  1299. clearTimeout(module.performance.timer);
  1300. $.each(performance, function(index, data) {
  1301. totalTime += data['Execution Time'];
  1302. });
  1303. title += ' ' + totalTime + 'ms';
  1304. if(moduleSelector) {
  1305. title += ' \'' + moduleSelector + '\'';
  1306. }
  1307. if($allModules.length > 1) {
  1308. title += ' ' + '(' + $allModules.length + ')';
  1309. }
  1310. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1311. console.groupCollapsed(title);
  1312. if(console.table) {
  1313. console.table(performance);
  1314. }
  1315. else {
  1316. $.each(performance, function(index, data) {
  1317. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1318. });
  1319. }
  1320. console.groupEnd();
  1321. }
  1322. performance = [];
  1323. }
  1324. },
  1325. invoke: function(query, passedArguments, context) {
  1326. var
  1327. object = instance,
  1328. maxDepth,
  1329. found,
  1330. response
  1331. ;
  1332. passedArguments = passedArguments || queryArguments;
  1333. context = element || context;
  1334. if(typeof query == 'string' && object !== undefined) {
  1335. query = query.split(/[\. ]/);
  1336. maxDepth = query.length - 1;
  1337. $.each(query, function(depth, value) {
  1338. var camelCaseValue = (depth != maxDepth)
  1339. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1340. : query
  1341. ;
  1342. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1343. object = object[camelCaseValue];
  1344. }
  1345. else if( object[camelCaseValue] !== undefined ) {
  1346. found = object[camelCaseValue];
  1347. return false;
  1348. }
  1349. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1350. object = object[value];
  1351. }
  1352. else if( object[value] !== undefined ) {
  1353. found = object[value];
  1354. return false;
  1355. }
  1356. else {
  1357. return false;
  1358. }
  1359. });
  1360. }
  1361. if( $.isFunction( found ) ) {
  1362. response = found.apply(context, passedArguments);
  1363. }
  1364. else if(found !== undefined) {
  1365. response = found;
  1366. }
  1367. if(Array.isArray(returnedValue)) {
  1368. returnedValue.push(response);
  1369. }
  1370. else if(returnedValue !== undefined) {
  1371. returnedValue = [returnedValue, response];
  1372. }
  1373. else if(response !== undefined) {
  1374. returnedValue = response;
  1375. }
  1376. return found;
  1377. }
  1378. };
  1379. module.initialize();
  1380. })
  1381. ;
  1382. return (returnedValue !== undefined)
  1383. ? returnedValue
  1384. : this
  1385. ;
  1386. };
  1387. $.fn.form.settings = {
  1388. name : 'Form',
  1389. namespace : 'form',
  1390. debug : false,
  1391. verbose : false,
  1392. performance : true,
  1393. fields : false,
  1394. keyboardShortcuts : true,
  1395. on : 'submit',
  1396. inline : false,
  1397. delay : 200,
  1398. revalidate : true,
  1399. shouldTrim : true,
  1400. transition : 'scale',
  1401. duration : 200,
  1402. autoCheckRequired : false,
  1403. preventLeaving : false,
  1404. dateHandling : 'date', // 'date', 'input', 'formatter'
  1405. onValid : function() {},
  1406. onInvalid : function() {},
  1407. onSuccess : function() { return true; },
  1408. onFailure : function() { return false; },
  1409. onDirty : function() {},
  1410. onClean : function() {},
  1411. metadata : {
  1412. defaultValue : 'default',
  1413. validate : 'validate',
  1414. isDirty : 'isDirty'
  1415. },
  1416. regExp: {
  1417. htmlID : /^[a-zA-Z][\w:.-]*$/g,
  1418. bracket : /\[(.*)\]/i,
  1419. decimal : /^\d+\.?\d*$/,
  1420. email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,
  1421. escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|:,=@]/g,
  1422. flags : /^\/(.*)\/(.*)?/,
  1423. integer : /^\-?\d+$/,
  1424. number : /^\-?\d*(\.\d+)?$/,
  1425. url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i
  1426. },
  1427. text: {
  1428. unspecifiedRule : 'Please enter a valid value',
  1429. unspecifiedField : 'This field',
  1430. leavingMessage : 'There are unsaved changes on this page which will be discarded if you continue.'
  1431. },
  1432. prompt: {
  1433. empty : '{name} must have a value',
  1434. checked : '{name} must be checked',
  1435. email : '{name} must be a valid e-mail',
  1436. url : '{name} must be a valid url',
  1437. regExp : '{name} is not formatted correctly',
  1438. integer : '{name} must be an integer',
  1439. decimal : '{name} must be a decimal number',
  1440. number : '{name} must be set to a number',
  1441. is : '{name} must be "{ruleValue}"',
  1442. isExactly : '{name} must be exactly "{ruleValue}"',
  1443. not : '{name} cannot be set to "{ruleValue}"',
  1444. notExactly : '{name} cannot be set to exactly "{ruleValue}"',
  1445. contain : '{name} must contain "{ruleValue}"',
  1446. containExactly : '{name} must contain exactly "{ruleValue}"',
  1447. doesntContain : '{name} cannot contain "{ruleValue}"',
  1448. doesntContainExactly : '{name} cannot contain exactly "{ruleValue}"',
  1449. minLength : '{name} must be at least {ruleValue} characters',
  1450. length : '{name} must be at least {ruleValue} characters',
  1451. exactLength : '{name} must be exactly {ruleValue} characters',
  1452. maxLength : '{name} cannot be longer than {ruleValue} characters',
  1453. match : '{name} must match {ruleValue} field',
  1454. different : '{name} must have a different value than {ruleValue} field',
  1455. creditCard : '{name} must be a valid credit card number',
  1456. minCount : '{name} must have at least {ruleValue} choices',
  1457. exactCount : '{name} must have exactly {ruleValue} choices',
  1458. maxCount : '{name} must have {ruleValue} or less choices'
  1459. },
  1460. selector : {
  1461. checkbox : 'input[type="checkbox"], input[type="radio"]',
  1462. clear : '.clear',
  1463. field : 'input, textarea, select',
  1464. group : '.field',
  1465. input : 'input',
  1466. message : '.error.message',
  1467. prompt : '.prompt.label',
  1468. radio : 'input[type="radio"]',
  1469. reset : '.reset:not([type="reset"])',
  1470. submit : '.submit:not([type="submit"])',
  1471. uiCheckbox : '.ui.checkbox',
  1472. uiDropdown : '.ui.dropdown',
  1473. uiCalendar : '.ui.calendar'
  1474. },
  1475. className : {
  1476. error : 'error',
  1477. label : 'ui basic red pointing prompt label',
  1478. pressed : 'down',
  1479. success : 'success',
  1480. required : 'required',
  1481. disabled : 'disabled'
  1482. },
  1483. error: {
  1484. identifier : 'You must specify a string identifier for each field',
  1485. method : 'The method you called is not defined.',
  1486. noRule : 'There is no rule matching the one you specified',
  1487. oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.',
  1488. noElement : 'This module requires ui {element}'
  1489. },
  1490. templates: {
  1491. // template that produces error message
  1492. error: function(errors) {
  1493. var
  1494. html = '<ul class="list">'
  1495. ;
  1496. $.each(errors, function(index, value) {
  1497. html += '<li>' + value + '</li>';
  1498. });
  1499. html += '</ul>';
  1500. return $(html);
  1501. },
  1502. // template that produces label
  1503. prompt: function(errors, labelClasses) {
  1504. return $('<div/>')
  1505. .addClass(labelClasses)
  1506. .html(errors[0])
  1507. ;
  1508. }
  1509. },
  1510. formatter: {
  1511. date: function(date) {
  1512. return Intl.DateTimeFormat('en-GB').format(date);
  1513. },
  1514. datetime: function(date) {
  1515. return Intl.DateTimeFormat('en-GB', {
  1516. year: "numeric",
  1517. month: "2-digit",
  1518. day: "2-digit",
  1519. hour: '2-digit',
  1520. minute: '2-digit',
  1521. second: '2-digit'
  1522. }).format(date);
  1523. },
  1524. time: function(date) {
  1525. return Intl.DateTimeFormat('en-GB', {
  1526. hour: '2-digit',
  1527. minute: '2-digit',
  1528. second: '2-digit'
  1529. }).format(date);
  1530. },
  1531. month: function(date) {
  1532. return Intl.DateTimeFormat('en-GB', {
  1533. month: '2-digit',
  1534. year: 'numeric'
  1535. }).format(date);
  1536. },
  1537. year: function(date) {
  1538. return Intl.DateTimeFormat('en-GB', {
  1539. year: 'numeric'
  1540. }).format(date);
  1541. }
  1542. },
  1543. rules: {
  1544. // is not empty or blank string
  1545. empty: function(value) {
  1546. return !(value === undefined || '' === value || Array.isArray(value) && value.length === 0);
  1547. },
  1548. // checkbox checked
  1549. checked: function() {
  1550. return ($(this).filter(':checked').length > 0);
  1551. },
  1552. // is most likely an email
  1553. email: function(value){
  1554. return $.fn.form.settings.regExp.email.test(value);
  1555. },
  1556. // value is most likely url
  1557. url: function(value) {
  1558. return $.fn.form.settings.regExp.url.test(value);
  1559. },
  1560. // matches specified regExp
  1561. regExp: function(value, regExp) {
  1562. if(regExp instanceof RegExp) {
  1563. return value.match(regExp);
  1564. }
  1565. var
  1566. regExpParts = regExp.match($.fn.form.settings.regExp.flags),
  1567. flags
  1568. ;
  1569. // regular expression specified as /baz/gi (flags)
  1570. if(regExpParts) {
  1571. regExp = (regExpParts.length >= 2)
  1572. ? regExpParts[1]
  1573. : regExp
  1574. ;
  1575. flags = (regExpParts.length >= 3)
  1576. ? regExpParts[2]
  1577. : ''
  1578. ;
  1579. }
  1580. return value.match( new RegExp(regExp, flags) );
  1581. },
  1582. // is valid integer or matches range
  1583. integer: function(value, range) {
  1584. var
  1585. intRegExp = $.fn.form.settings.regExp.integer,
  1586. min,
  1587. max,
  1588. parts
  1589. ;
  1590. if( !range || ['', '..'].indexOf(range) !== -1) {
  1591. // do nothing
  1592. }
  1593. else if(range.indexOf('..') == -1) {
  1594. if(intRegExp.test(range)) {
  1595. min = max = range - 0;
  1596. }
  1597. }
  1598. else {
  1599. parts = range.split('..', 2);
  1600. if(intRegExp.test(parts[0])) {
  1601. min = parts[0] - 0;
  1602. }
  1603. if(intRegExp.test(parts[1])) {
  1604. max = parts[1] - 0;
  1605. }
  1606. }
  1607. return (
  1608. intRegExp.test(value) &&
  1609. (min === undefined || value >= min) &&
  1610. (max === undefined || value <= max)
  1611. );
  1612. },
  1613. // is valid number (with decimal)
  1614. decimal: function(value) {
  1615. return $.fn.form.settings.regExp.decimal.test(value);
  1616. },
  1617. // is valid number
  1618. number: function(value) {
  1619. return $.fn.form.settings.regExp.number.test(value);
  1620. },
  1621. // is value (case insensitive)
  1622. is: function(value, text) {
  1623. text = (typeof text == 'string')
  1624. ? text.toLowerCase()
  1625. : text
  1626. ;
  1627. value = (typeof value == 'string')
  1628. ? value.toLowerCase()
  1629. : value
  1630. ;
  1631. return (value == text);
  1632. },
  1633. // is value
  1634. isExactly: function(value, text) {
  1635. return (value == text);
  1636. },
  1637. // value is not another value (case insensitive)
  1638. not: function(value, notValue) {
  1639. value = (typeof value == 'string')
  1640. ? value.toLowerCase()
  1641. : value
  1642. ;
  1643. notValue = (typeof notValue == 'string')
  1644. ? notValue.toLowerCase()
  1645. : notValue
  1646. ;
  1647. return (value != notValue);
  1648. },
  1649. // value is not another value (case sensitive)
  1650. notExactly: function(value, notValue) {
  1651. return (value != notValue);
  1652. },
  1653. // value contains text (insensitive)
  1654. contains: function(value, text) {
  1655. // escape regex characters
  1656. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1657. return (value.search( new RegExp(text, 'i') ) !== -1);
  1658. },
  1659. // value contains text (case sensitive)
  1660. containsExactly: function(value, text) {
  1661. // escape regex characters
  1662. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1663. return (value.search( new RegExp(text) ) !== -1);
  1664. },
  1665. // value contains text (insensitive)
  1666. doesntContain: function(value, text) {
  1667. // escape regex characters
  1668. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1669. return (value.search( new RegExp(text, 'i') ) === -1);
  1670. },
  1671. // value contains text (case sensitive)
  1672. doesntContainExactly: function(value, text) {
  1673. // escape regex characters
  1674. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1675. return (value.search( new RegExp(text) ) === -1);
  1676. },
  1677. // is at least string length
  1678. minLength: function(value, requiredLength) {
  1679. return (value !== undefined)
  1680. ? (value.length >= requiredLength)
  1681. : false
  1682. ;
  1683. },
  1684. // see rls notes for 2.0.6 (this is a duplicate of minLength)
  1685. length: function(value, requiredLength) {
  1686. return (value !== undefined)
  1687. ? (value.length >= requiredLength)
  1688. : false
  1689. ;
  1690. },
  1691. // is exactly length
  1692. exactLength: function(value, requiredLength) {
  1693. return (value !== undefined)
  1694. ? (value.length == requiredLength)
  1695. : false
  1696. ;
  1697. },
  1698. // is less than length
  1699. maxLength: function(value, maxLength) {
  1700. return (value !== undefined)
  1701. ? (value.length <= maxLength)
  1702. : false
  1703. ;
  1704. },
  1705. // matches another field
  1706. match: function(value, identifier, $module) {
  1707. var
  1708. matchingValue,
  1709. matchingElement
  1710. ;
  1711. if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) {
  1712. matchingValue = matchingElement.val();
  1713. }
  1714. else if((matchingElement = $module.find('#' + identifier)).length > 0) {
  1715. matchingValue = matchingElement.val();
  1716. }
  1717. else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) {
  1718. matchingValue = matchingElement.val();
  1719. }
  1720. else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) {
  1721. matchingValue = matchingElement;
  1722. }
  1723. return (matchingValue !== undefined)
  1724. ? ( value.toString() == matchingValue.toString() )
  1725. : false
  1726. ;
  1727. },
  1728. // different than another field
  1729. different: function(value, identifier, $module) {
  1730. // use either id or name of field
  1731. var
  1732. matchingValue,
  1733. matchingElement
  1734. ;
  1735. if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) {
  1736. matchingValue = matchingElement.val();
  1737. }
  1738. else if((matchingElement = $module.find('#' + identifier)).length > 0) {
  1739. matchingValue = matchingElement.val();
  1740. }
  1741. else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) {
  1742. matchingValue = matchingElement.val();
  1743. }
  1744. else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) {
  1745. matchingValue = matchingElement;
  1746. }
  1747. return (matchingValue !== undefined)
  1748. ? ( value.toString() !== matchingValue.toString() )
  1749. : false
  1750. ;
  1751. },
  1752. creditCard: function(cardNumber, cardTypes) {
  1753. var
  1754. cards = {
  1755. visa: {
  1756. pattern : /^4/,
  1757. length : [16]
  1758. },
  1759. amex: {
  1760. pattern : /^3[47]/,
  1761. length : [15]
  1762. },
  1763. mastercard: {
  1764. pattern : /^5[1-5]/,
  1765. length : [16]
  1766. },
  1767. discover: {
  1768. pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/,
  1769. length : [16]
  1770. },
  1771. unionPay: {
  1772. pattern : /^(62|88)/,
  1773. length : [16, 17, 18, 19]
  1774. },
  1775. jcb: {
  1776. pattern : /^35(2[89]|[3-8][0-9])/,
  1777. length : [16]
  1778. },
  1779. maestro: {
  1780. pattern : /^(5018|5020|5038|6304|6759|676[1-3])/,
  1781. length : [12, 13, 14, 15, 16, 17, 18, 19]
  1782. },
  1783. dinersClub: {
  1784. pattern : /^(30[0-5]|^36)/,
  1785. length : [14]
  1786. },
  1787. laser: {
  1788. pattern : /^(6304|670[69]|6771)/,
  1789. length : [16, 17, 18, 19]
  1790. },
  1791. visaElectron: {
  1792. pattern : /^(4026|417500|4508|4844|491(3|7))/,
  1793. length : [16]
  1794. }
  1795. },
  1796. valid = {},
  1797. validCard = false,
  1798. requiredTypes = (typeof cardTypes == 'string')
  1799. ? cardTypes.split(',')
  1800. : false,
  1801. unionPay,
  1802. validation
  1803. ;
  1804. if(typeof cardNumber !== 'string' || cardNumber.length === 0) {
  1805. return;
  1806. }
  1807. // allow dashes in card
  1808. cardNumber = cardNumber.replace(/[\-]/g, '');
  1809. // verify card types
  1810. if(requiredTypes) {
  1811. $.each(requiredTypes, function(index, type){
  1812. // verify each card type
  1813. validation = cards[type];
  1814. if(validation) {
  1815. valid = {
  1816. length : ($.inArray(cardNumber.length, validation.length) !== -1),
  1817. pattern : (cardNumber.search(validation.pattern) !== -1)
  1818. };
  1819. if(valid.length && valid.pattern) {
  1820. validCard = true;
  1821. }
  1822. }
  1823. });
  1824. if(!validCard) {
  1825. return false;
  1826. }
  1827. }
  1828. // skip luhn for UnionPay
  1829. unionPay = {
  1830. number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1),
  1831. pattern : (cardNumber.search(cards.unionPay.pattern) !== -1)
  1832. };
  1833. if(unionPay.number && unionPay.pattern) {
  1834. return true;
  1835. }
  1836. // verify luhn, adapted from <https://gist.github.com/2134376>
  1837. var
  1838. length = cardNumber.length,
  1839. multiple = 0,
  1840. producedValue = [
  1841. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  1842. [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
  1843. ],
  1844. sum = 0
  1845. ;
  1846. while (length--) {
  1847. sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)];
  1848. multiple ^= 1;
  1849. }
  1850. return (sum % 10 === 0 && sum > 0);
  1851. },
  1852. minCount: function(value, minCount) {
  1853. if(minCount == 0) {
  1854. return true;
  1855. }
  1856. if(minCount == 1) {
  1857. return (value !== '');
  1858. }
  1859. return (value.split(',').length >= minCount);
  1860. },
  1861. exactCount: function(value, exactCount) {
  1862. if(exactCount == 0) {
  1863. return (value === '');
  1864. }
  1865. if(exactCount == 1) {
  1866. return (value !== '' && value.search(',') === -1);
  1867. }
  1868. return (value.split(',').length == exactCount);
  1869. },
  1870. maxCount: function(value, maxCount) {
  1871. if(maxCount == 0) {
  1872. return false;
  1873. }
  1874. if(maxCount == 1) {
  1875. return (value.search(',') === -1);
  1876. }
  1877. return (value.split(',').length <= maxCount);
  1878. }
  1879. }
  1880. };
  1881. })( jQuery, window, document );