@componyx/ui 0.0.0-stage → 1.0.3

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 (169) hide show
  1. package/AppSkin/AppSkin.css +1268 -0
  2. package/AppSkin/AppSkin.js +2748 -0
  3. package/AppSkin/Themes/Default.css +241 -0
  4. package/Base/Base.js +3407 -0
  5. package/Base/ComponentIcons.css +66 -0
  6. package/Base/Font/ComponentIcons.woff +0 -0
  7. package/Base/Font/UI.woff +0 -0
  8. package/Base/Library.js +8432 -0
  9. package/Base/NavigationManager.js +462 -0
  10. package/Base/ResponsiveTemplates.js +371 -0
  11. package/Base/Sanitizer.js +288 -0
  12. package/Base/Themes/Default.css +255 -0
  13. package/Base/UI.css +618 -0
  14. package/Base/UIResourcePath.js +6 -0
  15. package/Bindary/Bindary.js +2011 -0
  16. package/Bindary/Modules/Connection.js +216 -0
  17. package/Bindary/Modules/Core.js +854 -0
  18. package/Bindary/Modules/DataUpdater.js +351 -0
  19. package/Bindary/Modules/ExpressionEngine.js +347 -0
  20. package/Bindary/Modules/ProxyManager.js +482 -0
  21. package/Bindary/Modules/Renderer.js +605 -0
  22. package/Bindary/Modules/Router.js +291 -0
  23. package/Bindary/Modules/Scroller.js +89 -0
  24. package/Bindary/Modules/Template.js +1273 -0
  25. package/BookmarkSpy/BookmarkSpy.js +430 -0
  26. package/Box/Box.css +36 -0
  27. package/Box/Box.js +1685 -0
  28. package/Button/Button.css +470 -0
  29. package/Button/Button.js +1398 -0
  30. package/Button/Themes/Default.css +200 -0
  31. package/Carousel/Carousel.css +273 -0
  32. package/Carousel/Carousel.js +607 -0
  33. package/ColorButton/ColorButton.css +39 -0
  34. package/ColorButton/ColorButton.js +336 -0
  35. package/ColorPicker/ColorPicker.css +359 -0
  36. package/ColorPicker/ColorPicker.js +972 -0
  37. package/ComboBox/ComboBox.css +357 -0
  38. package/ComboBox/ComboBox.js +2342 -0
  39. package/ComboBox/Themes/Default.css +23 -0
  40. package/DataPager/DataPager.css +72 -0
  41. package/DataPager/DataPager.js +674 -0
  42. package/DatePicker/DatePicker.css +227 -0
  43. package/DatePicker/DatePicker.js +1531 -0
  44. package/DatePicker/Themes/Default.css +26 -0
  45. package/Dialog/Dialog.css +106 -0
  46. package/Dialog/Dialog.js +572 -0
  47. package/Dialog/Themes/Default.css +10 -0
  48. package/Editor/Data/Emoji.json +8285 -0
  49. package/Editor/Data/Special.json +1042 -0
  50. package/Editor/Editor.css +1562 -0
  51. package/Editor/Editor.js +4058 -0
  52. package/Editor/Font/Editor.woff +0 -0
  53. package/Editor/Modules/ComponentFactory.js +565 -0
  54. package/Editor/Modules/ContentManager.js +748 -0
  55. package/Editor/Modules/DialogManager.js +1234 -0
  56. package/Editor/Modules/EventManager.js +458 -0
  57. package/Editor/Modules/Format.js +950 -0
  58. package/Editor/Modules/History.js +191 -0
  59. package/Editor/Modules/LayoutState.js +379 -0
  60. package/Editor/Modules/ListManager.js +296 -0
  61. package/Editor/Modules/MenuManager.js +388 -0
  62. package/Editor/Modules/NodeManager.js +681 -0
  63. package/Editor/Modules/ParagraphButtons.js +122 -0
  64. package/Editor/Modules/Resizer.js +176 -0
  65. package/Editor/Modules/SelectionRange.js +564 -0
  66. package/Editor/Modules/SourceViewBuilder.js +212 -0
  67. package/Editor/Modules/SymbolPicker.js +208 -0
  68. package/Editor/Modules/Table.js +2327 -0
  69. package/Editor/Modules/TableDialog.js +757 -0
  70. package/Editor/Modules/Toolbar.js +375 -0
  71. package/Editor/Modules/TooltipMenu.js +125 -0
  72. package/Editor/Modules/Utility.js +131 -0
  73. package/Editor/Themes/Default.css +204 -0
  74. package/FileUpload/FileUpload.css +156 -0
  75. package/FileUpload/FileUpload.js +898 -0
  76. package/FileUpload/Themes/Default.css +17 -0
  77. package/Form/Form.css +1934 -0
  78. package/Form/Form.js +4943 -0
  79. package/Form/Modules/ActionManager.js +368 -0
  80. package/Form/Modules/BuildPanelManager.js +319 -0
  81. package/Form/Modules/ComponentFactory.js +468 -0
  82. package/Form/Modules/ComponentPanel.js +265 -0
  83. package/Form/Modules/ConfigPanelManager.js +685 -0
  84. package/Form/Modules/DataObserver.js +717 -0
  85. package/Form/Modules/Databinder.js +602 -0
  86. package/Form/Modules/Draggable.js +756 -0
  87. package/Form/Modules/OptionsPanel.js +342 -0
  88. package/Form/Modules/Renderer.js +1889 -0
  89. package/Form/Modules/RuleEngine.js +585 -0
  90. package/Form/Modules/RulesPanel.js +1158 -0
  91. package/Form/Modules/Types.js +1709 -0
  92. package/Form/Modules/ValidationManager.js +194 -0
  93. package/Form/Themes/Default.css +216 -0
  94. package/FormField/FormField.css +468 -0
  95. package/FormField/FormField.js +576 -0
  96. package/FormField/Themes/Default.css +99 -0
  97. package/Grid/Grid.css +544 -0
  98. package/Grid/Grid.js +3761 -0
  99. package/Grid/Themes/Default.css +43 -0
  100. package/LICENSE +126 -0
  101. package/MaskedTextBox/MaskedTextBox.css +28 -0
  102. package/MaskedTextBox/MaskedTextBox.js +642 -0
  103. package/MaskedTextBox/Themes/Default.css +6 -0
  104. package/Menu/Menu.css +328 -0
  105. package/Menu/Menu.js +2386 -0
  106. package/Menu/Themes/Default.css +24 -0
  107. package/NumericBox/NumericBox.css +120 -0
  108. package/NumericBox/NumericBox.js +984 -0
  109. package/PanelBar/PanelBar.css +123 -0
  110. package/PanelBar/PanelBar.js +505 -0
  111. package/PanelBar/Themes/Default.css +51 -0
  112. package/PieChart/PieChart.css +95 -0
  113. package/PieChart/PieChart.js +820 -0
  114. package/PieChart/Themes/Default.css +9 -0
  115. package/README.md +178 -3
  116. package/Slider/Slider.css +348 -0
  117. package/Slider/Slider.js +1306 -0
  118. package/Slider/Themes/Default.css +44 -0
  119. package/TabStrip/TabStrip.css +52 -0
  120. package/TabStrip/TabStrip.js +362 -0
  121. package/TabStrip/Themes/Default.css +35 -0
  122. package/TimePicker/Themes/Default.css +30 -0
  123. package/TimePicker/TimePicker.css +253 -0
  124. package/TimePicker/TimePicker.js +1485 -0
  125. package/TooltipManager/Themes/Default.css +21 -0
  126. package/TooltipManager/TooltipManager.css +227 -0
  127. package/TooltipManager/TooltipManager.js +791 -0
  128. package/Validator/Validator.css +54 -0
  129. package/Validator/Validator.js +2508 -0
  130. package/componyx.html-data.json +619 -0
  131. package/index.js +27 -0
  132. package/package.json +43 -4
  133. package/types/AppSkin.d.ts +842 -0
  134. package/types/Base.d.ts +1034 -0
  135. package/types/Bindary.d.ts +1196 -0
  136. package/types/BookmarkSpy.d.ts +112 -0
  137. package/types/Box.d.ts +374 -0
  138. package/types/Button.d.ts +455 -0
  139. package/types/Carousel.d.ts +136 -0
  140. package/types/ColorButton.d.ts +120 -0
  141. package/types/ColorPicker.d.ts +225 -0
  142. package/types/ComboBox.d.ts +507 -0
  143. package/types/DataPager.d.ts +152 -0
  144. package/types/DatePicker.d.ts +354 -0
  145. package/types/Dialog.d.ts +160 -0
  146. package/types/Editor.d.ts +1289 -0
  147. package/types/FileUpload.d.ts +206 -0
  148. package/types/Form.d.ts +3078 -0
  149. package/types/FormField.d.ts +204 -0
  150. package/types/Grid.d.ts +911 -0
  151. package/types/Library.d.ts +1899 -0
  152. package/types/MaskedTextBox.d.ts +91 -0
  153. package/types/Menu.d.ts +577 -0
  154. package/types/NumericBox.d.ts +310 -0
  155. package/types/PanelBar.d.ts +223 -0
  156. package/types/PieChart.d.ts +224 -0
  157. package/types/ResponsiveTemplates.d.ts +108 -0
  158. package/types/Slider.d.ts +227 -0
  159. package/types/TabStrip.d.ts +185 -0
  160. package/types/TimePicker.d.ts +265 -0
  161. package/types/TooltipManager.d.ts +178 -0
  162. package/types/UI.d.ts +29 -0
  163. package/types/Validator.d.ts +653 -0
  164. package/types/index.d.ts +59 -0
  165. package/ui.css +11783 -0
  166. package/ui.js +79477 -0
  167. package/ui.min.css +138 -0
  168. package/ui.min.js +264 -0
  169. package/ui.min.js.map +1 -0
