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.
- checksums.yaml +7 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets.css +73555 -0
- data/app/components/accordion_component.rb +34 -0
- data/app/components/ad_component.rb +28 -0
- data/app/components/api_component.rb +24 -0
- data/app/components/breadcrumb_component.rb +26 -0
- data/app/components/button_component.rb +49 -0
- data/app/components/calendar_component.rb +34 -0
- data/app/components/card_component.rb +56 -0
- data/app/components/checkbox_component.rb +41 -0
- data/app/components/column_component.rb +62 -0
- data/app/components/comment_component.rb +45 -0
- data/app/components/concerns/alignable.rb +21 -0
- data/app/components/concerns/attachable.rb +16 -0
- data/app/components/concerns/orientable.rb +21 -0
- data/app/components/concerns/positionable.rb +21 -0
- data/app/components/concerns/sizeable.rb +18 -0
- data/app/components/container_component.rb +23 -0
- data/app/components/dimmer_component.rb +30 -0
- data/app/components/divider_component.rb +30 -0
- data/app/components/dropdown_component.rb +63 -0
- data/app/components/embed_component.rb +32 -0
- data/app/components/emoji_component.rb +15 -0
- data/app/components/feed_component.rb +22 -0
- data/app/components/flag_component.rb +15 -0
- data/app/components/flyout_component.rb +41 -0
- data/app/components/form_component.rb +39 -0
- data/app/components/grid_component.rb +85 -0
- data/app/components/h_stack_component.rb +67 -0
- data/app/components/header_component.rb +60 -0
- data/app/components/icon_component.rb +41 -0
- data/app/components/image_component.rb +46 -0
- data/app/components/input_component.rb +52 -0
- data/app/components/item_component.rb +39 -0
- data/app/components/item_group_component.rb +30 -0
- data/app/components/label_component.rb +49 -0
- data/app/components/link_component.rb +23 -0
- data/app/components/list_component.rb +39 -0
- data/app/components/loader_component.rb +33 -0
- data/app/components/menu_component.rb +64 -0
- data/app/components/menu_item_component.rb +52 -0
- data/app/components/message_component.rb +54 -0
- data/app/components/modal_component.rb +50 -0
- data/app/components/nag_component.rb +25 -0
- data/app/components/overlay_component.rb +16 -0
- data/app/components/placeholder_component.rb +39 -0
- data/app/components/popup_component.rb +31 -0
- data/app/components/progress_component.rb +48 -0
- data/app/components/pusher_component.rb +18 -0
- data/app/components/rail_component.rb +31 -0
- data/app/components/rating_component.rb +41 -0
- data/app/components/reset_component.rb +12 -0
- data/app/components/reveal_component.rb +39 -0
- data/app/components/row_component.rb +39 -0
- data/app/components/search_component.rb +44 -0
- data/app/components/segment_component.rb +57 -0
- data/app/components/segment_group_component.rb +36 -0
- data/app/components/shape_component.rb +25 -0
- data/app/components/sidebar_component.rb +33 -0
- data/app/components/site_component.rb +12 -0
- data/app/components/slider_component.rb +46 -0
- data/app/components/state_component.rb +25 -0
- data/app/components/statistic_component.rb +43 -0
- data/app/components/step_component.rb +56 -0
- data/app/components/step_group_component.rb +38 -0
- data/app/components/sticky_component.rb +22 -0
- data/app/components/sub_header_component.rb +15 -0
- data/app/components/sub_menu_component.rb +24 -0
- data/app/components/tab_component.rb +24 -0
- data/app/components/table_cell_component.rb +60 -0
- data/app/components/table_component.rb +160 -0
- data/app/components/table_row_component.rb +43 -0
- data/app/components/text_component.rb +73 -0
- data/app/components/toast_component.rb +36 -0
- data/app/components/transition_component.rb +32 -0
- data/app/components/v_stack_component.rb +31 -0
- data/app/components/visibility_component.rb +22 -0
- data/app/helpers/component_helper.rb +109 -0
- data/app/helpers/fui_helper.rb +53 -0
- data/app/javascript/accordion.js +547 -0
- data/app/javascript/accordion.min.js +11 -0
- data/app/javascript/api.js +1112 -0
- data/app/javascript/api.min.js +11 -0
- data/app/javascript/calendar.js +1960 -0
- data/app/javascript/calendar.min.js +11 -0
- data/app/javascript/checkbox.js +819 -0
- data/app/javascript/checkbox.min.js +11 -0
- data/app/javascript/dimmer.js +686 -0
- data/app/javascript/dimmer.min.js +11 -0
- data/app/javascript/dropdown.js +4019 -0
- data/app/javascript/dropdown.min.js +11 -0
- data/app/javascript/embed.js +646 -0
- data/app/javascript/embed.min.js +11 -0
- data/app/javascript/flyout.js +1405 -0
- data/app/javascript/flyout.min.js +11 -0
- data/app/javascript/form.js +2070 -0
- data/app/javascript/form.min.js +11 -0
- data/app/javascript/jquery.js +10716 -0
- data/app/javascript/jquery.min.js +2 -0
- data/app/javascript/modal.js +1507 -0
- data/app/javascript/modal.min.js +11 -0
- data/app/javascript/nag.js +522 -0
- data/app/javascript/nag.min.js +11 -0
- data/app/javascript/popup.js +1457 -0
- data/app/javascript/popup.min.js +11 -0
- data/app/javascript/progress.js +922 -0
- data/app/javascript/progress.min.js +11 -0
- data/app/javascript/rating.js +496 -0
- data/app/javascript/rating.min.js +11 -0
- data/app/javascript/search.js +1519 -0
- data/app/javascript/search.min.js +11 -0
- data/app/javascript/shape.js +721 -0
- data/app/javascript/shape.min.js +11 -0
- data/app/javascript/sidebar.js +952 -0
- data/app/javascript/sidebar.min.js +11 -0
- data/app/javascript/site.js +415 -0
- data/app/javascript/site.min.js +11 -0
- data/app/javascript/slider.js +1449 -0
- data/app/javascript/slider.min.js +11 -0
- data/app/javascript/state.js +653 -0
- data/app/javascript/state.min.js +11 -0
- data/app/javascript/sticky.js +852 -0
- data/app/javascript/sticky.min.js +11 -0
- data/app/javascript/tab.js +867 -0
- data/app/javascript/tab.min.js +11 -0
- data/app/javascript/toast.js +916 -0
- data/app/javascript/toast.min.js +11 -0
- data/app/javascript/transition.js +955 -0
- data/app/javascript/transition.min.js +11 -0
- data/app/javascript/ui/controllers/fui_accordion_controller.js +45 -0
- data/app/javascript/ui/controllers/fui_api_controller.js +80 -0
- data/app/javascript/ui/controllers/fui_calendar_controller.js +66 -0
- data/app/javascript/ui/controllers/fui_checkbox_controller.js +48 -0
- data/app/javascript/ui/controllers/fui_dimmer_controller.js +45 -0
- data/app/javascript/ui/controllers/fui_dropdown_controller.js +68 -0
- data/app/javascript/ui/controllers/fui_embed_controller.js +49 -0
- data/app/javascript/ui/controllers/fui_flyout_controller.js +49 -0
- data/app/javascript/ui/controllers/fui_form_controller.js +62 -0
- data/app/javascript/ui/controllers/fui_modal_controller.js +61 -0
- data/app/javascript/ui/controllers/fui_nag_controller.js +52 -0
- data/app/javascript/ui/controllers/fui_popup_controller.js +58 -0
- data/app/javascript/ui/controllers/fui_progress_controller.js +60 -0
- data/app/javascript/ui/controllers/fui_rating_controller.js +49 -0
- data/app/javascript/ui/controllers/fui_search_controller.js +76 -0
- data/app/javascript/ui/controllers/fui_shape_controller.js +45 -0
- data/app/javascript/ui/controllers/fui_sidebar_controller.js +48 -0
- data/app/javascript/ui/controllers/fui_site_controller.js +29 -0
- data/app/javascript/ui/controllers/fui_slider_controller.js +53 -0
- data/app/javascript/ui/controllers/fui_state_controller.js +63 -0
- data/app/javascript/ui/controllers/fui_sticky_controller.js +50 -0
- data/app/javascript/ui/controllers/fui_tab_controller.js +57 -0
- data/app/javascript/ui/controllers/fui_toast_controller.js +60 -0
- data/app/javascript/ui/controllers/fui_transition_controller.js +60 -0
- data/app/javascript/ui/controllers/fui_visibility_controller.js +55 -0
- data/app/javascript/ui/index.js +114 -0
- data/app/javascript/visibility.js +1196 -0
- data/app/javascript/visibility.min.js +11 -0
- data/app/lib/component.rb +63 -0
- data/config/importmap.rb +27 -0
- data/config/initializers/ruby_template_handler.rb +31 -0
- data/config/routes.rb +2 -0
- data/lib/tasks/ui_tasks.rake +4 -0
- data/lib/ui/engine.rb +27 -0
- data/lib/ui/version.rb +3 -0
- data/lib/ui.rb +6 -0
- 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);
|