fomantic-ui 2.9.1-beta.2 → 2.9.1-beta.20

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