@@ -0,0 +1,2508 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ import '../Base/Base.js';
3
+ import '../Base/Library.js';
4
+ import '../Box/Box.js';
5
+ import '../TooltipManager/TooltipManager.js';
6
+
7
+ (function (window)
8
+ {
9
+ /**
10
+ * A list of supported Validator HTML data-attributes.
11
+ * @typedef {Object} HTML_Attributes
12
+ * @memberof componyx.UI.Validator
13
+ * @property {String} ["data-ui-validator-id"] - The identifier of the Validator component on which the rule is added.
14
+ * @property {String} ["data-ui-validator-feedbackid"] - The identifier of the element used by the Validator to display validation result feedback.
15
+ * @property {String} ["data-ui-validator-autovalidate"] - A value ('true' or 'false') indicating if the field is validated when the field value changes.
16
+ * @property {String} ["data-ui-validator-live"] - A value ('true' or 'false') indicating if the field validation is done while typing.
17
+ * @property {String} ["data-ui-validator-getValue"] - A custom method to get the field value.
18
+ * @property {String} ["data-ui-validator-required-msg"] - Denotes an element as required validation field with the attribute value as message to display if validation fails.
19
+ * @property {String} ["data-ui-validator-required-when"] - The method name that returns a boolean value to determines whether the field is required (true) or not (false).
20
+ * @property {String} ["data-ui-validator-datatype-msg"] - Denotes an element as datatype validation field with the attribute value as message to display if validation fails.
21
+ * @property {componyx.UI.Validator.DataTypeOption} ["data-ui-validator-datatype-datatype"] - The data-type to which the field value must comply.
22
+ * @property {String} ["data-ui-validator-length-msg"] - Denotes an element as length validation field with the attribute value as message to display if validation fails.
23
+ * @property {String} ["data-ui-validator-length-min"] - The minimum length to which the field value must comply.
24
+ * @property {String} ["data-ui-validator-length-max"] - The maximum length to which the field value must comply.
25
+ * @property {String} ["data-ui-validator-range-msg"] - Denotes an element as range validation field with the attribute value as message to display if validation fails.
26
+ * @property {String} ["data-ui-validator-range-min"] - The minimum value to which the field value must comply.
27
+ * @property {String} ["data-ui-validator-range-max"] - The maximum value to which the field value must comply.
28
+ * @property {String} ["data-ui-validator-regex-msg"] - Denotes an element as regex validation field with the attribute value as message to display if validation fails.
29
+ * @property {String} ["data-ui-validator-regex-pattern"] - The pattern to which the field value must comply.
30
+ * @property {String} ["data-ui-validator-compare-msg"] - Denotes an element as compare validation field with the attribute value as message to display if validation fails.
31
+ * @property {String} ["data-ui-validator-compare-fields"] - The comparison field(s) with which the field value will be compared.
32
+ * @property {String} ["data-ui-validator-compare-operators"] - The comparison operator(s) for the comparison field validation.
33
+ * @property {String} ["data-ui-validator-custom-msg"] - Denotes an element as custom validation field with the attribute value as message to display if validation fails.
34
+ * @property {String} ["data-ui-validator-custom-onValidation"] - The event handler to invoke when the field is validated.
35
+ * @property {String} ["data-ui-validator-ajax-msg"] - Denotes an element as ajax validation field with the attribute value as message to display if validation fails.
36
+ * @property {String} ["data-ui-validator-ajax-onBeforeValidation"] - The event handler to invoke before the field is validated on the server.
37
+ * @property {String} ["data-ui-validator-ajax-onAfterValidation"] - The event handler to invoke after the field is validated on the server.
38
+ */
39
+
40
+ /**
41
+ * Validator class.
42
+ * @class
43
+ * @memberof componyx.UI
44
+ * @augments componyx.UI.base.Component
45
+ * @mixes componyx.UI.base.methods
46
+ * @param {String} id The id of the component.
47
+ * @param {Object|HTMLElement} properties The properties used to initialize the component or the container element.
48
+ * @property {componyx.UI.base.AjaxMethod} ajax.validate - AJAX method used to validate field values on the server.
49
+ * @returns {Object} An instance of the component.
50
+ */
51
+ componyx.UI.Validator = function Validator(id, properties)
52
+ {
53
+ // define private properties
54
+ let _instance = this,
55
+ _themes = ['themeDefault'],
56
+ _fields = null,
57
+ _ajaxFields = {},
58
+ _formSubmit = false,
59
+ _liveTimerId = null,
60
+ _validatingTimerId = null,
61
+ _fieldValueFromCache = false,
62
+ _tooltipManager,
63
+ _classOption =
64
+ {
65
+ VALIDATOR: 'validator',
66
+ FEEDBACK: 'feedback',
67
+ SUMMARY: 'summary',
68
+ VALIDATING: 'validating',
69
+ VALID: 'valid',
70
+ INVALID: 'invalid',
71
+ ICON: 'icon',
72
+ MULTIPLE: 'multiple',
73
+ TOOLTIPTRIGGER: 'tooltip-trigger'
74
+ },
75
+ _feedbackStatus =
76
+ {
77
+ VALIDATING: 0,
78
+ VALID: 1,
79
+ INVALID: 2
80
+ },
81
+ _typeOption = componyx.UI.Validator.TypeOption,
82
+ _dataTypeOption = componyx.UI.Validator.DataTypeOption,
83
+ _showValidFieldsOption = componyx.UI.Validator.ShowValidFieldsOption,
84
+ _messageDisplayOption = componyx.UI.Validator.MessageDisplayOption,
85
+ _prefix = 'data-ui-validator-',
86
+ _query = 'input,textarea,select,[contenteditable=true]',
87
+ _nativeInputDataTypes = ['email', 'url', 'tel', 'number', 'date', 'time', 'range'],
88
+ _container = null,
89
+ _create = Object.create,
90
+ _attrBase =
91
+ {
92
+ msg: ''
93
+ },
94
+ _prop = (val) => ({ value: val, writable: true, enumerable: true }),
95
+ _Required = function (when) { this.when = _prop(when); },
96
+ _DataType = function (dataType) { this.dataType = _prop(dataType); },
97
+ _Range = function (min, max)
98
+ {
99
+ this.min = _prop(min);
100
+ this.max = _prop(max);
101
+ },
102
+ _RegEx = function (pattern) { this.pattern = _prop(pattern); },
103
+ _Compare = function (fields, operators)
104
+ {
105
+ this.fields = _prop(fields);
106
+ this.operators = _prop(operators);
107
+ },
108
+ _Custom = function (onValidation) { this.onValidation = _prop(onValidation); },
109
+ _Ajax = function (onBeforeValidation, onAfterValidation)
110
+ {
111
+ this.onBeforeValidation = _prop(onBeforeValidation);
112
+ this.onAfterValidation = _prop(onAfterValidation);
113
+ },
114
+ _attr =
115
+ {
116
+ feedbackId: '',
117
+ autoValidate: '',
118
+ live: '',
119
+ getValue: '',
120
+ required: _create(_attrBase, new _Required()),
121
+ dataType: _create(_attrBase, new _DataType()),
122
+ length: _create(_attrBase, new _Range()),
123
+ range: _create(_attrBase, new _Range()),
124
+ regex: _create(_attrBase, new _RegEx()),
125
+ compare: _create(_attrBase, new _Compare()),
126
+ custom: _create(_attrBase, new _Custom()),
127
+ ajax: _create(_attrBase, new _Ajax())
128
+ };
129
+
130
+ _attr.id = _prefix + 'id';
131
+ _attr.feedbackId = _prefix + 'feedbackid';
132
+ _attr.autoValidate = _prefix + 'autovalidate';
133
+ _attr.live = _prefix + 'live';
134
+ _attr.getValue = _prefix + 'getvalue';
135
+
136
+ // create correct attribute keys with 'data-ui-validator-' prefix
137
+ $lib.each(_attr, function (v, k)
138
+ {
139
+ if (v == _attr.id || v == _attr.feedbackId || v == _attr.autoValidate || v == _attr.live || v == _attr.getValue)
140
+ return;
141
+
142
+ $lib.each(_attr[k], function (sv, sk)
143
+ {
144
+ _attr[k][sk] = _prefix + k.toLowerCase() + '-' + sk.toLowerCase();
145
+ });
146
+ });
147
+
148
+ // define public properties
149
+ /**
150
+ * Gets or sets the css class of a feedback ul element.
151
+ * @type {String}
152
+ */
153
+ this.cssClassFeedback = '';
154
+
155
+ /**
156
+ * Gets or sets the css class of a summary ul element.
157
+ * @type {String}
158
+ */
159
+ this.cssClassSummary = '';
160
+
161
+ /**
162
+ * Gets or sets the css class of the feedback message when server-side validation is processing.
163
+ * @type {String}
164
+ */
165
+ this.cssClassValidating = '';
166
+
167
+ /**
168
+ * Gets or sets the css class of the feedback/summary message when the input field passes validation.
169
+ * @type {String}
170
+ */
171
+ this.cssClassValid = '';
172
+
173
+ /**
174
+ * Gets or sets the css class of the feedback/summary message when the input field fails validation.
175
+ * @type {String}
176
+ */
177
+ this.cssClassInvalid = '';
178
+
179
+ /**
180
+ * Gets or sets the css class of the icon in a feedback or summary message.
181
+ * @type {String}
182
+ */
183
+ this.cssClassIcon = '';
184
+
185
+ /**
186
+ * Gets or sets the css class of the tooltip trigger.
187
+ * @type {String}
188
+ */
189
+ this.cssClassTooltipTrigger = '';
190
+
191
+ /**
192
+ * Gets or sets the message which is shown when ajax validation is processing.
193
+ * @type {String}
194
+ */
195
+ this.validatingFieldMessage = '';
196
+
197
+ /**
198
+ * Gets or sets the message which is shown when a field passes validation.
199
+ * @type {String}
200
+ */
201
+ this.validFieldMessage = '';
202
+
203
+ /**
204
+ * Gets or sets the message which is shown when a field fails validation. Message is not shown when the ErrorMessage is defined on the model property.
205
+ * @type {String}
206
+ */
207
+ this.invalidFieldMessage = '';
208
+
209
+ /**
210
+ * Gets or sets the message which is shown when ajax validation throws an error.
211
+ * @type {String}
212
+ */
213
+ this.ajaxErrorMessage = '';
214
+
215
+ /**
216
+ * Gets or sets the delay for the validating field message which is shown when ajax validation is processing.
217
+ * @type {Number}
218
+ */
219
+ this.validatingFieldMessageDelay = 50;
220
+
221
+ /**
222
+ * Gets or sets when valid fields are displayed.
223
+ * @type {componyx.UI.Validator.ShowValidFieldsOption}
224
+ */
225
+ this.showValidFields = _showValidFieldsOption.NONE;
226
+
227
+ /**
228
+ * Gets or sets a value indicating how validation messages are being displayed.
229
+ * @type {componyx.UI.Validator.MessageDisplayOption}
230
+ */
231
+ this.messageDisplay = _messageDisplayOption.FEEDBACKFIRST;
232
+
233
+ /**
234
+ * Gets or sets a value indicating how validation messages are being displayed in the summary container.
235
+ * @type {componyx.UI.Validator.MessageDisplayOption}
236
+ */
237
+ this.summaryMessageDisplay = _messageDisplayOption.FEEDBACKFIRST;
238
+
239
+ /**
240
+ * Gets or sets a value indicating whether valid fields are also displayed in the summary container.
241
+ * @type {Boolean}
242
+ */
243
+ this.showValidFieldsInSummary = false;
244
+
245
+ /**
246
+ * Gets or sets the identifying css class of the field feedback container.
247
+ * @type {String}
248
+ */
249
+ this.feedbackIdentifyingCssClass = '';
250
+
251
+ /**
252
+ * Gets or sets the container id of the validation message summary.
253
+ * @type {String}
254
+ */
255
+ this.summaryContainerId = '';
256
+
257
+ /**
258
+ * Gets or sets the form id of the form that holds the input fields when using multiple forms on a single page.
259
+ * @type {String|null}
260
+ */
261
+ this.formId = null;
262
+
263
+ /**
264
+ * Gets or sets the input format for dates. Default: MM/dd/yyyy
265
+ * @type {String}
266
+ */
267
+ this.dateFormat = 'mm/dd/yyyy';
268
+
269
+ /**
270
+ * Gets or sets the input decimal separator for numbers. Default: '.'
271
+ * @type {String}
272
+ */
273
+ this.decimalSeparator = '.';
274
+
275
+ /**
276
+ * Gets or sets the input group separator for numbers. Default: ','
277
+ * @type {String}
278
+ */
279
+ this.groupSeparator = ',';
280
+
281
+ /**
282
+ * Gets or sets the initial validator rules (Array or JSON string).
283
+ * @type {componyx.UI.Validator.Rule[]|String|null}
284
+ */
285
+ this.rules = null;
286
+
287
+ /**
288
+ * Gets or sets the fields which passed validation on postback.
289
+ * @type {String[]}
290
+ */
291
+ this.validFields = {};
292
+
293
+ /**
294
+ * Gets or sets the fields which failed validation on postback.
295
+ * @type {String[]}
296
+ */
297
+ this.invalidFields = {};
298
+
299
+ /**
300
+ * List containing the friendly field names.
301
+ * @type {String[]}
302
+ */
303
+ this.friendlyFieldNames = {};
304
+
305
+ /**
306
+ * Gets or sets a value indicating whether auto generated field names should be corrected to static names.
307
+ * @type {Boolean}
308
+ */
309
+ this.staticASPNETFieldNames = true;
310
+
311
+ /**
312
+ * Gets or sets a value indicating whether the validation status css classes are applied to the (input/textarea/select/contenteditable) field (defaults to true).
313
+ * @type {Boolean}
314
+ */
315
+ this.validationCssOnField = true;
316
+
317
+ /**
318
+ * Gets or sets a value indicating whether field validation should be performed while typing (defaults to false).
319
+ * @type {Boolean}
320
+ */
321
+ this.live = false;
322
+
323
+ /**
324
+ * Gets or sets the delay in milliseconds before the live field validation is performed.
325
+ * @type {Number}
326
+ */
327
+ this.liveDelay = 0;
328
+
329
+ /**
330
+ * Gets or sets a value indicating if fields are being detected when the component initializes.
331
+ * @type {Boolean}
332
+ */
333
+ this.detectFields = true;
334
+
335
+ /**
336
+ * Gets or sets the tabindex of the tooltip trigger element.
337
+ * @type {String}
338
+ */
339
+ this.tooltipTriggerTabIndex = '';
340
+
341
+ /**
342
+ * Gets or sets the tooltip manager used to display tooltips.
343
+ * @type {String|null}
344
+ */
345
+ this.tooltipManagerId = null;
346
+
347
+
348
+ /**
349
+ * @class
350
+ * @augments componyx.UI.base.Events
351
+ * @memberof componyx.UI.Validator
352
+ * @property {componyx.UI.base.Event} onPreInit - Event which fires before the initialization but after loading resources.
353
+ * @property {componyx.UI.base.Event} onPostInit - Event which fires after the initialization of the validator.
354
+ * @property {componyx.UI.base.Event} onValidField - Event which fires when validation for a field passes. @see {@link componyx.UI.Validator.FieldEventArgs}
355
+ * @property {componyx.UI.base.Event} onInvalidField - Event which fires when validation for a field fails. @see {@link componyx.UI.Validator.FieldEventArgs}
356
+ * @property {componyx.UI.base.Event} onValid - Event which fires when validation passes.
357
+ * @property {componyx.UI.base.Event} onInvalid - Event which fires when validation fails.
358
+ * @property {componyx.UI.base.Event} onFieldFeedback - Event which fires when feedback for a field is set. @see {@link componyx.UI.Validator.FieldEventArgs}
359
+ * @see {@link componyx.UI.base.Events}
360
+ */
361
+ function ValidatorEvents(events)
362
+ {
363
+ Object.assign(this, events);
364
+ this.onPreInit = $base.static.createEvent('onPreInit');
365
+ this.onPostInit = $base.static.createEvent('onPostInit');
366
+ this.onValidField = $base.static.createEvent('onValidField');
367
+ this.onInvalidField = $base.static.createEvent('onInvalidField');
368
+ this.onValid = $base.static.createEvent('onValid');
369
+ this.onInvalid = $base.static.createEvent('onInvalid');
370
+ this.onFieldFeedback = $base.static.createEvent('onFieldFeedback');
371
+ }
372
+ /**
373
+ * Validator events
374
+ * @type {componyx.UI.Validator.ValidatorEvents}
375
+ */
376
+ this.events = new ValidatorEvents(this.events);
377
+
378
+ /**
379
+ * Validator field event arguments.
380
+ * @typedef {Object} FieldEventArgs
381
+ * @memberof componyx.UI.Validator
382
+ * @property {HTMLElement} fieldElement - The validated field element.
383
+ * @property {String} fieldName - The name of the field.
384
+ * @property {String} fieldValue - The value of the field.
385
+ * @property {Object} fieldData - The internal validation data of the field (validation settings and state).
386
+ * @property {String[]} msg - The validation messages.
387
+ * @property {HTMLUListElement} [feedbackElement] - The feedback list element (onFieldFeedback only, when feedback is displayed).
388
+ * @property {HTMLUListElement} [summaryElement] - The summary list element (onFieldFeedback only, when the field is summarised).
389
+ */
390
+
391
+ // inherit base instance members
392
+ $base.Component.apply(this, [id, properties]);
393
+
394
+ // define ajax method
395
+ this.ajax.addMethod('validate');
396
+
397
+ /**
398
+ * Returns true if the current validation state is valid, otherwise false
399
+ */
400
+ this.isValid = function ()
401
+ {
402
+ return isValid();
403
+ };
404
+
405
+ /**
406
+ * Validates the required field using the specified settings.
407
+ * @param {Object} settings The validation rule settings.
408
+ * @param {HTMLElement[]} field The field or fields in case of a Radio Button.
409
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
410
+ */
411
+ this.validateRequired = function (settings, field)
412
+ {
413
+ return validateRequired(settings, field);
414
+ };
415
+
416
+ /**
417
+ * Validates the field by value comparison using the specified settings.
418
+ * @param {Object} settings Validation rule settings (includes fields and operators).
419
+ * @param {String} value The field value.
420
+ * @param {componyx.UI.Validator.DataTypeOption|null} [dataType] Field value data type (default: string).
421
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
422
+ */
423
+ this.validateCompare = function (settings, value, dataType)
424
+ {
425
+ return validateCompare(settings, value, dataType);
426
+ };
427
+
428
+ /**
429
+ * Validates the field value range using the specified settings.
430
+ * @param {Object} settings The validation rule settings.
431
+ * @param {String} value The field value.
432
+ * @param {DataTypeOption} value The data type of the value.
433
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
434
+ */
435
+ this.validateRange = function (settings, value, dataType)
436
+ {
437
+ return validateRange(settings, value, dataType);
438
+ };
439
+
440
+ /**
441
+ * Validates the field value length using the specified settings.
442
+ * @param {Object} settings The validation rule settings.
443
+ * @param {String} value The field value.
444
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
445
+ */
446
+ this.validateLength = function (settings, value)
447
+ {
448
+ return validateLength(settings, value);
449
+ };
450
+
451
+ /**
452
+ * Validates the field value data-type using the specified settings.
453
+ * @param {Object} settings The validation rule settings.
454
+ * @param {String} value The field value.
455
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
456
+ */
457
+ this.validateType = function (settings, value)
458
+ {
459
+ return validateType(settings, value);
460
+ };
461
+
462
+ /**
463
+ * Validates the field value by the configured regex pattern using the specified settings.
464
+ * @param {Object} settings The validation rule settings.
465
+ * @param {String} value The field value.
466
+ * @returns {Boolean} A value indicating if the field is valid (true) or invalid (false).
467
+ */
468
+ this.validateRegEx = function (settings, value)
469
+ {
470
+ return validateRegEx(settings, value);
471
+ };
472
+
473
+ /**
474
+ * Gets all the validation fields with there defined rules.
475
+ * @returns {Object} An object with field-name as key and the validation rules as value.
476
+ */
477
+ this.getFields = function ()
478
+ {
479
+ return _fields;
480
+ };
481
+
482
+ /**
483
+ * Defines the template for a validating field in the feedback container. The template supports the below listed interpolations. Default value: {icon}{message}
484
+ * - {icon} This value will be replaced validation state icon.
485
+ * - {fieldName} This value will be replaced with the name of the validation field.
486
+ * - {message} This value will be replaced with the validation message.
487
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
488
+ */
489
+ this.setValidatingFieldFeedbackTemplate = function (content)
490
+ {
491
+ _instance.addTemplate('ValidatingFieldFeedback', content);
492
+ };
493
+
494
+ /**
495
+ * Defines the template for a valid field in the feedback container. The template supports the below listed interpolations. Default value: {icon}{message}
496
+ * - {icon} This value will be replaced validation state icon.
497
+ * - {fieldName} This value will be replaced with the name of the validation field.
498
+ * - {message} This value will be replaced with the validation message.
499
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
500
+ */
501
+ this.setValidFieldFeedbackTemplate = function (content)
502
+ {
503
+ _instance.addTemplate('ValidFieldFeedback', content);
504
+ };
505
+
506
+ /**
507
+ * Defines the template for an invalid field in the feedback container. The template supports the below listed interpolations. Default value: {icon}{tooltipTrigger}{multiple}[ 1-{messageCount} ]{/multiple}{/tooltipTrigger} {message}
508
+ * - {icon} This value will be replaced validation state icon.
509
+ * - {fieldName} This value will be replaced with the name of the validation field.
510
+ * - {message} This value will be replaced with the validation message.
511
+ * - {tooltipTrigger} This value will be replaced with the opening trigger element tag.
512
+ * - {/tooltipTrigger} This value will be replaced with the closing trigger element tag.
513
+ * - {multiple} This value will be replaced with the opening multiple element tag.
514
+ * - {/multiple} This value will be replaced with the closing multiple element tag.
515
+ * - {messageCount} This value will be replaced with the validation message count.
516
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
517
+ */
518
+ this.setInvalidFieldFeedbackTemplate = function (content)
519
+ {
520
+ _instance.addTemplate('InvalidFieldFeedback', content);
521
+ };
522
+
523
+ /**
524
+ * Defines the template for a valid field in the summary container. The template supports the below listed interpolations. Default value: {icon}{fieldName} {message
525
+ * - {icon} This value will be replaced validation state icon.
526
+ * - {fieldName} This value will be replaced with the name of the validation field.
527
+ * - {message} This value will be replaced with the validation message.
528
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
529
+ */
530
+ this.setValidFieldSummaryTemplate = function (content)
531
+ {
532
+ _instance.addTemplate('ValidFieldSummary', content);
533
+ };
534
+
535
+ /**
536
+ * Defines the template for an invalid field in the summary container. The template supports the below listed interpolations. Default value: {icon}{fieldName}: {tooltipTrigger}{multiple}[ 1-{messageCount} ]{/multiple}{/tooltipTrigger} {message}
537
+ * - {icon} This value will be replaced validation state icon.
538
+ * - {fieldName} This value will be replaced with the name of the validation field.
539
+ * - {message} This value will be replaced with the validation message.
540
+ * - {tooltipTrigger} This value will be replaced with the opening trigger element tag.
541
+ * - {/tooltipTrigger} This value will be replaced with the closing trigger element tag.
542
+ * - {multiple} This value will be replaced with the opening multiple element tag.
543
+ * - {/multiple} This value will be replaced with the closing multiple element tag.
544
+ * - {messageCount} This value will be replaced with the validation message count.
545
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
546
+ */
547
+ this.setInvalidFieldSummaryTemplate = function (content)
548
+ {
549
+ _instance.addTemplate('InvalidFieldSummary', content);
550
+ };
551
+
552
+ /**
553
+ * Defines the template for an invalid field in the popup. The template supports the below listed interpolations. Default value: {icon}{fieldName}: {tooltipTrigger}{multiple}[ 1-{messageCount} ]{/multiple}{/tooltipTrigger} {message}
554
+ * - {message} This value will be replaced with the validation message.
555
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The content of the template
556
+ */
557
+ this.setInvalidFieldPopupTemplate = function (content)
558
+ {
559
+ _instance.addTemplate('InvalidFieldPopup', content);
560
+ };
561
+
562
+ // the validator does not render html output, so nothing to hide
563
+ this.hide = null;
564
+
565
+ /**
566
+ * Initializes the Validator component if this has not yet been done.
567
+ */
568
+ this.show = function ()
569
+ {
570
+ if (_instance.renderState == $base.static.RenderState.NONE)
571
+ this.render();
572
+ };
573
+
574
+ /**
575
+ * Initializes the Validator component.
576
+ */
577
+ this.init = function ()
578
+ {
579
+ if (_instance.renderState == $base.static.RenderState.RENDERING)
580
+ return;
581
+
582
+ const element = _instance.element;
583
+ let script = ['Box', 'TooltipManager'];
584
+
585
+ _instance.destroy(true, false); // keep events, keep the placeholder in the DOM
586
+ _instance.element = element;
587
+ _instance.hasTheme = false;
588
+ $UI.store[_instance.id] = _instance;
589
+
590
+ if (element) // a Validator created by another component does not have an element
591
+ {
592
+ $UI.elementStore.set(element, _instance); // destroy() removed it
593
+ $base.methods.setupMutationObserver.call(_instance);
594
+ }
595
+
596
+ _instance.renderState = $base.static.RenderState.RENDERING;
597
+
598
+ // initialize css
599
+ _instance.registerResources(function ()
600
+ {
601
+ return ['Validator', script];
602
+ },
603
+ function ()
604
+ {
605
+ // create default templates
606
+ if (!_instance.hasTemplate('ValidatingFieldFeedback'))
607
+ _instance.addTemplate('ValidatingFieldFeedback', '{icon}{message}', true);
608
+
609
+ if (!_instance.hasTemplate('ValidFieldFeedback'))
610
+ _instance.addTemplate('ValidFieldFeedback', '{icon}{message}', true);
611
+
612
+ if (!_instance.hasTemplate('InvalidFieldFeedback'))
613
+ _instance.addTemplate('InvalidFieldFeedback', '{icon}{tooltipTrigger}{multiple}[ 1-{messageCount} ]{/multiple}{/tooltipTrigger} {message}', true);
614
+
615
+ if (!_instance.hasTemplate('ValidFieldSummary'))
616
+ _instance.addTemplate('ValidFieldSummary', '{icon}{fieldName} {message}', true);
617
+
618
+ if (!_instance.hasTemplate('InvalidFieldSummary'))
619
+ _instance.addTemplate('InvalidFieldSummary', '{icon}{fieldName}: {tooltipTrigger}{multiple}[ 1-{messageCount} ]{/multiple}{/tooltipTrigger} {message}', true);
620
+
621
+ if (!_instance.hasTemplate('InvalidFieldPopup'))
622
+ _instance.addTemplate('InvalidFieldPopup', '{message}', true);
623
+
624
+ _instance.renderState = $base.static.RenderState.RENDERED; // we need to set this state otherwise main UI postRender event might be blocked
625
+ createTooltipManager();
626
+ _instance.events.onPreInit.fire(_instance);
627
+
628
+ if ($UI.busy)
629
+ {
630
+ if (!$UI.onPostRender.has(postInit))
631
+ $UI.onPostRender.once(postInit);
632
+ }
633
+ else
634
+ postInit();
635
+ });
636
+ };
637
+
638
+ /**
639
+ * Initializes the Validator component. Both init and render are the same for this component.
640
+ * @function
641
+ */
642
+ this.render = this.init;
643
+
644
+ /**
645
+ * Detects validation rules specified through data-attributes on input elements.
646
+ * @param {HTMLElement} [container] A container element to restrict detection to fields within the container.
647
+ * @param {boolean} [ignoreHidden] A value indicating whether hidden fields should be excluded from detection.
648
+ */
649
+ this.detect = function (container, ignoreHidden = false)
650
+ {
651
+ let settings, dataType, detected = {},
652
+ getValidatorAttr = (el, attrFullName) =>
653
+ {
654
+ let val = el.getAttribute(attrFullName);
655
+ if (val != null)
656
+ return val;
657
+
658
+ if (attrFullName.startsWith('data-ui-validator-')) // check for shortened attribute without 'data-' prefix
659
+ {
660
+ const suffix = attrFullName.slice('data-ui-validator-'.length);
661
+
662
+ val = el.getAttribute('ui-val-' + suffix);
663
+ if (val != null) return val;
664
+
665
+ val = el.getAttribute('ui-validator-' + suffix);
666
+ if (val != null) return val;
667
+ }
668
+
669
+ return null;
670
+ },
671
+ hasValidatorAttr = (el, attrFullName) =>
672
+ {
673
+ return getValidatorAttr(el, attrFullName) != null;
674
+ },
675
+ setup = function (el, type, props)
676
+ {
677
+ var settings = _create(_attrBase, props);
678
+ settings.feedBackId = getValidatorAttr(el, _attr.feedbackId);
679
+ settings.autoValidate = hasValidatorAttr(el, _attr.autoValidate) ? getValidatorAttr(el, _attr.autoValidate) != 'false' : null;
680
+ settings.live = hasValidatorAttr(el, _attr.live) ? getValidatorAttr(el, _attr.live) != 'false' : null;
681
+ settings.getValue = getValidatorAttr(el, _attr.getValue);
682
+ settings.msg = getValidatorAttr(el, type.msg);
683
+ settings.native = (type === _attr.required && el.hasAttribute('required')) ||
684
+ (type === _attr.dataType && _nativeInputDataTypes.includes(el.type)) ||
685
+ (type === _attr.length && (el.hasAttribute('minlength') || el.hasAttribute('maxlength'))) ||
686
+ (type === _attr.range && (el.hasAttribute('min') || el.hasAttribute('max'))) ||
687
+ (type === _attr.regex && el.hasAttribute('pattern'));
688
+ settings.checkValidity = (settings.native) ? el.checkValidity.bind(el) : null;
689
+
690
+ return settings;
691
+ };
692
+
693
+ _container = container || getForm() || document;
694
+ let list = _container.querySelectorAll(_query);
695
+
696
+ if (ignoreHidden)
697
+ list = Array.from(list).filter(el => isVisible(el));
698
+
699
+ $lib.each(list, function (el)
700
+ {
701
+ let name = getFieldName(el);
702
+
703
+ if ($lib.isEmpty(name) || detected[name] || (hasValidatorAttr(el, _attr.id) && getValidatorAttr(el, _attr.id) != _instance.id))
704
+ return;
705
+
706
+ detected[name] = true;
707
+
708
+ if (hasValidatorAttr(el, _attr.required.msg))
709
+ {
710
+ settings = setup(el, _attr.required, new _Required(getValidatorAttr(el, _attr.required.when)));
711
+ _instance.addRule(name, settings, _typeOption.REQUIRED);
712
+ }
713
+
714
+ if (hasValidatorAttr(el, _attr.dataType.msg))
715
+ {
716
+ dataType = getValidatorAttr(el, _attr.dataType.dataType);
717
+ settings = setup(el, _attr.dataType, new _DataType(!$lib.isEmpty(dataType) ? _dataTypeOption[dataType.toUpperCase()] : 0));
718
+ _instance.addRule(name, settings, _typeOption.DATATYPE);
719
+ }
720
+
721
+ if (hasValidatorAttr(el, _attr.length.msg))
722
+ {
723
+ settings = setup(el, _attr.length, new _Range(getValidatorAttr(el, _attr.length.min), getValidatorAttr(el, _attr.length.max)));
724
+ _instance.addRule(name, settings, _typeOption.LENGTH);
725
+ }
726
+
727
+ if (hasValidatorAttr(el, _attr.range.msg))
728
+ {
729
+ settings = setup(el, _attr.range, new _Range(getValidatorAttr(el, _attr.range.min), getValidatorAttr(el, _attr.range.max)));
730
+ _instance.addRule(name, settings, _typeOption.RANGE);
731
+ }
732
+
733
+ if (hasValidatorAttr(el, _attr.regex.msg))
734
+ {
735
+ settings = setup(el, _attr.regex, new _RegEx(getValidatorAttr(el, _attr.regex.pattern)));
736
+ _instance.addRule(name, settings, _typeOption.REGEX);
737
+ }
738
+
739
+ if (hasValidatorAttr(el, _attr.compare.msg))
740
+ {
741
+ const fields = (getValidatorAttr(el, _attr.compare.fields) || '').split(',').map(f => f.trim()),
742
+ operators = (getValidatorAttr(el, _attr.compare.operators) || '').split(',').map(o => o.trim());
743
+
744
+ settings = setup(el, _attr.compare, new _Compare(fields, operators));
745
+ _instance.addRule(name, settings, _typeOption.COMPARE);
746
+ }
747
+
748
+ if (hasValidatorAttr(el, _attr.custom.onValidation))
749
+ {
750
+ settings = setup(el, _attr.custom, new _Custom(getMethod(getValidatorAttr(el, _attr.custom.onValidation))));
751
+ _instance.addRule(name, settings, _typeOption.CUSTOM);
752
+ }
753
+
754
+ if (hasValidatorAttr(el, _attr.ajax.msg))
755
+ {
756
+ settings = setup(el, _attr.ajax, new _Ajax(
757
+ getMethod(getValidatorAttr(el, _attr.ajax.onBeforeValidation)),
758
+ getMethod(getValidatorAttr(el, _attr.ajax.onAfterValidation))
759
+ ));
760
+ _instance.addRule(name, settings, _typeOption.AJAX);
761
+ }
762
+ });
763
+
764
+ _container = null;
765
+ };
766
+
767
+ /**
768
+ * Adds a validation rule of the specified type for the given field name(s).
769
+ * @param {String|String[]} fieldName The name of the form field(s).
770
+ * @param {Object} settings The validation rule settings.
771
+ * @param {String} settings.feedbackId The identifier of the element used by the Validator to display validation result feedback.
772
+ * @param {Boolean} settings.autoValidate A value indicating if the field is validated when the field value changes.
773
+ * @param {Boolean} settings.live A value indicating if the field validation is done while typing.
774
+ * @param {Function} settings.getValue A custom method to return the value for this field. Passes in the field element and expects the field value as result.
775
+ * @param {String} settings.msg The message to display if validation fails.
776
+ * @param {String} [settings.when] The name of the method that returns a boolean value to determines whether the field is required (true) or not (false). (REQUIRED type validation).
777
+ * @param {Number|String} [settings.min] The minimum value. (LENGTH and RANGE type validation).
778
+ * @param {Number|String} [settings.max] The maximum value. (LENGTH and RANGE type validation).
779
+ * @param {RegExp} settings.pattern The Regular expression pattern. (REGEX type validation).
780
+ * @param {HTMLElement[]|String[]} settings.fields The HTML element(s), element name(s) or values to compare against.
781
+ * @param {String[]} settings.operators The operator(s) to use for comparison: < 'LessThan', <= 'LessThanOrEqualTo', > 'GreaterThan', >= 'GreaterThanOrEqualTo', or == 'EqualTo' (default). Can be a single entry (applied to all fields) or an array aligned with `fields`.
782
+ * @param {componyx.UI.Validator.DataTypeOption} settings.dataType The data-type to which the field value must comply. (DATATYPE type validation).
783
+ * @param {Boolean} settings.alwaysCheck A value indicating if the validation rule should always be checked even if the field validation has already failed (only applies to type CUSTOM).
784
+ * @param {OnValidation} settings.onValidation A function(sender, args) invoked when the field is validated. (CUSTOM type validation).
785
+ * @param {OnBeforeValidation} settings.onBeforeValidation A function(sender, args) invoked before the field is validated. (AJAX type validation).
786
+ * @param {OnAfterValidation} settings.onAfterValidation A function function(sender, args) invoked after the field is validated. (AJAX type validation).
787
+ * @param {TypeOption} [type] The type of the validation. May be omitted to apply a global field setting only (feedbackId, autoValidate or live).
788
+ * - 0 REQUIRED
789
+ * - 1 DATATYPE
790
+ * - 2 LENGTH
791
+ * - 3 RANGE
792
+ * - 4 REGEX
793
+ * - 5 COMPARE
794
+ * - 6 CUSTOM
795
+ * - 7 AJAX
796
+ */
797
+ this.addRule = function (fieldName, settings, type)
798
+ {
799
+ var form = getForm();
800
+
801
+ if (!fieldName)
802
+ return;
803
+
804
+ if (!$lib.isArray(fieldName))
805
+ fieldName = [fieldName];
806
+
807
+ if (!settings)
808
+ settings = {};
809
+
810
+ if (!_fields)
811
+ {
812
+ _fields = {};
813
+
814
+ if (form)
815
+ $lib.on(form, 'submit', validateForm);
816
+ }
817
+
818
+ if (settings.fields && !$lib.isArray(settings.fields))
819
+ settings.fields = [settings.fields];
820
+
821
+ $lib.each(fieldName, function (name)
822
+ {
823
+ let fieldEl = getFieldByName(name);
824
+
825
+ if (!_fields[name])
826
+ {
827
+ _fields[name] =
828
+ {
829
+ feedbackId: settings.feedbackId,
830
+ autoValidate: settings.autoValidate,
831
+ live: settings.live,
832
+ getValue: settings.getValue,
833
+ isValid: false,
834
+ lastValue: null,
835
+ lastMsg: null,
836
+ element: fieldEl || null
837
+ };
838
+ }
839
+ else
840
+ {
841
+ _fields[name].isValid = false;
842
+ _fields[name].lastValue = null;
843
+ _fields[name].lastMsg = null;
844
+
845
+ if (!$lib.isEmpty(settings.feedbackId))
846
+ _fields[name].feedbackId = settings.feedbackId;
847
+
848
+ if (!$lib.isEmpty(settings.autoValidate))
849
+ _fields[name].autoValidate = settings.autoValidate;
850
+
851
+ if (!$lib.isEmpty(settings.live))
852
+ _fields[name].live = settings.live;
853
+
854
+ if (!$lib.isEmpty(settings.getValue))
855
+ _fields[name].getValue = settings.getValue;
856
+ }
857
+
858
+ if (fieldEl && _fields[name].autoValidate != false)
859
+ {
860
+ let fieldEls = [fieldEl];
861
+
862
+ if (fieldEl.type == 'radio')
863
+ fieldEls = getRadioFields(fieldEl);
864
+
865
+ if (type === _typeOption.COMPARE && settings.fields) // make sure original field gets validated
866
+ {
867
+ settings.fields.forEach(cf =>
868
+ {
869
+ let cfEl = (cf instanceof HTMLElement) ? cf : getFieldByName(cf);
870
+
871
+ if (cfEl && fieldEls.indexOf(cfEl) === -1)
872
+ {
873
+ if (cfEl.type == 'radio')
874
+ fieldEls = fieldEls.concat(getRadioFields(cfEl));
875
+ else
876
+ fieldEls.push(cfEl);
877
+ }
878
+ });
879
+ }
880
+
881
+ $lib.each(fieldEls, function (fld)
882
+ {
883
+ // add unique event handler for field, binding will only be added once per field
884
+ let eventType = (fld.isContentEditable) ? 'blur' : 'change';
885
+
886
+ $lib.on(fld, eventType, validateFieldOnChange, [fld, false], null, true);
887
+ $lib.on(fld, 'focus', hideTooltip, name, null, true);
888
+
889
+ if (fld.nodeName.toLowerCase() != 'select' && (_fields[name].live == true || (_instance.live && _fields[name].live != false)))
890
+ $lib.on(fld, 'input', validateFieldOnInput, [fld, false], null, true);
891
+ });
892
+ }
893
+
894
+ delete settings.feedbackId;
895
+ delete settings.autoValidate;
896
+ delete settings.live;
897
+ delete settings.getValue;
898
+
899
+ if (!$lib.isEmpty(type))
900
+ {
901
+ if (!_fields[name][type])
902
+ _fields[name].isValid = false; // new rule
903
+
904
+ _fields[name][type] = settings;
905
+ }
906
+
907
+ var reqSettings = _fields[name][_typeOption.REQUIRED];
908
+
909
+ if ((!_fields[name][_typeOption.REQUIRED] || (reqSettings.when && !isRequired(reqSettings.when, fieldEl))) && $lib.isEmpty(getFieldValue(fieldEl)))
910
+ _fields[name].isValid = true; // not required and no field value
911
+ });
912
+ };
913
+
914
+ /**
915
+ * Resets the field's validation state.
916
+ * @param {String|String[]} fieldName The name of the form field (or array of field names).
917
+ */
918
+ this.resetState = function (fieldName)
919
+ {
920
+ $lib.each(fieldName, function (name)
921
+ {
922
+ resetState(_fields[name], name);
923
+ });
924
+ };
925
+
926
+ /**
927
+ * Resets the validation state for all fields.
928
+ * @param {HTMLElement} [container] A container element to restrict the state reset to fields within the container.
929
+ */
930
+ this.resetStates = function (container)
931
+ {
932
+ $lib.each(_fields, function (fld, name)
933
+ {
934
+ if (!container || (container && fld.element && container.contains(fld.element)))
935
+ resetState(fld, name);
936
+ });
937
+ };
938
+
939
+ /**
940
+ * Returns true if there are validation rules for this field, otherwise false.
941
+ * @param {String} fieldName The name of the form field.
942
+ */
943
+ this.hasRules = function (fieldName)
944
+ {
945
+ return (_fields != null && _fields[fieldName] != undefined);
946
+ };
947
+
948
+ /**
949
+ * Returns true if the validation rule is set for this field, otherwise false.
950
+ * @param {String} fieldName The name of the form field.
951
+ */
952
+ this.hasRule = function (fieldName, type)
953
+ {
954
+ return (_fields != null && _fields[fieldName] != undefined && _fields[fieldName][type] != undefined);
955
+ };
956
+
957
+ /**
958
+ * Returns the validation rules for the specified field.
959
+ * @param {String} fieldName The name of the form field.
960
+ */
961
+ this.getRules = function (fieldName)
962
+ {
963
+ if (_fields != null && _fields[fieldName] != undefined)
964
+ return _fields[fieldName];
965
+ else
966
+ return null
967
+ };
968
+
969
+ /**
970
+ * Removes the validation rule of the specified type for the given field name(s).
971
+ * @param {String|String[]} fieldName The name of the form field (or array of field names).
972
+ * @param {TypeOption} type The type of the validation
973
+ */
974
+ this.removeRule = function (fieldName, type)
975
+ {
976
+ if (!_fields)
977
+ return;
978
+
979
+ if (!fieldName.constructor || fieldName.constructor.toString().indexOf('Array') == -1)
980
+ fieldName = [fieldName];
981
+
982
+ $lib.each(fieldName, function (name)
983
+ {
984
+ if (_fields[name] && _fields[name][type])
985
+ {
986
+ clearFieldFeedback(name);
987
+ delete _fields[name][type];
988
+ _fields[name].lastValue = null;
989
+
990
+ if (!hasRule(name))
991
+ {
992
+ removeBinding(name);
993
+ delete _fields[name];
994
+ }
995
+ }
996
+ });
997
+ };
998
+
999
+ /**
1000
+ * Removes the validation rules for the given field name(s).
1001
+ * @param {String|String[]} fieldName The name of the form field (or array of field names).
1002
+ */
1003
+ this.removeRules = function (fieldName)
1004
+ {
1005
+ if (!_fields)
1006
+ return;
1007
+
1008
+ fieldName = Array.isArray(fieldName) ? fieldName : [fieldName];
1009
+
1010
+ $lib.each(fieldName, function (name)
1011
+ {
1012
+ if (_fields[name])
1013
+ {
1014
+ clearFieldFeedback(name);
1015
+ delete _fields[name];
1016
+ removeBinding(name);
1017
+ }
1018
+ });
1019
+ };
1020
+
1021
+ /**
1022
+ * Removes the validation rules for all fields.
1023
+ * @param {HTMLElement} [container] A container element to restrict clearance to fields within the container.
1024
+ * @param {Boolean} [keepForRemovedField] A value indicating to keep (instead of clearing) validation rules for field elements removed from the document.
1025
+ */
1026
+ this.clear = function (container, keepForRemovedField)
1027
+ {
1028
+ var clearFields = [];
1029
+
1030
+ _instance.clearFeedback(container, keepForRemovedField);
1031
+
1032
+ $lib.each(_fields, function (fld, name)
1033
+ {
1034
+ if (container)
1035
+ {
1036
+ if (fld.element && (document.contains(fld.element) || keepForRemovedField) && !container.contains(fld.element))
1037
+ return;
1038
+ else
1039
+ clearFields.push(name);
1040
+ }
1041
+
1042
+ removeBinding(name);
1043
+ });
1044
+
1045
+ if (container)
1046
+ {
1047
+ $lib.each(clearFields, function (name)
1048
+ {
1049
+ delete _fields[name];
1050
+
1051
+ if (_ajaxFields[name])
1052
+ delete _ajaxFields[name];
1053
+ });
1054
+ }
1055
+ else
1056
+ {
1057
+ _fields = null;
1058
+ _ajaxFields = {};
1059
+ }
1060
+ };
1061
+
1062
+ /**
1063
+ * Removes the feedback messages for all fields.
1064
+ * @param {HTMLElement} [container] A container element to restrict clearance to fields within the container.
1065
+ * @param {Boolean} [keepForRemovedField] A value indicating to keep (instead of clearing) validation rules for field elements removed from the document.
1066
+ */
1067
+ this.clearFeedback = function (container, keepForRemovedField)
1068
+ {
1069
+ $lib.each(_fields, function (field, name)
1070
+ {
1071
+ if (container && field.element && (document.contains(field.element) || keepForRemovedField) && !container.contains(field.element))
1072
+ return;
1073
+
1074
+ clearFieldFeedback(name);
1075
+ });
1076
+ };
1077
+
1078
+ /**
1079
+ * Removes the feedback messages for the specified field name.
1080
+ */
1081
+ this.clearFieldFeedback = function (fieldName)
1082
+ {
1083
+ clearFieldFeedback(fieldName);
1084
+ };
1085
+
1086
+ /**
1087
+ * Validates all fields or the fields within the specified container element.
1088
+ * @param {HTMLElement} [container] A container element to restrict validation to fields within the container.
1089
+ * @param {Boolean} [filledOnly] A value indicating to only validate filled out fields.
1090
+ */
1091
+ this.validate = function (container, filledOnly)
1092
+ {
1093
+ return validate(container, filledOnly);
1094
+ };
1095
+
1096
+ /**
1097
+ * Validates a specific field
1098
+ * @param {String} fieldName The name of the form field.
1099
+ * @returns {boolean} A value indicating if the field is valid, works only for simple non async validations.
1100
+ */
1101
+ this.validateField = function (fieldName)
1102
+ {
1103
+ validateField(getFieldByName(fieldName));
1104
+ return _fields[fieldName].isValid;
1105
+ };
1106
+
1107
+ /**
1108
+ * Destroys the component.
1109
+ * @see {@link componyx.UI.base.methods#destroy}
1110
+ */
1111
+ this.destroy = function (...args)
1112
+ {
1113
+ _instance.clear();
1114
+ $base.methods.destroy.call(this, ...args);
1115
+ };
1116
+
1117
+ function postInit()
1118
+ {
1119
+ if (_instance.rules)
1120
+ initRules();
1121
+
1122
+ if (_instance.detectFields)
1123
+ _instance.detect();
1124
+
1125
+ initFeedback();
1126
+ _instance.events.onPostInit.fire(_instance);
1127
+ }
1128
+
1129
+ function initRules()
1130
+ {
1131
+ if (typeof _instance.rules == 'string')
1132
+ _instance.rules = window.JSON.parse(_instance.rules);
1133
+
1134
+ $lib.each(_instance.rules, function (rule)
1135
+ {
1136
+ _instance.addRule(rule.fieldName, rule, rule.type);
1137
+ });
1138
+ }
1139
+
1140
+ function initFeedback()
1141
+ {
1142
+ var fieldName;
1143
+
1144
+ if (_instance.validFields)
1145
+ {
1146
+ for (var index = 0; _instance.validFields[index]; ++index)
1147
+ {
1148
+ fieldName = _instance.validFields[index];
1149
+ _fields[fieldName].isValid = true;
1150
+ addFieldFeedback(getFieldByName(fieldName), _feedbackStatus.VALID, _fields[fieldName].feedbackId, true);
1151
+ }
1152
+ }
1153
+
1154
+ for (fieldName in _instance.invalidFields)
1155
+ {
1156
+ addFieldFeedback(getFieldByName(fieldName), _feedbackStatus.INVALID, _fields[fieldName].feedbackId, true, _instance.invalidFields[fieldName]);
1157
+ }
1158
+ }
1159
+
1160
+ function getFieldByName(name)
1161
+ {
1162
+ return $lib(function (el)
1163
+ {
1164
+ var fieldName = getFieldName(el);
1165
+
1166
+ if (fieldName && fieldName == name)
1167
+ return true;
1168
+
1169
+ return false;
1170
+ }, _container || getForm(), '*', true);
1171
+ }
1172
+
1173
+ function getRadioFields(el)
1174
+ {
1175
+ let name = getFieldName(el),
1176
+ root = getForm() || document,
1177
+ radios = root.querySelectorAll('input[type="radio"]'),
1178
+ fields = [];
1179
+
1180
+ $lib.each(radios, function (r)
1181
+ {
1182
+ if (name == getFieldName(r))
1183
+ fields.push(r);
1184
+ });
1185
+
1186
+ return fields;
1187
+ }
1188
+
1189
+ function getFieldName(el)
1190
+ {
1191
+ var name = el.getAttribute('name');
1192
+
1193
+ if (name && name.indexOf('$') > -1 && _instance.staticASPNETFieldNames)
1194
+ name = name.substr(name.lastIndexOf('$') + 1);
1195
+
1196
+ return name;
1197
+ }
1198
+
1199
+ function getFieldValue(fieldEl)
1200
+ {
1201
+ let value, field = _fields[getFieldName(fieldEl)];
1202
+
1203
+ if (_fieldValueFromCache && field.lastValue != null)
1204
+ return field.lastValue;
1205
+
1206
+ if (' input textarea '.indexOf(' ' + fieldEl.nodeName.toLowerCase() + ' ') > -1)
1207
+ {
1208
+ if (fieldEl.type == 'radio')
1209
+ {
1210
+ let fields = getRadioFields(fieldEl);
1211
+
1212
+ $lib.each(fields, function (el)
1213
+ {
1214
+ if (el.checked)
1215
+ {
1216
+ value = el.value;
1217
+ return false;
1218
+ }
1219
+ });
1220
+ }
1221
+ else
1222
+ value = fieldEl.value;
1223
+ }
1224
+ else if (fieldEl.nodeName.toLowerCase() == 'select')
1225
+ value = fieldEl.options[fieldEl.selectedIndex].value;
1226
+ else // content-editable
1227
+ {
1228
+ let grab = field.getValue;
1229
+ value = (grab) ? getMethod(grab)(fieldEl) : fieldEl.innerHTML;
1230
+ }
1231
+
1232
+ return value;
1233
+ }
1234
+
1235
+ function getForm()
1236
+ {
1237
+ if (_instance.formId)
1238
+ return $lib('#' + _instance.formId);
1239
+ }
1240
+
1241
+ function validateForm(e)
1242
+ {
1243
+ _formSubmit = true;
1244
+ return validate(null, false, e);
1245
+ }
1246
+
1247
+ function validate(container, filledOnly, e)
1248
+ {
1249
+ const shouldValidate = (el) =>
1250
+ {
1251
+ if (!isVisible(el))
1252
+ return false;
1253
+ if (el.disabled)
1254
+ return true;
1255
+ if (!filledOnly)
1256
+ return true;
1257
+ if (el.type === 'checkbox' || el.type === 'radio')
1258
+ return el.checked;
1259
+ return !$lib.isEmpty(getFieldValue(el));
1260
+ };
1261
+
1262
+ if ($lib.isEmpty(_fields))
1263
+ return validationComplete(e);
1264
+
1265
+ if (container)
1266
+ {
1267
+ let fieldEls = container.querySelectorAll(_query),
1268
+ validated = {};
1269
+
1270
+ $lib.each(fieldEls, function (el, index)
1271
+ {
1272
+ let name = getFieldName(el);
1273
+
1274
+ if (!validated[name] && _fields[name] && shouldValidate(el))
1275
+ {
1276
+ validated[name] = true;
1277
+ validateField(el, true);
1278
+ }
1279
+ });
1280
+ }
1281
+ else
1282
+ {
1283
+ $lib.each(_fields, function (field, fieldName)
1284
+ {
1285
+ let el = getFieldByName(fieldName);
1286
+
1287
+ if (shouldValidate(el))
1288
+ validateField(getFieldByName(fieldName), true);
1289
+ });
1290
+ }
1291
+
1292
+ if (!$lib.isEmpty(_ajaxFields))
1293
+ {
1294
+ let form = getForm();
1295
+
1296
+ if (form)
1297
+ $lib.off(form, 'submit', validateForm);
1298
+
1299
+ if (e)
1300
+ e.preventDefault();
1301
+
1302
+ return false;
1303
+ }
1304
+ else
1305
+ _formSubmit = false;
1306
+
1307
+ return validationComplete(e);
1308
+ }
1309
+
1310
+ function isVisible(el)
1311
+ {
1312
+ if (el.type === "hidden")
1313
+ return el.parentElement.offsetParent !== null;
1314
+ else
1315
+ return el.offsetParent !== null;
1316
+ }
1317
+
1318
+ function asyncValidationComplete()
1319
+ {
1320
+ var form = getForm();
1321
+ var formSubmit = _formSubmit;
1322
+ _formSubmit = false;
1323
+
1324
+ var isValid = validationComplete();
1325
+
1326
+ if (isValid && formSubmit)
1327
+ form.submit();
1328
+
1329
+ if (formSubmit)
1330
+ $lib.on(form, 'submit', validateForm);
1331
+ }
1332
+
1333
+ function validationComplete(e)
1334
+ {
1335
+ if (isValid())
1336
+ {
1337
+ var form = getForm();
1338
+
1339
+ if (form)
1340
+ _instance.setPostBackArgs(form);
1341
+
1342
+ _instance.events.onValid.fire(_instance);
1343
+ return true;
1344
+ }
1345
+ else
1346
+ {
1347
+ if (e)
1348
+ e.preventDefault();
1349
+
1350
+ _instance.events.onInvalid.fire(_instance);
1351
+ return false;
1352
+ }
1353
+ }
1354
+
1355
+ function isValid()
1356
+ {
1357
+ var valid = true;
1358
+
1359
+ if ($lib.isEmpty(_fields))
1360
+ return valid;
1361
+
1362
+ $lib.each(_fields, function (field)
1363
+ {
1364
+ return (valid = field.isValid);
1365
+ });
1366
+
1367
+ return valid;
1368
+ }
1369
+
1370
+ function validateFieldOnChange(field, summarise)
1371
+ {
1372
+ validateField(field, summarise);
1373
+ }
1374
+
1375
+ function validateFieldOnInput(field, summarise, e)
1376
+ {
1377
+ var keyCode = e.key;
1378
+
1379
+ clearLiveTimer();
1380
+
1381
+ if (keyCode == 'Tab')
1382
+ return;
1383
+
1384
+ _liveTimerId = setTimeout(function () { validateField(field, summarise); }, _instance.liveDelay);
1385
+ }
1386
+
1387
+ function clearLiveTimer()
1388
+ {
1389
+ clearTimeout(_liveTimerId);
1390
+ _liveTimerId = null;
1391
+ }
1392
+
1393
+ function validateField(fieldEl, summarise, visited = new Set())
1394
+ {
1395
+ if (visited.has(fieldEl))
1396
+ return;
1397
+
1398
+ _fieldValueFromCache = false;
1399
+
1400
+ let valid = true,
1401
+ validField = true,
1402
+ fieldName = getFieldName(fieldEl),
1403
+ fieldType = fieldEl.type,
1404
+ fieldValue = getFieldValue(fieldEl) || '',
1405
+ fieldData = _fields[fieldName],
1406
+ showAll = (_instance.messageDisplay == _messageDisplayOption.FEEDBACKALL || _instance.messageDisplay == _messageDisplayOption.FEEDBACKFIRST_TOOLTIPALL || _instance.messageDisplay == _messageDisplayOption.TOOLTIPALL)
1407
+ || (summarise && (_instance.summaryMessageDisplay == _messageDisplayOption.FEEDBACKALL || _instance.summaryMessageDisplay == _messageDisplayOption.FEEDBACKFIRST_TOOLTIPALL)),
1408
+ msg = [],
1409
+ dataType = (fieldData?.[_typeOption.DATATYPE]) ? fieldData[_typeOption.DATATYPE].dataType : null;
1410
+
1411
+ if (_liveTimerId)
1412
+ clearLiveTimer();
1413
+
1414
+ visited.add(fieldEl);
1415
+
1416
+ if (!fieldData)
1417
+ {
1418
+ validateDependentFields(fieldEl, visited);
1419
+ return;
1420
+ }
1421
+
1422
+ if (fieldEl.disabled)
1423
+ {
1424
+ clearFieldFeedback(fieldName);
1425
+ fieldData.isValid = true;
1426
+
1427
+ if (!summarise)
1428
+ validationComplete();
1429
+
1430
+ return;
1431
+ }
1432
+
1433
+ if (_ajaxFields[fieldName])
1434
+ {
1435
+ // cache validation when field is already in async validation
1436
+ let cached = _ajaxFields[fieldName].cached;
1437
+ cached.push(function () { validateField(fieldEl, summarise); });
1438
+ return;
1439
+ }
1440
+
1441
+ if (!fieldData[_typeOption.REQUIRED] && !fieldData[_typeOption.CUSTOM] && !fieldData[_typeOption.COMPARE] && !summarise && fieldData.lastValue != null && fieldData.lastValue === fieldValue && ' radio checkbox '.indexOf(fieldType) == -1)
1442
+ {
1443
+ if (_instance.messageDisplay >= _messageDisplayOption.TOOLTIPFIRST && fieldData.tooltipTrigger)
1444
+ _tooltipManager.showTooltip(fieldData.tooltipTrigger);
1445
+
1446
+ validationComplete();
1447
+ return;
1448
+ }
1449
+
1450
+ _fieldValueFromCache = true;
1451
+ fieldData.lastValue = fieldValue;
1452
+ fieldData.isValid = false;
1453
+
1454
+ if (fieldData[_typeOption.REQUIRED])
1455
+ validField = valid = validateRequired(fieldData[_typeOption.REQUIRED], fieldEl, msg);
1456
+
1457
+ if ((showAll || valid) && fieldData[_typeOption.COMPARE])
1458
+ {
1459
+ let dataType = fieldData[_typeOption.DATATYPE]?.dataType;
1460
+
1461
+ valid = validateCompare(fieldData[_typeOption.COMPARE], fieldValue, dataType, msg);
1462
+
1463
+ if (validField)
1464
+ validField = valid;
1465
+ }
1466
+
1467
+ if (!$lib.isEmpty(fieldValue))
1468
+ {
1469
+ if ((showAll || valid) && fieldData[_typeOption.DATATYPE])
1470
+ {
1471
+ valid = validateType(fieldData[_typeOption.DATATYPE], fieldValue, msg);
1472
+
1473
+ if (validField)
1474
+ validField = valid;
1475
+ }
1476
+
1477
+ if ((showAll || valid) && fieldData[_typeOption.LENGTH])
1478
+ {
1479
+ valid = validateLength(fieldData[_typeOption.LENGTH], fieldValue, msg);
1480
+
1481
+ if (validField)
1482
+ validField = valid;
1483
+ }
1484
+
1485
+ if ((showAll || valid) && fieldData[_typeOption.RANGE])
1486
+ {
1487
+ valid = validateRange(fieldData[_typeOption.RANGE], fieldValue, dataType, msg);
1488
+
1489
+ if (validField)
1490
+ validField = valid;
1491
+ }
1492
+
1493
+ if ((showAll || valid) && fieldData[_typeOption.REGEX])
1494
+ {
1495
+ valid = validateRegEx(fieldData[_typeOption.REGEX], fieldValue, msg);
1496
+
1497
+ if (validField)
1498
+ validField = valid;
1499
+ }
1500
+
1501
+ let settings = fieldData[_typeOption.CUSTOM];
1502
+
1503
+ if (settings && (settings.alwaysCheck || showAll || valid))
1504
+ {
1505
+ valid = validateCustom(settings, { settings: settings, field: fieldEl, fieldName: fieldName, fieldValue: fieldValue, valid: validField }, msg);
1506
+
1507
+ if (validField || settings.alwaysCheck)
1508
+ validField = valid;
1509
+ }
1510
+
1511
+ if (valid && fieldData[_typeOption.AJAX] && _instance.ajax.validate && _instance.ajax.validate.isDefined())
1512
+ {
1513
+ validateAjax(fieldData[_typeOption.AJAX], fieldEl, fieldName, fieldValue, summarise);
1514
+ let cached = _ajaxFields[fieldName].cached;
1515
+ cached.push(function () { validateDependentFields(fieldEl, visited); });
1516
+ return;
1517
+ }
1518
+ }
1519
+
1520
+ fieldData.isValid = validField;
1521
+ validateFieldComplete(fieldEl, summarise, msg);
1522
+ fieldData.silent = false;
1523
+ _fieldValueFromCache = false;
1524
+
1525
+ validateDependentFields(fieldEl, visited);
1526
+ }
1527
+
1528
+ function validateDependentFields(fieldEl, visited = new Set())
1529
+ {
1530
+ let dependentFields = [],
1531
+ fieldName = getFieldName(fieldEl),
1532
+ fieldData = _fields[fieldName];
1533
+
1534
+ if (!fieldData || !fieldData[_typeOption.COMPARE])
1535
+ dependentFields = getDependentFields(fieldEl);
1536
+
1537
+ dependentFields.forEach((fld) =>
1538
+ {
1539
+ let fieldName = getFieldName(fld),
1540
+ fieldData = _fields[fieldName];
1541
+
1542
+ if (fieldData)
1543
+ {
1544
+ fieldData.silent = true;
1545
+ validateField(fld, false, visited);
1546
+ }
1547
+ });
1548
+ }
1549
+
1550
+ function getDependentFields(fieldEl)
1551
+ {
1552
+ const dependentFields = [];
1553
+
1554
+ $lib.each(_fields, function (fld, name)
1555
+ {
1556
+ const compare = fld[_typeOption.COMPARE];
1557
+ if (!compare || !compare.fields)
1558
+ return;
1559
+
1560
+ compare.fields.forEach(cf =>
1561
+ {
1562
+ const cfEl = (cf instanceof HTMLElement)
1563
+ ? cf
1564
+ : getFieldByName(cf);
1565
+
1566
+ if (cfEl === fieldEl)
1567
+ dependentFields.push(getFieldByName(name));
1568
+ });
1569
+ });
1570
+
1571
+ return dependentFields;
1572
+ }
1573
+
1574
+ function validateRequired(settings, fieldEl, msg)
1575
+ {
1576
+ let valid = false;
1577
+
1578
+ if (settings.native && !settings.when)
1579
+ {
1580
+ valid = settings.checkValidity();
1581
+ }
1582
+ else
1583
+ {
1584
+ if (settings.when && !isRequired(settings.when, fieldEl))
1585
+ return true;
1586
+
1587
+ if (fieldEl.type == 'checkbox')
1588
+ valid = fieldEl.checked;
1589
+ else if (fieldEl.type == 'radio')
1590
+ {
1591
+ let fields = getRadioFields(fieldEl);
1592
+ $lib.each(fields, function (fld)
1593
+ {
1594
+ if (fld.checked)
1595
+ {
1596
+ valid = true;
1597
+ return false;
1598
+ }
1599
+ });
1600
+ }
1601
+ else
1602
+ {
1603
+ valid = !$lib.isEmpty(getFieldValue(fieldEl));
1604
+ }
1605
+ }
1606
+
1607
+ if (!valid && !$lib.isEmpty(settings.msg))
1608
+ msg.push(settings.msg);
1609
+
1610
+ return valid;
1611
+ }
1612
+
1613
+ function isRequired(when, field)
1614
+ {
1615
+ return getMethod(when)(_instance, { field: field });
1616
+ }
1617
+
1618
+ function validateType(settings, value, msg)
1619
+ {
1620
+ var valid = true;
1621
+
1622
+ if (settings.native)
1623
+ {
1624
+ valid = settings.checkValidity();
1625
+ }
1626
+ else
1627
+ {
1628
+ switch (settings.dataType)
1629
+ {
1630
+ case _dataTypeOption.INTEGER:
1631
+ {
1632
+ var intRegEx = new RegExp('^[0-9]*[' + $lib.escapeRegExp(_instance.groupSeparator) + ']*[0-9]+$');
1633
+ valid = intRegEx.test(value);
1634
+ break;
1635
+ }
1636
+ case _dataTypeOption.FLOAT:
1637
+ {
1638
+ var floatRegEx = new RegExp('^[0-9]*[' + $lib.escapeRegExp(_instance.groupSeparator) + ']?[0-9]*[' + $lib.escapeRegExp(_instance.decimalSeparator) + ']?[0-9]+$');
1639
+ valid = floatRegEx.test(value);
1640
+ break;
1641
+ }
1642
+ case _dataTypeOption.DATETIME:
1643
+ {
1644
+ var date = $lib.parseDate(value, _instance.dateFormat.toLowerCase());
1645
+
1646
+ if (!date)
1647
+ valid = false;
1648
+
1649
+ break;
1650
+ }
1651
+ case _dataTypeOption.EMAIL:
1652
+ {
1653
+ valid = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/.test(value);
1654
+ break;
1655
+ }
1656
+ case _dataTypeOption.URL:
1657
+ {
1658
+ valid = /^(https?|ftp):\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]$/.test(value);
1659
+ break;
1660
+ }
1661
+ case _dataTypeOption.SOURCE:
1662
+ {
1663
+ valid = /^(blob:)?(https?|ftp|file):\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]$/.test(value) // absolute URL with allowed protocols, blob: URLs for file inputs
1664
+ || /^\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]*$/.test(value); // relative URL starting with /
1665
+ break;
1666
+ }
1667
+ }
1668
+ }
1669
+
1670
+ return addMsg(settings, msg, valid);
1671
+ }
1672
+
1673
+ function validateLength(settings, value, msg)
1674
+ {
1675
+ let valid = true, length = value.length;
1676
+
1677
+ if (settings.native)
1678
+ {
1679
+ valid = settings.checkValidity();
1680
+ }
1681
+ else
1682
+ {
1683
+ if (!$lib.isEmpty(settings.min))
1684
+ valid = (length >= settings.min);
1685
+
1686
+ if (valid && !$lib.isEmpty(settings.max))
1687
+ valid = (length <= settings.max);
1688
+ }
1689
+
1690
+ return addMsg(settings, msg, valid);
1691
+ }
1692
+
1693
+ function validateRange(settings, value, dataType, msg)
1694
+ {
1695
+ var valid = true;
1696
+
1697
+ if (settings.native)
1698
+ {
1699
+ valid = settings.checkValidity();
1700
+ }
1701
+ else
1702
+ {
1703
+ if (dataType == _dataTypeOption.DATETIME)
1704
+ {
1705
+ var date = $lib.parseDate(value, _instance.dateFormat.toLowerCase()),
1706
+ minDate = $lib.parseDate(settings.min, _instance.dateFormat.toLowerCase()),
1707
+ maxDate = $lib.parseDate(settings.max, _instance.dateFormat.toLowerCase());
1708
+
1709
+ if (date)
1710
+ {
1711
+ if (minDate)
1712
+ valid = comparableDate(date) >= comparableDate(minDate);
1713
+
1714
+ if (maxDate && valid)
1715
+ valid = comparableDate(date) <= comparableDate(maxDate);
1716
+ }
1717
+ else
1718
+ valid = false;
1719
+ }
1720
+ else if (dataType == _dataTypeOption.INTEGER || dataType == _dataTypeOption.FLOAT)
1721
+ {
1722
+ var number = value.replace(_instance.decimalSeparator, '.');
1723
+
1724
+ valid = (!isNaN(number));
1725
+
1726
+ if (valid)
1727
+ {
1728
+ if (!isNaN(settings.min))
1729
+ valid = parseFloat(number) >= parseFloat(settings.min);
1730
+
1731
+ if (valid && !isNaN(settings.max))
1732
+ valid = parseFloat(number) <= parseFloat(settings.max);
1733
+ }
1734
+ }
1735
+ else
1736
+ {
1737
+ if (!$lib.isEmpty(settings.min))
1738
+ valid = (value >= settings.min);
1739
+
1740
+ if (valid && !$lib.isEmpty(settings.max))
1741
+ valid = (value <= settings.max);
1742
+ }
1743
+ }
1744
+
1745
+ return addMsg(settings, msg, valid);
1746
+ }
1747
+
1748
+ function validateRegEx(settings, value, msg)
1749
+ {
1750
+ let valid = false;
1751
+
1752
+ if (settings.native)
1753
+ {
1754
+ valid = settings.checkValidity();
1755
+ }
1756
+ else
1757
+ {
1758
+ if (typeof settings.pattern == 'string')
1759
+ settings.pattern = new RegExp(settings.pattern);
1760
+
1761
+ valid = settings.pattern.test(value);
1762
+ }
1763
+
1764
+ return addMsg(settings, msg, valid);
1765
+ }
1766
+
1767
+ function validateCompare(settings, value, dataType, msg)
1768
+ {
1769
+ const fields = settings.fields,
1770
+ operators = settings.operators || [];
1771
+
1772
+ let valid = true;
1773
+
1774
+ for (let i = 0; i < fields.length; i++)
1775
+ {
1776
+ const f = fields[i],
1777
+ operator = operators[i] || operators[0] || '==';
1778
+
1779
+ let compareValue;
1780
+
1781
+ if (f instanceof HTMLElement)
1782
+ {
1783
+ compareValue = f.value;
1784
+ }
1785
+ else if (typeof f === 'string')
1786
+ {
1787
+ const el = getFieldByName(f);
1788
+ compareValue = el ? el.value : f; // fallback: treat as literal value
1789
+ } else
1790
+ {
1791
+ compareValue = f; // literal value
1792
+ }
1793
+
1794
+ // convert types
1795
+ if (dataType === _dataTypeOption.INTEGER || dataType === _dataTypeOption.FLOAT)
1796
+ {
1797
+ const v1 = parseFloat(value.replace(_instance.decimalSeparator, '.')),
1798
+ v2 = parseFloat(compareValue.replace(_instance.decimalSeparator, '.'));
1799
+
1800
+ if (isNaN(v1) || isNaN(v2))
1801
+ return addMsg(settings, msg, false);
1802
+
1803
+ value = v1;
1804
+ compareValue = v2;
1805
+ } else if (dataType === _dataTypeOption.DATETIME)
1806
+ {
1807
+ const fmt = _instance.dateFormat.toLowerCase(),
1808
+ d1 = $lib.parseDate(value, fmt),
1809
+ d2 = $lib.parseDate(compareValue, fmt);
1810
+
1811
+ if (!d1 || !d2)
1812
+ return addMsg(settings, msg, false);
1813
+
1814
+ value = comparableDate(d1);
1815
+ compareValue = comparableDate(d2);
1816
+ }
1817
+
1818
+ // compare
1819
+ switch (operator)
1820
+ {
1821
+ case '<': valid = value < compareValue; break;
1822
+ case '<=': valid = value <= compareValue; break;
1823
+ case '>=': valid = value >= compareValue; break;
1824
+ case '>': valid = value > compareValue; break;
1825
+ default: valid = value == compareValue;
1826
+ }
1827
+
1828
+ if (!valid) break;
1829
+ }
1830
+
1831
+ return addMsg(settings, msg, valid);
1832
+ }
1833
+
1834
+
1835
+ function validateCustom(settings, obj, msg)
1836
+ {
1837
+ return addMsg(settings, msg, getMethod(settings.onValidation)(_instance, obj));
1838
+ }
1839
+
1840
+ function validateAjax(settings, fieldEl, name, value, summarise)
1841
+ {
1842
+ var data;
1843
+
1844
+ _ajaxFields[name] = { cached: [] };
1845
+
1846
+ if (settings.onBeforeValidation)
1847
+ data = getMethod(settings.onBeforeValidation)(_instance, { field: fieldEl, fieldName: name, fieldValue: value, feedbackId: _fields[name].feedbackId, settings: settings });
1848
+
1849
+ if (settings.data)
1850
+ data = (data) ? $lib.clone(data, settings.data, true) : settings.data;
1851
+
1852
+ if (data && data.validationFields && data.validationFields[0])
1853
+ {
1854
+ if ($lib.isEmpty(data.validationFields[0].fieldName))
1855
+ data.validationFields[0].fieldName = name;
1856
+
1857
+ if ($lib.isEmpty(data.validationFields[0].value))
1858
+ data.validationFields[0].value = value;
1859
+ }
1860
+
1861
+ _validatingTimerId = setTimeout(function () { addFieldFeedback(fieldEl, _feedbackStatus.VALIDATING, _fields[name].feedbackId, false); }, _instance.validatingFieldMessageDelay);
1862
+
1863
+ _instance.ajaxCall('validate', data,
1864
+ {
1865
+ onSuccess: function (ajaxArgs)
1866
+ {
1867
+ validateAjaxFieldComplete(fieldEl, name, value, settings, summarise, ajaxArgs);
1868
+ },
1869
+ onError: function (ajaxArgs)
1870
+ {
1871
+ validateAjaxFieldComplete(fieldEl, name, value, settings, summarise, ajaxArgs, true);
1872
+ }
1873
+ });
1874
+ }
1875
+
1876
+ function addMsg(settings, msg, valid)
1877
+ {
1878
+ if (!valid && !$lib.isEmpty(settings.msg) && msg)
1879
+ msg.push(settings.msg);
1880
+
1881
+ return valid;
1882
+ }
1883
+
1884
+ function validateAjaxFieldComplete(field, fieldName, fieldValue, settings, summarise, ajaxArgs, ajaxError)
1885
+ {
1886
+ var msg,
1887
+ fieldData = _fields[fieldName],
1888
+ cached = _ajaxFields[fieldName].cached,
1889
+ data = settings.data;
1890
+
1891
+ if (_validatingTimerId)
1892
+ clearTimeout(_validatingTimerId);
1893
+
1894
+ _validatingTimerId = null;
1895
+ fieldData.lastValue = fieldData.lastMsg = null;
1896
+ fieldData.isValid = false;
1897
+ fieldData.silent = false;
1898
+ msg = _instance.ajaxErrorMessage;
1899
+
1900
+ if (data && data.validationFields)
1901
+ {
1902
+ $lib.each(data.validationFields, function (field)
1903
+ {
1904
+ field.fieldName = field.value = null;
1905
+ });
1906
+ }
1907
+
1908
+ if (!ajaxError)
1909
+ {
1910
+ msg = (!$lib.isEmpty(ajaxArgs.data.msg)) ? ajaxArgs.data.msg : settings.msg;
1911
+
1912
+ fieldData.lastValue = fieldValue;
1913
+ fieldData.lastMsg = msg;
1914
+ fieldData.isValid = ajaxArgs.data.isValid;
1915
+
1916
+ if (!ajaxArgs.data.isValid && (!msg.constructor || msg.constructor.toString().indexOf('Array') == -1))
1917
+ msg = [msg];
1918
+
1919
+ if (settings.onAfterValidation)
1920
+ getMethod(settings.onAfterValidation)(_instance, { field: field, fieldName: fieldName, fieldValue: fieldValue });
1921
+ }
1922
+
1923
+ validateFieldComplete(field, summarise, msg, true);
1924
+ delete _ajaxFields[fieldName];
1925
+
1926
+ if (cached && cached.length)
1927
+ cached.forEach((callback) => callback());
1928
+ }
1929
+
1930
+ function validateFieldComplete(fieldEL, summarise, msg, async)
1931
+ {
1932
+ var fieldName = getFieldName(fieldEL),
1933
+ fieldValue = getFieldValue(fieldEL),
1934
+ fieldData = _fields[fieldName],
1935
+ showValid = _instance.showValidFields,
1936
+ args = eventArgs(fieldEL, fieldName, fieldValue, fieldData, msg);
1937
+
1938
+ clearCss(fieldEL);
1939
+
1940
+ // fire event
1941
+ (fieldData.isValid) ? _instance.events.onValidField.fire(_instance, args) : _instance.events.onInvalidField.fire(_instance, args);
1942
+
1943
+ if (fieldData.isValid && (showValid == _showValidFieldsOption.ALL
1944
+ || (showValid == _showValidFieldsOption.FILLED && !$lib.isEmpty(fieldValue))
1945
+ || (showValid == _showValidFieldsOption.REQUIRED && fieldData[_typeOption.REQUIRED])))
1946
+ {
1947
+ clearFieldFeedback(fieldName);
1948
+ addFieldFeedback(fieldEL, _feedbackStatus.VALID, _fields[fieldName].feedbackId, summarise, msg);
1949
+ }
1950
+ else if (!fieldData.isValid)
1951
+ {
1952
+ addFieldFeedback(fieldEL, _feedbackStatus.INVALID, _fields[fieldName].feedbackId, summarise, msg);
1953
+ }
1954
+ else
1955
+ {
1956
+ clearFieldFeedback(fieldName);
1957
+ }
1958
+
1959
+ if (!summarise && !async)
1960
+ validationComplete();
1961
+ else if (async && $lib.isEmpty(_ajaxFields))
1962
+ asyncValidationComplete();
1963
+ }
1964
+
1965
+ function eventArgs()
1966
+ {
1967
+ var args = arguments;
1968
+ return { fieldElement: args[0], fieldName: args[1], fieldValue: args[2], fieldData: args[3], msg: args[4], feedbackElement: args[5], summaryElement: args[6] }
1969
+ }
1970
+
1971
+ function comparableDate(date)
1972
+ {
1973
+ return parseInt(date.getFullYear() + addNil(date.getMonth(), true) + addNil(date.getDate(), true));
1974
+ }
1975
+
1976
+ function addNil(val, leadingZero)
1977
+ {
1978
+ val = val.toString();
1979
+
1980
+ if (!leadingZero)
1981
+ return val;
1982
+
1983
+ return (val.length != 2) ? '0' + val : val;
1984
+ }
1985
+
1986
+ function addFieldFeedback(fieldEl, status, feedbackId, summarise, msg)
1987
+ {
1988
+ if (!fieldEl)
1989
+ return;
1990
+
1991
+ var feedbackContainer = (feedbackId) ? $lib('#' + feedbackId) : getFeedbackElement(fieldEl),
1992
+ summaryContainer = (_instance.summaryContainerId) ? $lib('#' + _instance.summaryContainerId) : null,
1993
+ fieldName = getFieldName(fieldEl),
1994
+ fieldValue = getFieldValue(fieldEl),
1995
+ fieldData = _fields[fieldName],
1996
+ silent = fieldData.silent,
1997
+ values = {},
1998
+ summarise = summaryContainer && summarise,
1999
+ triggerClass = _instance.cssClassTooltipTrigger || _classOption.TOOLTIPTRIGGER,
2000
+ invalidCssClass = _instance.cssClassInvalid || _classOption.INVALID,
2001
+ validCssClass = _instance.cssClassValid || _classOption.VALID,
2002
+ validatingCssClass = _instance.cssClassValidating || _classOption.VALIDATING,
2003
+ ulFeedback, ulSummary, ul, li, liSummary;
2004
+
2005
+ if (!silent)
2006
+ {
2007
+ if (feedbackContainer && _instance.messageDisplay > _messageDisplayOption.NONE)
2008
+ {
2009
+ ul = document.createElement('ul');
2010
+
2011
+ if (fieldData.feedbackElement?.isConnected)
2012
+ {
2013
+ ulFeedback = fieldData.feedbackElement;
2014
+ ulFeedback.parentNode.replaceChild(ul, ulFeedback);
2015
+ }
2016
+
2017
+ fieldData.feedbackElement = ulFeedback = ul;
2018
+ addValidatorClass(ulFeedback);
2019
+ $lib.addClass(ulFeedback, _instance.cssClassFeedback || _classOption.FEEDBACK);
2020
+ feedbackContainer.appendChild(ulFeedback);
2021
+ }
2022
+
2023
+ if (summarise && ((status == _feedbackStatus.INVALID && _instance.summaryMessageDisplay > _messageDisplayOption.NONE) || (status == _feedbackStatus.VALID && _instance.showValidFieldsInSummary)))
2024
+ {
2025
+ ul = document.createElement('ul');
2026
+
2027
+ if (fieldData.summaryElement?.isConnected)
2028
+ {
2029
+ ulSummary = fieldData.summaryElement;
2030
+ ulSummary.parentNode.replaceChild(ul, ulSummary);
2031
+ }
2032
+
2033
+ fieldData.summaryElement = ulSummary = ul;
2034
+ addValidatorClass(ulSummary);
2035
+ $lib.addClass(ulSummary, _instance.cssClassSummary || _classOption.SUMMARY);
2036
+ summaryContainer.appendChild(ulSummary);
2037
+ }
2038
+
2039
+ values['fieldName'] = _instance.friendlyFieldNames[fieldName] || fieldName;
2040
+ values['icon'] = $lib.format('<span class="{0}"></span>', _instance.cssClassIcon || _classOption.ICON);
2041
+ }
2042
+
2043
+ switch (status)
2044
+ {
2045
+ case _feedbackStatus.VALIDATING:
2046
+ {
2047
+ if (_instance.validationCssOnField)
2048
+ $lib.addClass(fieldEl, validatingCssClass);
2049
+
2050
+ if (!ulFeedback)
2051
+ break;
2052
+
2053
+ values['message'] = _instance.validatingFieldMessage;
2054
+
2055
+ if (ulFeedback)
2056
+ createItem(ulFeedback, validatingCssClass, 'ValidatingFieldFeedback', values);
2057
+
2058
+ break;
2059
+ }
2060
+ case _feedbackStatus.VALID:
2061
+ {
2062
+ if (_instance.validationCssOnField)
2063
+ $lib.addClass(fieldEl, validCssClass);
2064
+
2065
+ if (!ulFeedback && !ulSummary)
2066
+ break;
2067
+
2068
+ values['message'] = _instance.validFieldMessage;
2069
+
2070
+ if (ulFeedback)
2071
+ createItem(ulFeedback, validCssClass, 'ValidFieldFeedback', values);
2072
+
2073
+ if (ulSummary)
2074
+ createItem(ulSummary, validCssClass, 'ValidFieldSummary', values);
2075
+
2076
+ break;
2077
+ }
2078
+ case _feedbackStatus.INVALID:
2079
+ {
2080
+ if (_instance.validationCssOnField)
2081
+ $lib.addClass(fieldEl, invalidCssClass);
2082
+
2083
+ if (silent || (!ulFeedback && !ulSummary && _instance.messageDisplay < 3))
2084
+ break;
2085
+
2086
+ let multipleStart = $lib.format('<span class="{0}">', _instance.cssClassMultiple || _classOption.MULTIPLE),
2087
+ tooltipTriggerStart = $lib.format('<span class="{0}">', triggerClass),
2088
+ spanEnd = '</span>',
2089
+ tooltipTriggerKey = 'tooltipTrigger',
2090
+ multipleKey = 'multiple';
2091
+
2092
+ $lib.addClass(ulFeedback, invalidCssClass);
2093
+ values['messageCount'] = msg.length;
2094
+ values['tooltipTrigger'] = values['/tooltipTrigger'] = '';
2095
+ values['multiple'] = '<span style="display:none;">';
2096
+ values['/multiple'] = '</span>';
2097
+
2098
+ if (!msg.length)
2099
+ msg.push(_instance.invalidFieldMessage);
2100
+
2101
+ if (msg.length > 1)
2102
+ {
2103
+ if (_instance.messageDisplay == _messageDisplayOption.FEEDBACKFIRST_TOOLTIPALL)
2104
+ {
2105
+ values[multipleKey] = multipleStart;
2106
+ values[tooltipTriggerKey] = tooltipTriggerStart;
2107
+ values['/' + tooltipTriggerKey] = spanEnd;
2108
+ }
2109
+ }
2110
+
2111
+ $lib.each(msg, function (msg, index)
2112
+ {
2113
+ values['message'] = msg;
2114
+
2115
+ if (ulFeedback)
2116
+ {
2117
+ if (_instance.messageDisplay < _messageDisplayOption.TOOLTIPFIRST && (index == 0 || _instance.messageDisplay == _messageDisplayOption.FEEDBACKALL))
2118
+ li = createItem(ulFeedback, invalidCssClass, 'InvalidFieldFeedback', values);
2119
+ }
2120
+
2121
+ if (ulSummary)
2122
+ {
2123
+ if (index == 0 || _instance.messageDisplay == _messageDisplayOption.FEEDBACKALL)
2124
+ liSummary = createItem(ulSummary, invalidCssClass, 'InvalidFieldSummary', values);
2125
+ }
2126
+ });
2127
+
2128
+ if (msg.length > 0)
2129
+ {
2130
+ if (_instance.messageDisplay >= _messageDisplayOption.TOOLTIPFIRST)
2131
+ addTooltip(fieldEl, msg, false, fieldData);
2132
+ else if (ulFeedback && _instance.messageDisplay == _messageDisplayOption.FEEDBACKFIRST_TOOLTIPALL)
2133
+ addTooltip($lib(triggerClass, li, '', true), msg, true, fieldData);
2134
+
2135
+ if (ulSummary && _instance.summaryMessageDisplay == _messageDisplayOption.FEEDBACKFIRST_TOOLTIPALL)
2136
+ addTooltip($lib(triggerClass, liSummary, '', true), msg, true, fieldData, true);
2137
+ }
2138
+
2139
+ break;
2140
+ }
2141
+ }
2142
+
2143
+ fieldData.lastMsg = msg;
2144
+
2145
+ _instance.events.onFieldFeedback.fire(_instance, eventArgs(fieldEl, fieldName, fieldValue, fieldData, msg, ulFeedback, ulSummary));
2146
+ }
2147
+
2148
+ function createItem(ul, cssClass, template, values)
2149
+ {
2150
+ $lib.addClass(ul, cssClass);
2151
+ var li = ul.appendChild(document.createElement('li'));
2152
+ _instance.applyTemplate(li, template, values);
2153
+
2154
+ return li;
2155
+ }
2156
+
2157
+ function clearFieldFeedback(fieldName)
2158
+ {
2159
+ if (!fieldName)
2160
+ return;
2161
+
2162
+ var fieldData = _fields[fieldName];
2163
+
2164
+ if (fieldData.feedbackElement)
2165
+ fieldData.feedbackElement.innerHTML = '';
2166
+ if (fieldData.summaryElement)
2167
+ fieldData.summaryElement.innerHTML = '';
2168
+
2169
+ fieldData.lastMsg = null;
2170
+
2171
+ if (fieldData.tooltipId)
2172
+ {
2173
+ _tooltipManager.hideTooltip(fieldData.tooltipTrigger);
2174
+ _tooltipManager.removeTrigger(fieldData.tooltipTrigger);
2175
+ fieldData.tooltipId = fieldData.tooltipTrigger = null;
2176
+ }
2177
+
2178
+ if (fieldData.summaryTooltipId)
2179
+ {
2180
+ _tooltipManager.hideTooltip(fieldData.summaryTooltipTrigger);
2181
+ _tooltipManager.removeTrigger(fieldData.summaryTooltipTrigger);
2182
+ fieldData.summaryTooltipId = fieldData.summaryTooltipTrigger = null;
2183
+ }
2184
+
2185
+ if (fieldData.element)
2186
+ clearCss(fieldData.element);
2187
+ }
2188
+
2189
+ function clearCss(element)
2190
+ {
2191
+ if (!element)
2192
+ return;
2193
+
2194
+ var invalidCssClass = _instance.cssClassInvalid || _classOption.INVALID,
2195
+ validCssClass = _instance.cssClassValid || _classOption.VALID,
2196
+ validatingCssClass = _instance.cssClassValidating || _classOption.VALIDATING;
2197
+
2198
+ $lib.removeClass(element, validatingCssClass + ' ' + validCssClass + ' ' + invalidCssClass);
2199
+ }
2200
+
2201
+ function createTooltipManager()
2202
+ {
2203
+ var id = _instance.id + '_TooltipManager';
2204
+
2205
+ _tooltipManager = $UI.createComponent(componyx.UI.TooltipManager, { id: id });
2206
+ _tooltipManager.clone($UI.store[_instance.tooltipManagerId], _instance, true, true, { triggers: '' });
2207
+ _tooltipManager.singleBoxInstance = false;
2208
+
2209
+ if (!_tooltipManager.boxId)
2210
+ {
2211
+ var box = $UI.createComponent(componyx.UI.Box, { id: id + '_TempBox' });
2212
+ box.expandDirection = componyx.UI.Box.ExpandDirectionOption.DOWN;
2213
+ box.autoPosition = componyx.UI.Box.AutoPositionOption.EXPAND;
2214
+ box.autoInvertFit = true;
2215
+ _tooltipManager.boxId = box.id;
2216
+ }
2217
+
2218
+ _tooltipManager.show();
2219
+ }
2220
+
2221
+ function addTooltip(el, msg, isTrigger, fieldData, summary)
2222
+ {
2223
+ if (!el)
2224
+ return;
2225
+
2226
+ var ul = document.createElement('ul'), values = {},
2227
+ id = (summary) ? fieldData.summaryTooltipId : fieldData.tooltipId;
2228
+
2229
+ if (!id)
2230
+ id = $lib.guid();
2231
+
2232
+ if (summary)
2233
+ {
2234
+ fieldData.summaryTooltipId = id;
2235
+ fieldData.summaryTooltipTrigger = el;
2236
+ }
2237
+ else
2238
+ {
2239
+ fieldData.tooltipId = id;
2240
+ fieldData.tooltipTrigger = el;
2241
+ }
2242
+
2243
+ addValidatorClass(ul);
2244
+ $lib.addClass(ul, 'tooltip');
2245
+
2246
+ $lib.each(msg, function (msg)
2247
+ {
2248
+ values['message'] = msg;
2249
+ _instance.applyTemplate(ul.appendChild(document.createElement('li')), 'InvalidFieldPopup', values);
2250
+ });
2251
+
2252
+ _tooltipManager.addTooltip(id, ul); // add or update
2253
+
2254
+ if (isTrigger) // trigger element
2255
+ {
2256
+ el.setAttribute('tabindex', _instance.tooltipTriggerTabIndex || '0');
2257
+ _tooltipManager.addTrigger(el, id);
2258
+ }
2259
+ else // field
2260
+ _tooltipManager.addTrigger(el, id, 0, false, false); // show on blur
2261
+
2262
+ if (!isTrigger)
2263
+ _tooltipManager.showTooltip(el);
2264
+ }
2265
+
2266
+ function hideTooltip(fieldName)
2267
+ {
2268
+ var fieldData = _fields[fieldName];
2269
+
2270
+ if (fieldData.tooltipTrigger)
2271
+ _tooltipManager.hideTooltip(fieldData.tooltipTrigger);
2272
+ }
2273
+
2274
+ function getFeedbackElement(field)
2275
+ {
2276
+ if (!_instance.feedbackIdentifyingCssClass)
2277
+ return null;
2278
+
2279
+ var feedbackElement = null;
2280
+
2281
+ $lib(function (el, stop)
2282
+ {
2283
+ if (feedbackElement = $lib(_instance.feedbackIdentifyingCssClass, el)[0])
2284
+ {
2285
+ stop();
2286
+ return false;
2287
+ }
2288
+
2289
+ return false;
2290
+
2291
+ }, field, '', false, true);
2292
+
2293
+ return feedbackElement || null;
2294
+ }
2295
+
2296
+ function addValidatorClass(el)
2297
+ {
2298
+ $lib.addClass(el, _instance.className || _classOption.VALIDATOR);
2299
+
2300
+ if (_instance.theme > $base.static.ThemeOption.NONE)
2301
+ {
2302
+ $lib.addClass(el, 'theme');
2303
+ $lib.addClass(el, _themes[parseInt(_instance.theme, 10) - 1]);
2304
+ }
2305
+ }
2306
+
2307
+ function getMethod(method)
2308
+ {
2309
+ return $base.static.getMethod(method);
2310
+ }
2311
+
2312
+ function hasRule(name)
2313
+ {
2314
+ if (!_fields[name])
2315
+ return false;
2316
+
2317
+ for (var key in _typeOption)
2318
+ {
2319
+ if (_fields[name][_typeOption[key]])
2320
+ return true;
2321
+ }
2322
+
2323
+ return false;
2324
+ }
2325
+
2326
+ function resetState(field, name)
2327
+ {
2328
+ if (!field)
2329
+ return;
2330
+
2331
+ field.isValid = false;
2332
+ field.lastValue = null;
2333
+
2334
+ if (!field[_typeOption.REQUIRED] && $lib.isEmpty(getFieldValue(field.element || getFieldByName(name)))) // not required and no field value
2335
+ field.isValid = true;
2336
+ }
2337
+
2338
+ function removeBinding(name)
2339
+ {
2340
+ let fieldEl = getFieldByName(name),
2341
+ fieldEls = [fieldEl];
2342
+
2343
+ if (!fieldEl)
2344
+ return;
2345
+
2346
+ if (fieldEl.type == 'radio')
2347
+ fieldEls = getRadioFields(fieldEl);
2348
+
2349
+ $lib.each(fieldEls, function (fld)
2350
+ {
2351
+ $lib.off(fld, 'blur change', validateFieldOnChange);
2352
+ $lib.off(fld, 'focus', hideTooltip);
2353
+ $lib.off(fld, 'input', validateFieldOnInput);
2354
+ });
2355
+ }
2356
+ };
2357
+
2358
+ /**
2359
+ * @see {@link componyx.UI.base.methods}
2360
+ */
2361
+ componyx.UI.Validator.prototype = Object.create($base.methods);
2362
+ componyx.UI.Validator.prototype.constructor = componyx.UI.Validator;
2363
+
2364
+ /**
2365
+ * TypeOption
2366
+ * @readonly
2367
+ * @enum {number}
2368
+ * @memberof componyx.UI.Validator
2369
+ */
2370
+ componyx.UI.Validator.TypeOption =
2371
+ {
2372
+ REQUIRED: 0,
2373
+ DATATYPE: 1,
2374
+ LENGTH: 2,
2375
+ RANGE: 3,
2376
+ REGEX: 4,
2377
+ COMPARE: 5,
2378
+ CUSTOM: 6,
2379
+ AJAX: 7
2380
+ };
2381
+
2382
+ /**
2383
+ * DataTypeOption
2384
+ * @readonly
2385
+ * @enum {number}
2386
+ * @memberof componyx.UI.Validator
2387
+ */
2388
+ componyx.UI.Validator.DataTypeOption =
2389
+ {
2390
+ INTEGER: 0,
2391
+ FLOAT: 1,
2392
+ DATETIME: 2,
2393
+ EMAIL: 3,
2394
+ URL: 4,
2395
+ SOURCE: 5
2396
+ };
2397
+
2398
+ /**
2399
+ * ShowValidFieldsOption
2400
+ * @readonly
2401
+ * @enum {number}
2402
+ * @memberof componyx.UI.Validator
2403
+ */
2404
+ componyx.UI.Validator.ShowValidFieldsOption =
2405
+ {
2406
+ NONE: 0,
2407
+ REQUIRED: 1,
2408
+ FILLED: 2,
2409
+ ALL: 3
2410
+ };
2411
+
2412
+ /**
2413
+ * MessageDisplayOption
2414
+ * @readonly
2415
+ * @enum {number}
2416
+ * @memberof componyx.UI.Validator
2417
+ */
2418
+ componyx.UI.Validator.MessageDisplayOption =
2419
+ {
2420
+ NONE: 0,
2421
+ FEEDBACKFIRST: 1,
2422
+ FEEDBACKALL: 2,
2423
+ FEEDBACKFIRST_TOOLTIPALL: 3,
2424
+ TOOLTIPFIRST: 4,
2425
+ TOOLTIPALL: 5
2426
+ };
2427
+
2428
+ /**
2429
+ * @typedef {object} ValidationData
2430
+ * @memberof componyx.UI.Validator
2431
+ * @property {string} fieldName The name of the field to validate.
2432
+ * @property {string} value The value of the field to validate.
2433
+ */
2434
+
2435
+ /**
2436
+ * @typedef {object} ValidationArgs
2437
+ * @memberof componyx.UI.Validator
2438
+ * @property {HTMLElement} [field] The field.
2439
+ * @property {string} [fieldName] The field name.
2440
+ * @property {string} [fieldValue] The field value.
2441
+ * @property {string} [feedbackId] The field feedback identifier.
2442
+ * @property {object} [settings] The validation settings of the field.
2443
+ */
2444
+
2445
+ /**
2446
+ * @callback OnValidation
2447
+ * @memberof componyx.UI.Validator
2448
+ * @param {componyx.UI.Validator} sender
2449
+ * @param {ValidationArgs} args
2450
+ * @returns {boolean} A value indicating if the validation result is valid or invalid.
2451
+ */
2452
+ /**
2453
+ * @callback OnBeforeValidation
2454
+ * @memberof componyx.UI.Validator
2455
+ * @param {componyx.UI.Validator} sender
2456
+ * @param {ValidationArgs} args
2457
+ * @returns {ValidationData[]} The validation data.
2458
+ */
2459
+ /**
2460
+ * @callback OnAfterValidation
2461
+ * @memberof componyx.UI.Validator
2462
+ * @param {componyx.UI.Validator} sender
2463
+ * @param {ValidationArgs} args
2464
+ */
2465
+
2466
+ /**
2467
+ * Creates a Rule object instance.
2468
+ * @class
2469
+ * @property {String} fieldName Gets or sets the field name to which the rule applies.
2470
+ * @property {TypeOption} type Gets or sets the validation type of the rule.
2471
+ * @param {String} feedbackId Gets or sets the identifier of the element used by the Validator to display validation result feedback.
2472
+ * @param {Boolean} autoValidate Gets or sets a value indicating if the field is validated when the field value changes.
2473
+ * @param {Boolean} live Gets or sets a value indicating if the field validation is done while typing.
2474
+ * @param {Function} getValue A custom method to return the value for this field. Passes in the field element and expects the field value as result.
2475
+ * @param {String} msg Gets or sets the message to display if validation fails.
2476
+ * @param {Number|String} min Gets or sets the minimum value. (LENGTH and RANGE type validation).
2477
+ * @param {Number|String} max Gets or sets the maximum value. (LENGTH and RANGE type validation).
2478
+ * @param {RegExp} pattern Gets or sets the Regular expression pattern (REGEX type validation).
2479
+ * @param {HTMLElement|String} field Gets or sets the element or element id (COMPARE type validation).
2480
+ * @param {componyx.UI.DataTypeOption} dataType Gets or sets the data type (DATATYPE type validation).
2481
+ * @param {componyx.UI.OnValidation} onValidation Gets or sets a function invoked when the field is validated (CUSTOM type validation).
2482
+ * @param {componyx.UI.OnBeforeValidation} onBeforeValidation Gets or sets a function invoked before the field is validated (AJAX type validation).
2483
+ * @param {componyx.UI.OnAfterValidation} onAfterValidation Gets or sets a function invoked after the field is validated (AJAX type validation).
2484
+ * @param {Object} data Gets or sets the data query for the AJAX type validation.
2485
+ */
2486
+ componyx.UI.Validator.Rule = function ()
2487
+ {
2488
+ this.fieldName = null;
2489
+ this.type = null;
2490
+ this.feedbackId = null;
2491
+ this.autoValidate = null;
2492
+ this.getValue = null;
2493
+ this.live = null;
2494
+ this.msg = null;
2495
+ this.min = null;
2496
+ this.max = null;
2497
+ this.pattern = null;
2498
+ this.field = null;
2499
+ this.dataType = null;
2500
+ this.onValidation = null;
2501
+ this.onBeforeValidation = null;
2502
+ this.onAfterValidation = null;
2503
+ };
2504
+ })(window);
2505
+
2506
+ const Validator = componyx.UI.Validator;
2507
+
2508
+ export { Validator };