rails-active-ui 0.1.0

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