1
0

form.js 64 KB

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