client_side_validations 23.1.0 → 24.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +103 -63
- data/lib/client_side_validations/action_view/form_helper.rb +1 -1
- data/lib/client_side_validations/version.rb +1 -1
- data/vendor/assets/javascripts/rails.validations.js +285 -189
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d881931955d33c3687fb6b38218fe696d081d40c6d41f788d3201bc82a5b2da
|
|
4
|
+
data.tar.gz: b2ca222a27708e478bd129d30eb694d6ee9cdaf8ae6547f32dad4b0ab280d199
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18e724e1e357c233d91bc16106ef5b7326f8c0b54a04a986cf488c31e1ba72cd357fce89bb3a59178e237143e05c87255a7adc6dd33e02ae9e9b7aa44ddc76e0
|
|
7
|
+
data.tar.gz: 0b14c029231eff6401e95f37db1cb5fb0434379fb675121e704ddbb268bba77e237315d05e30e31858ea10ae7ddd6432145f96e4d09a8c43da1abaa5e145cd6d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 24.0.0 / 2026-04-19
|
|
4
|
+
* [FEATURE] Breaking change: Remove the jQuery runtime dependency and the old jQuery plugin aliases from the published JavaScript assets
|
|
5
|
+
* [FEATURE] Breaking change: Public JavaScript APIs now work with native DOM elements and DOM collections instead of jQuery-wrapped objects
|
|
6
|
+
* [ENHANCEMENT] Use native browser events and event listeners throughout the runtime and test harness
|
|
7
|
+
|
|
3
8
|
## 23.1.0 / 2026-01-27
|
|
4
9
|
|
|
5
10
|
* [FEATURE] Add jQuery 4.0.0 compatibility
|
data/README.md
CHANGED
|
@@ -56,12 +56,11 @@ config/initializers/client_side_validations.rb
|
|
|
56
56
|
|
|
57
57
|
Instructions depend on your technology stack.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
ClientSideValidations no longer depends on jQuery.
|
|
60
|
+
If you previously installed `jquery-rails`, `jquery_ujs`, or custom jQuery startup code only for ClientSideValidations, you can remove that integration when upgrading to 24.x.
|
|
60
61
|
|
|
61
62
|
#### When using Webpacker ####
|
|
62
63
|
|
|
63
|
-
Make sure that you are requiring jQuery.
|
|
64
|
-
|
|
65
64
|
Add the following package:
|
|
66
65
|
|
|
67
66
|
```sh
|
|
@@ -92,42 +91,77 @@ detect `window.Turbolinks` and attach its event handlers.
|
|
|
92
91
|
|
|
93
92
|
#### When using Sprockets ####
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
by default `jquery-rails` gem.
|
|
94
|
+
Add the following to your `app/assets/javascripts/application.js` file:
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
```js
|
|
97
|
+
//= require rails.validations
|
|
98
|
+
```
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
If you are using [Turbolinks](https://github.com/turbolinks/turbolinks),
|
|
101
|
+
make sure that `rails.validations` is required **after** `turbolinks`, so
|
|
102
|
+
ClientSideValidations can properly attach its event handlers.
|
|
103
|
+
|
|
104
|
+
If you need to copy the asset files from the gem into your project, run:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
rails g client_side_validations:copy_assets
|
|
103
108
|
```
|
|
104
109
|
|
|
105
|
-
|
|
110
|
+
Note: If you run `copy_assets`, you will need to run it again each time you update this project.
|
|
111
|
+
|
|
112
|
+
## Migration Guide ##
|
|
113
|
+
|
|
114
|
+
### 24.x Breaking Changes ###
|
|
115
|
+
|
|
116
|
+
If you are upgrading to 24.x, update your integration code to use the `ClientSideValidations` object directly.
|
|
117
|
+
|
|
118
|
+
The old jQuery plugin methods are removed. Use the DOM-first public API instead:
|
|
106
119
|
|
|
107
120
|
```js
|
|
108
|
-
|
|
121
|
+
ClientSideValidations.enable(form)
|
|
122
|
+
ClientSideValidations.validate(form)
|
|
123
|
+
ClientSideValidations.isValid(form, validators)
|
|
124
|
+
ClientSideValidations.disable(form)
|
|
125
|
+
ClientSideValidations.reset(form)
|
|
109
126
|
```
|
|
110
127
|
|
|
111
|
-
|
|
128
|
+
These methods accept native DOM elements and DOM collections. They do not accept jQuery objects or CSS selector strings.
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
Custom validators, form builders, and callbacks now receive native DOM nodes instead of jQuery wrappers. Update any custom code to use DOM APIs such as `.value`, `.form`, `.closest()`, and `querySelector()`.
|
|
131
|
+
Local validators are called as `(element, options)`. Form callbacks receive `(form, eventData)`, and element callbacks receive either `(element, message, callback)` or `(element, callback)` depending on the event.
|
|
115
132
|
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
All runtime-owned validation state attributes are now namespaced under `csv`. If you read or write these attributes in custom selectors, callbacks, or validators, update them to the scoped names:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
data-changed => data-csv-changed
|
|
137
|
+
data-valid => data-csv-valid
|
|
138
|
+
data-validate => data-csv-validate
|
|
139
|
+
data-not-locally-unique => data-csv-not-locally-unique
|
|
118
140
|
```
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
make sure that `rails.validations` is required **after** `turbolinks`, so
|
|
122
|
-
ClientSideValidations can properly attach its event handlers.
|
|
142
|
+
The matching dataset properties are `element.dataset.csvChanged`, `element.dataset.csvValid`, `element.dataset.csvValidate`, and `element.dataset.csvNotLocallyUnique`. `csvChanged` is stored as the string values `'true'` and `'false'`.
|
|
123
143
|
|
|
124
|
-
|
|
144
|
+
**jQuery namespaced events are removed.** Events are now plain native DOM custom events. If your application listens to or unbinds events using jQuery-style namespacing, you must update those calls.
|
|
145
|
+
|
|
146
|
+
Before:
|
|
125
147
|
|
|
148
|
+
```js
|
|
149
|
+
$(form).on('form:validate:before.ClientSideValidations', handler)
|
|
150
|
+
$(input).off('.ClientSideValidations')
|
|
126
151
|
```
|
|
127
|
-
|
|
152
|
+
|
|
153
|
+
After:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
form.addEventListener('form:validate:before', handler)
|
|
157
|
+
// store and pass the handler reference to removeEventListener when unbinding
|
|
128
158
|
```
|
|
129
159
|
|
|
130
|
-
|
|
160
|
+
The full list of native events dispatched by ClientSideValidations: `form:validate:before`, `form:validate:after`, `form:validate:pass`, `form:validate:fail`, `element:validate:before`, `element:validate:after`, `element:validate:pass`, `element:validate:fail`.
|
|
161
|
+
|
|
162
|
+
If you are upgrading from a version older than 23.0.0, the `data-csv-*` renaming above is required for any custom code that still reads or writes the old attribute names. If you are already on 23.x, the new 24.x upgrade step is to update any local uniqueness integrations that still reference `data-not-locally-unique` or `element.dataset.notLocallyUnique`.
|
|
163
|
+
|
|
164
|
+
If your application vendors the compiled asset with `rails g client_side_validations:copy_assets`, run that generator again after upgrading so your copied asset matches the current jQuery-free bundle.
|
|
131
165
|
|
|
132
166
|
## Initializer ##
|
|
133
167
|
|
|
@@ -324,11 +358,11 @@ If you need to change the markup of how the errors are rendered you can modify t
|
|
|
324
358
|
|
|
325
359
|
```js
|
|
326
360
|
window.ClientSideValidations.formBuilders['ActionView::Helpers::FormBuilder'] = {
|
|
327
|
-
add: function(
|
|
361
|
+
add: function(element, settings, message) {
|
|
328
362
|
// custom add code here
|
|
329
363
|
},
|
|
330
364
|
|
|
331
|
-
remove: function(
|
|
365
|
+
remove: function(element, settings) {
|
|
332
366
|
// custom remove code here
|
|
333
367
|
}
|
|
334
368
|
}
|
|
@@ -376,14 +410,14 @@ en:
|
|
|
376
410
|
Finally we need to add a client side validator. This can be done by hooking into the `ClientSideValidations.validator` object. Create a new file `app/assets/javascripts/rails.validations.customValidators.js`
|
|
377
411
|
|
|
378
412
|
```js
|
|
379
|
-
// The
|
|
380
|
-
// The
|
|
381
|
-
window.ClientSideValidations.validators.local
|
|
413
|
+
// The options variable is a JSON Object
|
|
414
|
+
// The element variable is a DOM element
|
|
415
|
+
window.ClientSideValidations.validators.local.email = function (element, options) {
|
|
382
416
|
// Your validator code goes in here
|
|
383
|
-
if (!/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i.test(
|
|
417
|
+
if (!/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i.test(element.value)) {
|
|
384
418
|
// When the value fails to pass validation you need to return the error message.
|
|
385
419
|
// It can be derived from validator.message
|
|
386
|
-
return options.message
|
|
420
|
+
return options.message
|
|
387
421
|
}
|
|
388
422
|
}
|
|
389
423
|
```
|
|
@@ -408,7 +442,7 @@ There are many reasons why you might want to enable, disable, or even completely
|
|
|
408
442
|
If you have rendered a new form via AJAX into your page you will need to enable that form for validation:
|
|
409
443
|
|
|
410
444
|
```js
|
|
411
|
-
|
|
445
|
+
ClientSideValidations.enable(newForm)
|
|
412
446
|
```
|
|
413
447
|
|
|
414
448
|
You should attach this to an event that is fired when the new HTML renders.
|
|
@@ -416,7 +450,7 @@ You should attach this to an event that is fired when the new HTML renders.
|
|
|
416
450
|
You can use the same function if you introduce new inputs to an existing form:
|
|
417
451
|
|
|
418
452
|
```js
|
|
419
|
-
|
|
453
|
+
ClientSideValidations.enable(newInput)
|
|
420
454
|
```
|
|
421
455
|
|
|
422
456
|
### Disabling ###
|
|
@@ -424,7 +458,7 @@ $(new_input).enableClientSideValidations();
|
|
|
424
458
|
If you wish to turn off validations entirely on a form:
|
|
425
459
|
|
|
426
460
|
```js
|
|
427
|
-
|
|
461
|
+
ClientSideValidations.disable(form)
|
|
428
462
|
```
|
|
429
463
|
|
|
430
464
|
### Resetting ###
|
|
@@ -432,44 +466,54 @@ $(form).disableClientSideValidations();
|
|
|
432
466
|
You can reset the current state of the validations, clear all error messages, and reattach clean event handlers:
|
|
433
467
|
|
|
434
468
|
```js
|
|
435
|
-
|
|
469
|
+
ClientSideValidations.reset(form)
|
|
436
470
|
```
|
|
437
471
|
|
|
438
472
|
## Callbacks ##
|
|
439
473
|
|
|
440
474
|
`ClientSideValidations` will run callbacks based upon the state of the element or form. The following callbacks are supported:
|
|
441
475
|
|
|
442
|
-
* `ClientSideValidations.callbacks.element.after(
|
|
443
|
-
* `ClientSideValidations.callbacks.element.before(
|
|
444
|
-
* `ClientSideValidations.callbacks.element.fail(
|
|
445
|
-
* `ClientSideValidations.callbacks.element.pass(
|
|
446
|
-
* `ClientSideValidations.callbacks.form.after(
|
|
447
|
-
* `ClientSideValidations.callbacks.form.before(
|
|
448
|
-
* `ClientSideValidations.callbacks.form.fail(
|
|
449
|
-
* `ClientSideValidations.callbacks.form.pass(
|
|
476
|
+
* `ClientSideValidations.callbacks.element.after(element, eventData)`
|
|
477
|
+
* `ClientSideValidations.callbacks.element.before(element, eventData)`
|
|
478
|
+
* `ClientSideValidations.callbacks.element.fail(element, message, callback, eventData)`
|
|
479
|
+
* `ClientSideValidations.callbacks.element.pass(element, callback, eventData)`
|
|
480
|
+
* `ClientSideValidations.callbacks.form.after(form, eventData)`
|
|
481
|
+
* `ClientSideValidations.callbacks.form.before(form, eventData)`
|
|
482
|
+
* `ClientSideValidations.callbacks.form.fail(form, eventData)`
|
|
483
|
+
* `ClientSideValidations.callbacks.form.pass(form, eventData)`
|
|
450
484
|
|
|
451
485
|
The names of the callbacks should be pretty straight forward. For example, `ClientSideValidations.callbacks.form.fail` will be called if a form failed to validate. And `ClientSideValidations.callbacks.element.before` will be called before that particular element's validations are run.
|
|
452
486
|
|
|
453
|
-
All element callbacks
|
|
487
|
+
All element callbacks receive the DOM element as the first parameter and the native event object as the second parameter. `ClientSideValidations.callbacks.element.fail()` receives the failed message as the second parameter, the callback for adding error fields as the third parameter, and the eventData object as the fourth parameter. `ClientSideValidations.callbacks.element.pass()` receives the callback for removing the error fields as the second parameter. The error field callbacks must still be invoked by your custom callback.
|
|
454
488
|
|
|
455
|
-
All form callbacks
|
|
489
|
+
All form callbacks receive the DOM form element as the first parameter and the native event object as the second parameter.
|
|
456
490
|
|
|
457
|
-
Here is an example callback
|
|
491
|
+
Here is an example callback that animates the error message when validation fails:
|
|
458
492
|
|
|
459
493
|
``` javascript
|
|
460
|
-
|
|
461
|
-
window.ClientSideValidations.callbacks.element.fail = function($element, message, callback) {
|
|
494
|
+
window.ClientSideValidations.callbacks.element.fail = function (element, message, callback) {
|
|
462
495
|
callback()
|
|
463
496
|
|
|
464
|
-
|
|
465
|
-
|
|
497
|
+
var messageElement = element.parentElement.querySelector('.message')
|
|
498
|
+
|
|
499
|
+
if (messageElement) {
|
|
500
|
+
if (typeof messageElement.animate === 'function') {
|
|
501
|
+
messageElement.animate(
|
|
502
|
+
[
|
|
503
|
+
{ opacity: 0, transform: 'translateX(-8px)' },
|
|
504
|
+
{ opacity: 1, transform: 'translateX(0)' }
|
|
505
|
+
],
|
|
506
|
+
{ duration: 250, easing: 'ease-out', fill: 'both' }
|
|
507
|
+
)
|
|
508
|
+
} else {
|
|
509
|
+
messageElement.style.opacity = '1'
|
|
510
|
+
messageElement.style.transform = 'translateX(0)'
|
|
511
|
+
}
|
|
466
512
|
}
|
|
467
513
|
}
|
|
468
514
|
|
|
469
|
-
window.ClientSideValidations.callbacks.element.pass = function(
|
|
470
|
-
|
|
471
|
-
// method so it is run after the animation is complete.
|
|
472
|
-
$element.parent().find('.message').hide('slide', { direction: 'left' }, 500, callback)
|
|
515
|
+
window.ClientSideValidations.callbacks.element.pass = function (element, callback) {
|
|
516
|
+
callback()
|
|
473
517
|
}
|
|
474
518
|
```
|
|
475
519
|
|
|
@@ -478,12 +522,9 @@ window.ClientSideValidations.callbacks.element.pass = function($element, callbac
|
|
|
478
522
|
background-color: red;
|
|
479
523
|
border-bottom-right-radius: 5px 5px;
|
|
480
524
|
border-top-right-radius: 5px 5px;
|
|
525
|
+
display: inline-block;
|
|
481
526
|
padding: 2px 5px;
|
|
482
527
|
}
|
|
483
|
-
|
|
484
|
-
div.field_with_errors div.ui-effects-wrapper {
|
|
485
|
-
display: inline-block !important;
|
|
486
|
-
}
|
|
487
528
|
```
|
|
488
529
|
|
|
489
530
|
Finally uncomment the `ActionView::Base.field_error_proc` override in `config/initializers/client_side_validations.rb`
|
|
@@ -506,22 +547,21 @@ By default, ClientSideValidations will automatically validate the form.
|
|
|
506
547
|
If for some reason you would like to manually validate the form (for example you're working with a multi-step form), you can use the following approach:
|
|
507
548
|
|
|
508
549
|
```js
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
validators =
|
|
550
|
+
const input = document.getElementById('myInputField')
|
|
551
|
+
const form = input.form
|
|
552
|
+
const validators = form.ClientSideValidations.settings.validators
|
|
512
553
|
|
|
513
554
|
// Validate a single field
|
|
514
|
-
|
|
515
|
-
$input.isValid(validators);
|
|
555
|
+
ClientSideValidations.isValid(input, validators)
|
|
516
556
|
|
|
517
557
|
// Validate the whole form
|
|
518
|
-
|
|
558
|
+
ClientSideValidations.isValid(form, validators)
|
|
519
559
|
```
|
|
520
560
|
|
|
521
561
|
To manually validate a single field, you may also trigger a focusout event:
|
|
522
562
|
|
|
523
563
|
```js
|
|
524
|
-
|
|
564
|
+
input.dispatchEvent(new Event('focusout', { bubbles: true }))
|
|
525
565
|
```
|
|
526
566
|
|
|
527
567
|
## Authors ##
|
|
@@ -86,7 +86,7 @@ module ClientSideValidations
|
|
|
86
86
|
@validators.each_with_object({}) do |object_opts, validator_hash|
|
|
87
87
|
next unless object_opts[0].respond_to?(:client_side_validation_hash)
|
|
88
88
|
|
|
89
|
-
option_hash = object_opts[1].each_with_object({}) do |attr, result|
|
|
89
|
+
option_hash = object_opts[1].each_with_object({}) do |attr, result| # rubocop:disable Style/ReduceToHash -- False positive
|
|
90
90
|
result[attr[0]] = attr[1][:options]
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Client Side Validations JS -
|
|
2
|
+
* Client Side Validations JS - v24.0.0 (https://github.com/DavyJonesLocker/client_side_validations)
|
|
3
3
|
* Copyright (c) 2026 Geremia Taglialatela, Brian Cardarella
|
|
4
4
|
* Licensed under MIT (https://opensource.org/licenses/mit-license.php)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
(function (global, factory) {
|
|
8
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(
|
|
9
|
-
typeof define === 'function' && define.amd ? define(
|
|
10
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ClientSideValidations = factory(
|
|
11
|
-
})(this, (function (
|
|
8
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
9
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
10
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ClientSideValidations = factory());
|
|
11
|
+
})(this, (function () { 'use strict';
|
|
12
|
+
|
|
13
|
+
const boundEventListeners = new WeakMap();
|
|
14
|
+
const addBoundEventListener = (element, eventName, listener) => {
|
|
15
|
+
element.addEventListener(eventName, listener);
|
|
16
|
+
const listeners = boundEventListeners.get(element) || [];
|
|
17
|
+
listeners.push({
|
|
18
|
+
eventName,
|
|
19
|
+
listener
|
|
20
|
+
});
|
|
21
|
+
boundEventListeners.set(element, listeners);
|
|
22
|
+
};
|
|
23
|
+
const bindElementEvents = (element, eventsToBind) => {
|
|
24
|
+
for (const eventName in eventsToBind) {
|
|
25
|
+
addBoundEventListener(element, eventName, eventsToBind[eventName]);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const clearBoundEventListeners = element => {
|
|
29
|
+
const listeners = boundEventListeners.get(element);
|
|
30
|
+
if (!listeners) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
listeners.forEach(_ref => {
|
|
34
|
+
let {
|
|
35
|
+
eventName,
|
|
36
|
+
listener
|
|
37
|
+
} = _ref;
|
|
38
|
+
element.removeEventListener(eventName, listener);
|
|
39
|
+
});
|
|
40
|
+
boundEventListeners.delete(element);
|
|
41
|
+
};
|
|
42
|
+
const dispatchCustomEvent = (element, eventName, detail) => {
|
|
43
|
+
element.dispatchEvent(new CustomEvent(eventName, {
|
|
44
|
+
bubbles: true,
|
|
45
|
+
detail
|
|
46
|
+
}));
|
|
47
|
+
};
|
|
12
48
|
|
|
13
49
|
const arrayHasValue = (value, otherValues) => {
|
|
14
50
|
for (let i = 0, l = otherValues.length; i < l; i++) {
|
|
@@ -23,136 +59,190 @@
|
|
|
23
59
|
element.innerHTML = html;
|
|
24
60
|
return element.firstChild;
|
|
25
61
|
};
|
|
62
|
+
const isDOMCollection = target => {
|
|
63
|
+
return Array.isArray(target) || typeof NodeList !== 'undefined' && target instanceof NodeList || typeof HTMLCollection !== 'undefined' && target instanceof HTMLCollection || typeof RadioNodeList !== 'undefined' && target instanceof RadioNodeList;
|
|
64
|
+
};
|
|
65
|
+
const isDOMElement = target => {
|
|
66
|
+
return target != null && target.nodeType === 1;
|
|
67
|
+
};
|
|
68
|
+
const getDOMElements = target => {
|
|
69
|
+
if (target == null) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
if (isDOMElement(target)) {
|
|
73
|
+
return [target];
|
|
74
|
+
}
|
|
75
|
+
if (isDOMCollection(target)) {
|
|
76
|
+
return Array.from(target).filter(isDOMElement);
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
};
|
|
80
|
+
const isFormElement = element => {
|
|
81
|
+
return element.tagName === 'FORM';
|
|
82
|
+
};
|
|
83
|
+
const isInputElement = element => {
|
|
84
|
+
switch (element.tagName) {
|
|
85
|
+
case 'INPUT':
|
|
86
|
+
return element.type !== 'submit' && element.type !== 'button';
|
|
87
|
+
case 'SELECT':
|
|
88
|
+
case 'TEXTAREA':
|
|
89
|
+
return true;
|
|
90
|
+
default:
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const isVisible = element => {
|
|
95
|
+
return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
|
|
96
|
+
};
|
|
26
97
|
const isValuePresent = value => {
|
|
27
98
|
return !/^\s*$/.test(value || '');
|
|
28
99
|
};
|
|
29
100
|
|
|
101
|
+
const isNamedInputElement = element => {
|
|
102
|
+
return isInputElement(element) && element.name != null && element.name !== '';
|
|
103
|
+
};
|
|
104
|
+
const getFormControls = form => {
|
|
105
|
+
return Array.from(form.elements).filter(isInputElement);
|
|
106
|
+
};
|
|
107
|
+
const getFormInputs = form => {
|
|
108
|
+
return getFormControls(form).filter(element => {
|
|
109
|
+
return isNamedInputElement(element) && !element.disabled && isVisible(element);
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const findFormElementByName = (form, name) => {
|
|
113
|
+
return getFormControls(form).find(element => element.name === name);
|
|
114
|
+
};
|
|
115
|
+
const enableForm = form => {
|
|
116
|
+
ClientSideValidations.enablers.form(form);
|
|
117
|
+
};
|
|
118
|
+
const enableForms = () => {
|
|
119
|
+
document.querySelectorAll(ClientSideValidations.selectors.forms).forEach(enableForm);
|
|
120
|
+
};
|
|
30
121
|
const ClientSideValidations = {
|
|
31
122
|
callbacks: {
|
|
32
123
|
element: {
|
|
33
|
-
after: (
|
|
34
|
-
before: (
|
|
35
|
-
fail: (
|
|
36
|
-
pass: (
|
|
124
|
+
after: (element, eventData) => {},
|
|
125
|
+
before: (element, eventData) => {},
|
|
126
|
+
fail: (element, message, addError, eventData) => addError(),
|
|
127
|
+
pass: (element, removeError, eventData) => removeError()
|
|
37
128
|
},
|
|
38
129
|
form: {
|
|
39
|
-
after: (
|
|
40
|
-
before: (
|
|
41
|
-
fail: (
|
|
42
|
-
pass: (
|
|
130
|
+
after: (form, eventData) => {},
|
|
131
|
+
before: (form, eventData) => {},
|
|
132
|
+
fail: (form, eventData) => {},
|
|
133
|
+
pass: (form, eventData) => {}
|
|
43
134
|
}
|
|
44
135
|
},
|
|
45
136
|
eventsToBind: {
|
|
46
|
-
form:
|
|
47
|
-
|
|
48
|
-
if (
|
|
137
|
+
form: form => ({
|
|
138
|
+
submit: eventData => {
|
|
139
|
+
if (!ClientSideValidations.isValid(form, form.ClientSideValidations.settings.validators)) {
|
|
49
140
|
eventData.preventDefault();
|
|
50
141
|
eventData.stopImmediatePropagation();
|
|
51
142
|
}
|
|
52
143
|
},
|
|
53
|
-
'ajax:beforeSend
|
|
144
|
+
'ajax:beforeSend': function (eventData) {
|
|
54
145
|
if (eventData.target === this) {
|
|
55
|
-
|
|
146
|
+
ClientSideValidations.isValid(form, form.ClientSideValidations.settings.validators);
|
|
56
147
|
}
|
|
57
148
|
},
|
|
58
|
-
'form:validate:after
|
|
59
|
-
ClientSideValidations.callbacks.form.after(
|
|
149
|
+
'form:validate:after': eventData => {
|
|
150
|
+
ClientSideValidations.callbacks.form.after(form, eventData);
|
|
60
151
|
},
|
|
61
|
-
'form:validate:before
|
|
62
|
-
ClientSideValidations.callbacks.form.before(
|
|
152
|
+
'form:validate:before': eventData => {
|
|
153
|
+
ClientSideValidations.callbacks.form.before(form, eventData);
|
|
63
154
|
},
|
|
64
|
-
'form:validate:fail
|
|
65
|
-
ClientSideValidations.callbacks.form.fail(
|
|
155
|
+
'form:validate:fail': eventData => {
|
|
156
|
+
ClientSideValidations.callbacks.form.fail(form, eventData);
|
|
66
157
|
},
|
|
67
|
-
'form:validate:pass
|
|
68
|
-
ClientSideValidations.callbacks.form.pass(
|
|
158
|
+
'form:validate:pass': eventData => {
|
|
159
|
+
ClientSideValidations.callbacks.form.pass(form, eventData);
|
|
69
160
|
}
|
|
70
161
|
}),
|
|
71
162
|
input: form => ({
|
|
72
|
-
|
|
73
|
-
|
|
163
|
+
focusout: function () {
|
|
164
|
+
ClientSideValidations.isValid(this, form.ClientSideValidations.settings.validators);
|
|
74
165
|
},
|
|
75
|
-
|
|
166
|
+
change: function () {
|
|
76
167
|
this.dataset.csvChanged = 'true';
|
|
77
168
|
},
|
|
78
|
-
'element:validate:after
|
|
79
|
-
ClientSideValidations.callbacks.element.after(
|
|
169
|
+
'element:validate:after': function (eventData) {
|
|
170
|
+
ClientSideValidations.callbacks.element.after(this, eventData);
|
|
80
171
|
},
|
|
81
|
-
'element:validate:before
|
|
82
|
-
ClientSideValidations.callbacks.element.before(
|
|
172
|
+
'element:validate:before': function (eventData) {
|
|
173
|
+
ClientSideValidations.callbacks.element.before(this, eventData);
|
|
83
174
|
},
|
|
84
|
-
'element:validate:fail
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
175
|
+
'element:validate:fail': function (eventData) {
|
|
176
|
+
const element = this;
|
|
177
|
+
const message = eventData.detail;
|
|
178
|
+
ClientSideValidations.callbacks.element.fail(element, message, function () {
|
|
179
|
+
form.ClientSideValidations.addError(element, message);
|
|
88
180
|
}, eventData);
|
|
89
181
|
},
|
|
90
|
-
'element:validate:pass
|
|
91
|
-
const
|
|
92
|
-
ClientSideValidations.callbacks.element.pass(
|
|
93
|
-
form.ClientSideValidations.removeError(
|
|
182
|
+
'element:validate:pass': function (eventData) {
|
|
183
|
+
const element = this;
|
|
184
|
+
ClientSideValidations.callbacks.element.pass(element, function () {
|
|
185
|
+
form.ClientSideValidations.removeError(element);
|
|
94
186
|
}, eventData);
|
|
95
187
|
}
|
|
96
188
|
}),
|
|
97
|
-
inputConfirmation: (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
inputConfirmation: (elementToConfirm, form) => ({
|
|
190
|
+
focusout: () => {
|
|
191
|
+
elementToConfirm.dataset.csvChanged = 'true';
|
|
192
|
+
ClientSideValidations.isValid(elementToConfirm, form.ClientSideValidations.settings.validators);
|
|
101
193
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
194
|
+
keyup: () => {
|
|
195
|
+
elementToConfirm.dataset.csvChanged = 'true';
|
|
196
|
+
ClientSideValidations.isValid(elementToConfirm, form.ClientSideValidations.settings.validators);
|
|
105
197
|
}
|
|
106
198
|
})
|
|
107
199
|
},
|
|
108
200
|
enablers: {
|
|
109
201
|
form: form => {
|
|
110
|
-
|
|
202
|
+
clearBoundEventListeners(form);
|
|
203
|
+
getFormControls(form).forEach(clearBoundEventListeners);
|
|
111
204
|
form.ClientSideValidations = {
|
|
112
205
|
settings: JSON.parse(form.dataset.clientSideValidations),
|
|
113
|
-
addError: (
|
|
114
|
-
removeError:
|
|
206
|
+
addError: (element, message) => ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].add(element, form.ClientSideValidations.settings.html_settings, message),
|
|
207
|
+
removeError: element => ClientSideValidations.formBuilders[form.ClientSideValidations.settings.html_settings.type].remove(element, form.ClientSideValidations.settings.html_settings)
|
|
115
208
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
$form.on(eventName, eventFunction);
|
|
120
|
-
}
|
|
121
|
-
$form.find(ClientSideValidations.selectors.inputs).each(function () {
|
|
122
|
-
ClientSideValidations.enablers.input(this);
|
|
209
|
+
bindElementEvents(form, ClientSideValidations.eventsToBind.form(form));
|
|
210
|
+
getFormInputs(form).forEach(element => {
|
|
211
|
+
ClientSideValidations.enablers.input(element);
|
|
123
212
|
});
|
|
124
213
|
},
|
|
125
214
|
input: function (input) {
|
|
126
|
-
const $input = jQuery(input);
|
|
127
215
|
const form = input.form;
|
|
128
|
-
|
|
216
|
+
if (!form) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
clearBoundEventListeners(input);
|
|
129
220
|
const eventsToBind = ClientSideValidations.eventsToBind.input(form);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.dataset.csvValidate = 'true';
|
|
134
|
-
}).on(eventName, eventFunction);
|
|
221
|
+
if (input.type !== 'radio' && !(input.id && input.id.endsWith('_confirmation'))) {
|
|
222
|
+
input.dataset.csvValidate = 'true';
|
|
223
|
+
bindElementEvents(input, eventsToBind);
|
|
135
224
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const $element = jQuery(this);
|
|
141
|
-
const $elementToConfirm = $form.find("#".concat(this.id.match(/(.+)_confirmation/)[1], ":input"));
|
|
142
|
-
if ($elementToConfirm.length) {
|
|
143
|
-
const eventsToBind = ClientSideValidations.eventsToBind.inputConfirmation($elementToConfirm, form);
|
|
144
|
-
for (const eventName in eventsToBind) {
|
|
145
|
-
const eventFunction = eventsToBind[eventName];
|
|
146
|
-
jQuery("#".concat($element.attr('id'))).on(eventName, eventFunction);
|
|
225
|
+
if (input.type === 'checkbox') {
|
|
226
|
+
bindElementEvents(input, {
|
|
227
|
+
change: function () {
|
|
228
|
+
ClientSideValidations.isValid(this, form.ClientSideValidations.settings.validators);
|
|
147
229
|
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (input.id && input.id.endsWith('_confirmation')) {
|
|
233
|
+
const elementToConfirm = document.getElementById(input.id.match(/(.+)_confirmation/)[1]);
|
|
234
|
+
if (elementToConfirm && elementToConfirm.form === form) {
|
|
235
|
+
bindElementEvents(input, ClientSideValidations.eventsToBind.inputConfirmation(elementToConfirm, form));
|
|
148
236
|
}
|
|
149
|
-
}
|
|
237
|
+
}
|
|
150
238
|
}
|
|
151
239
|
},
|
|
152
240
|
formBuilders: {
|
|
153
241
|
'ActionView::Helpers::FormBuilder': {
|
|
154
|
-
add: (
|
|
155
|
-
|
|
242
|
+
add: (element, settings, message) => {
|
|
243
|
+
if (!element) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
156
246
|
const form = element.form;
|
|
157
247
|
const inputErrorTemplate = createElementFromHTML(settings.input_tag);
|
|
158
248
|
let inputErrorElement = element.closest(".".concat(inputErrorTemplate.getAttribute('class').replace(/ /g, '.')));
|
|
@@ -182,8 +272,10 @@
|
|
|
182
272
|
labelMessageElement.textContent = message;
|
|
183
273
|
}
|
|
184
274
|
},
|
|
185
|
-
remove: (
|
|
186
|
-
|
|
275
|
+
remove: (element, settings) => {
|
|
276
|
+
if (!element) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
187
279
|
const form = element.form;
|
|
188
280
|
const inputErrorClass = createElementFromHTML(settings.input_tag).getAttribute('class');
|
|
189
281
|
const inputErrorElement = element.closest(".".concat(inputErrorClass.replace(/ /g, '.')));
|
|
@@ -213,8 +305,8 @@
|
|
|
213
305
|
}
|
|
214
306
|
},
|
|
215
307
|
selectors: {
|
|
216
|
-
inputs: '
|
|
217
|
-
validate_inputs: '
|
|
308
|
+
inputs: 'input:not([type="submit"]):not([type="button"])[name], select[name], textarea[name]',
|
|
309
|
+
validate_inputs: 'input[data-csv-validate]:not([type="submit"]):not([type="button"]), select[data-csv-validate], textarea[data-csv-validate]',
|
|
218
310
|
forms: 'form[data-client-side-validations]'
|
|
219
311
|
},
|
|
220
312
|
validators: {
|
|
@@ -228,23 +320,31 @@
|
|
|
228
320
|
remote: {}
|
|
229
321
|
},
|
|
230
322
|
disable: target => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
323
|
+
getDOMElements(target).forEach(element => {
|
|
324
|
+
clearBoundEventListeners(element);
|
|
325
|
+
if (isFormElement(element)) {
|
|
326
|
+
getFormControls(element).forEach(input => {
|
|
327
|
+
clearBoundEventListeners(input);
|
|
328
|
+
delete input.dataset.csvValid;
|
|
329
|
+
delete input.dataset.csvChanged;
|
|
330
|
+
delete input.dataset.csvValidate;
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
delete element.dataset.csvValid;
|
|
335
|
+
delete element.dataset.csvChanged;
|
|
336
|
+
if (isInputElement(element)) {
|
|
337
|
+
delete element.dataset.csvValidate;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
242
340
|
},
|
|
243
341
|
reset: form => {
|
|
244
|
-
const $form = jQuery(form);
|
|
245
342
|
ClientSideValidations.disable(form);
|
|
246
343
|
for (const key in form.ClientSideValidations.settings.validators) {
|
|
247
|
-
|
|
344
|
+
const element = findFormElementByName(form, key);
|
|
345
|
+
if (element) {
|
|
346
|
+
form.ClientSideValidations.removeError(element);
|
|
347
|
+
}
|
|
248
348
|
}
|
|
249
349
|
ClientSideValidations.enablers.form(form);
|
|
250
350
|
},
|
|
@@ -258,21 +358,23 @@
|
|
|
258
358
|
start: () => {
|
|
259
359
|
const initializeOnEvent = ClientSideValidations.initializeOnEvent();
|
|
260
360
|
if (initializeOnEvent != null) {
|
|
261
|
-
|
|
361
|
+
document.addEventListener(initializeOnEvent, enableForms);
|
|
362
|
+
} else if (document.readyState === 'loading') {
|
|
363
|
+
document.addEventListener('DOMContentLoaded', enableForms, {
|
|
364
|
+
once: true
|
|
365
|
+
});
|
|
262
366
|
} else {
|
|
263
|
-
|
|
367
|
+
enableForms();
|
|
264
368
|
}
|
|
265
369
|
}
|
|
266
370
|
};
|
|
267
371
|
|
|
268
|
-
const absenceLocalValidator = (
|
|
269
|
-
const element = $element[0];
|
|
372
|
+
const absenceLocalValidator = (element, options) => {
|
|
270
373
|
if (isValuePresent(element.value)) {
|
|
271
374
|
return options.message;
|
|
272
375
|
}
|
|
273
376
|
};
|
|
274
|
-
const presenceLocalValidator = (
|
|
275
|
-
const element = $element[0];
|
|
377
|
+
const presenceLocalValidator = (element, options) => {
|
|
276
378
|
if (!isValuePresent(element.value)) {
|
|
277
379
|
return options.message;
|
|
278
380
|
}
|
|
@@ -288,8 +390,7 @@
|
|
|
288
390
|
}
|
|
289
391
|
return value === acceptOption;
|
|
290
392
|
};
|
|
291
|
-
const acceptanceLocalValidator = (
|
|
292
|
-
const element = $element[0];
|
|
393
|
+
const acceptanceLocalValidator = (element, options) => {
|
|
293
394
|
let valid = true;
|
|
294
395
|
if (element.type === 'checkbox') {
|
|
295
396
|
valid = element.checked;
|
|
@@ -308,8 +409,7 @@
|
|
|
308
409
|
const hasValidFormat = (value, withOptions, withoutOptions) => {
|
|
309
410
|
return withOptions && isMatching(value, withOptions) || withoutOptions && !isMatching(value, withoutOptions);
|
|
310
411
|
};
|
|
311
|
-
const formatLocalValidator = (
|
|
312
|
-
const element = $element[0];
|
|
412
|
+
const formatLocalValidator = (element, options) => {
|
|
313
413
|
const value = element.value;
|
|
314
414
|
if (options.allow_blank && !isValuePresent(value)) {
|
|
315
415
|
return;
|
|
@@ -394,8 +494,7 @@
|
|
|
394
494
|
}
|
|
395
495
|
return runFunctionValidations(formattedValue, form, options);
|
|
396
496
|
};
|
|
397
|
-
const numericalityLocalValidator = (
|
|
398
|
-
const element = $element[0];
|
|
497
|
+
const numericalityLocalValidator = (element, options) => {
|
|
399
498
|
const value = element.value;
|
|
400
499
|
if (options.allow_blank && !isValuePresent(value)) {
|
|
401
500
|
return;
|
|
@@ -425,8 +524,7 @@
|
|
|
425
524
|
}
|
|
426
525
|
}
|
|
427
526
|
};
|
|
428
|
-
const lengthLocalValidator = (
|
|
429
|
-
const element = $element[0];
|
|
527
|
+
const lengthLocalValidator = (element, options) => {
|
|
430
528
|
const value = element.value;
|
|
431
529
|
if (options.allow_blank && !isValuePresent(value)) {
|
|
432
530
|
return;
|
|
@@ -450,23 +548,20 @@
|
|
|
450
548
|
}
|
|
451
549
|
return options.in && isInList(value, options.in) || options.range && isInRange(value, options.range);
|
|
452
550
|
};
|
|
453
|
-
const exclusionLocalValidator = (
|
|
454
|
-
const element = $element[0];
|
|
551
|
+
const exclusionLocalValidator = (element, options) => {
|
|
455
552
|
const value = element.value;
|
|
456
553
|
if (isIncluded(value, options, false) || !options.allow_blank && !isValuePresent(value)) {
|
|
457
554
|
return options.message;
|
|
458
555
|
}
|
|
459
556
|
};
|
|
460
|
-
const inclusionLocalValidator = (
|
|
461
|
-
const element = $element[0];
|
|
557
|
+
const inclusionLocalValidator = (element, options) => {
|
|
462
558
|
const value = element.value;
|
|
463
559
|
if (!isIncluded(value, options, true)) {
|
|
464
560
|
return options.message;
|
|
465
561
|
}
|
|
466
562
|
};
|
|
467
563
|
|
|
468
|
-
const confirmationLocalValidator = (
|
|
469
|
-
const element = $element[0];
|
|
564
|
+
const confirmationLocalValidator = (element, options) => {
|
|
470
565
|
let value = element.value;
|
|
471
566
|
let confirmationValue = document.getElementById("".concat(element.id, "_confirmation")).value;
|
|
472
567
|
if (!options.case_sensitive) {
|
|
@@ -484,17 +579,16 @@
|
|
|
484
579
|
otherValue = otherValue.toLowerCase();
|
|
485
580
|
}
|
|
486
581
|
if (otherValue === value) {
|
|
487
|
-
element.dataset.
|
|
582
|
+
element.dataset.csvNotLocallyUnique = 'true';
|
|
488
583
|
return false;
|
|
489
584
|
}
|
|
490
|
-
if (element.dataset.
|
|
491
|
-
delete element.dataset.
|
|
492
|
-
element.dataset.
|
|
585
|
+
if (element.dataset.csvNotLocallyUnique) {
|
|
586
|
+
delete element.dataset.csvNotLocallyUnique;
|
|
587
|
+
element.dataset.csvChanged = 'true';
|
|
493
588
|
}
|
|
494
589
|
return true;
|
|
495
590
|
};
|
|
496
|
-
const uniquenessLocalValidator = (
|
|
497
|
-
const element = $element[0];
|
|
591
|
+
const uniquenessLocalValidator = (element, options) => {
|
|
498
592
|
const elementName = element.name;
|
|
499
593
|
const matches = elementName.match(/^(.+_attributes\])\[\d+\](.+)$/);
|
|
500
594
|
if (!matches) {
|
|
@@ -529,42 +623,38 @@
|
|
|
529
623
|
confirmation: confirmationLocalValidator,
|
|
530
624
|
uniqueness: uniquenessLocalValidator
|
|
531
625
|
};
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
inputs: 'input'
|
|
540
|
-
};
|
|
541
|
-
for (const selector in selectors) {
|
|
542
|
-
const enablers = selectors[selector];
|
|
543
|
-
this.filter(ClientSideValidations.selectors[selector]).each(function () {
|
|
544
|
-
ClientSideValidations.enablers[enablers](this);
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
return this;
|
|
548
|
-
};
|
|
549
|
-
jQuery.fn.resetClientSideValidations = function () {
|
|
550
|
-
this.filter(ClientSideValidations.selectors.forms).each(function () {
|
|
551
|
-
ClientSideValidations.reset(this);
|
|
626
|
+
ClientSideValidations.enable = target => {
|
|
627
|
+
getDOMElements(target).forEach(element => {
|
|
628
|
+
if (isFormElement(element)) {
|
|
629
|
+
ClientSideValidations.enablers.form(element);
|
|
630
|
+
} else if (isInputElement(element)) {
|
|
631
|
+
ClientSideValidations.enablers.input(element);
|
|
632
|
+
}
|
|
552
633
|
});
|
|
553
|
-
return
|
|
634
|
+
return target;
|
|
554
635
|
};
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
636
|
+
ClientSideValidations.validate = target => {
|
|
637
|
+
getDOMElements(target).forEach(element => {
|
|
638
|
+
if (isFormElement(element)) {
|
|
639
|
+
ClientSideValidations.enable(element);
|
|
640
|
+
}
|
|
558
641
|
});
|
|
559
|
-
return
|
|
642
|
+
return target;
|
|
560
643
|
};
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
|
|
644
|
+
ClientSideValidations.isValid = (target, validators) => {
|
|
645
|
+
const element = getDOMElements(target)[0];
|
|
646
|
+
if (!element) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
if (!validators) {
|
|
650
|
+
var _form$ClientSideValid;
|
|
651
|
+
const form = isFormElement(element) ? element : element.form;
|
|
652
|
+
validators = form === null || form === void 0 || (_form$ClientSideValid = form.ClientSideValidations) === null || _form$ClientSideValid === void 0 || (_form$ClientSideValid = _form$ClientSideValid.settings) === null || _form$ClientSideValid === void 0 ? void 0 : _form$ClientSideValid.validators;
|
|
653
|
+
}
|
|
654
|
+
if (isFormElement(element)) {
|
|
655
|
+
return validateForm(element, validators || {});
|
|
567
656
|
}
|
|
657
|
+
return validateElement(element, validatorsFor(element.name, validators || {}));
|
|
568
658
|
};
|
|
569
659
|
const cleanNestedElementName = (elementName, nestedMatches, validators) => {
|
|
570
660
|
for (const validatorName in validators) {
|
|
@@ -583,97 +673,103 @@
|
|
|
583
673
|
return elementName;
|
|
584
674
|
};
|
|
585
675
|
const validatorsFor = (elementName, validators) => {
|
|
676
|
+
if (!elementName || !validators) {
|
|
677
|
+
return {};
|
|
678
|
+
}
|
|
586
679
|
if (Object.prototype.hasOwnProperty.call(validators, elementName)) {
|
|
587
680
|
return validators[elementName];
|
|
588
681
|
}
|
|
589
682
|
return validators[cleanElementName(elementName, validators)] || {};
|
|
590
683
|
};
|
|
591
|
-
const
|
|
684
|
+
const getValidationInputs = form => {
|
|
685
|
+
return Array.from(form.elements).filter(element => {
|
|
686
|
+
if (element.dataset.csvValidate == null || element.disabled) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
return isVisible(element);
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
const validateForm = (form, validators) => {
|
|
592
693
|
let valid = true;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (!
|
|
694
|
+
dispatchCustomEvent(form, 'form:validate:before');
|
|
695
|
+
getValidationInputs(form).forEach(element => {
|
|
696
|
+
if (!validateElement(element, validatorsFor(element.name, validators))) {
|
|
596
697
|
valid = false;
|
|
597
698
|
}
|
|
598
|
-
return true;
|
|
599
699
|
});
|
|
600
700
|
if (valid) {
|
|
601
|
-
|
|
701
|
+
dispatchCustomEvent(form, 'form:validate:pass');
|
|
602
702
|
} else {
|
|
603
|
-
|
|
703
|
+
dispatchCustomEvent(form, 'form:validate:fail');
|
|
604
704
|
}
|
|
605
|
-
|
|
705
|
+
dispatchCustomEvent(form, 'form:validate:after');
|
|
606
706
|
return valid;
|
|
607
707
|
};
|
|
608
|
-
const passElement =
|
|
609
|
-
|
|
610
|
-
$element.trigger('element:validate:pass.ClientSideValidations');
|
|
708
|
+
const passElement = element => {
|
|
709
|
+
dispatchCustomEvent(element, 'element:validate:pass');
|
|
611
710
|
delete element.dataset.csvValid;
|
|
612
711
|
};
|
|
613
|
-
const failElement = (
|
|
614
|
-
|
|
615
|
-
$element.trigger('element:validate:fail.ClientSideValidations', message);
|
|
712
|
+
const failElement = (element, message) => {
|
|
713
|
+
dispatchCustomEvent(element, 'element:validate:fail', message);
|
|
616
714
|
element.dataset.csvValid = 'false';
|
|
617
715
|
};
|
|
618
|
-
const afterValidate =
|
|
619
|
-
|
|
620
|
-
$element.trigger('element:validate:after.ClientSideValidations');
|
|
716
|
+
const afterValidate = element => {
|
|
717
|
+
dispatchCustomEvent(element, 'element:validate:after');
|
|
621
718
|
return element.dataset.csvValid !== 'false';
|
|
622
719
|
};
|
|
623
|
-
const executeValidator = (validatorFunctions, validatorFunction, validatorOptions,
|
|
720
|
+
const executeValidator = (validatorFunctions, validatorFunction, validatorOptions, element) => {
|
|
624
721
|
for (const validatorOption in validatorOptions) {
|
|
625
722
|
if (!validatorOptions[validatorOption]) {
|
|
626
723
|
continue;
|
|
627
724
|
}
|
|
628
|
-
const message = validatorFunction.call(validatorFunctions,
|
|
725
|
+
const message = validatorFunction.call(validatorFunctions, element, validatorOptions[validatorOption]);
|
|
629
726
|
if (message) {
|
|
630
|
-
failElement(
|
|
727
|
+
failElement(element, message);
|
|
631
728
|
return false;
|
|
632
729
|
}
|
|
633
730
|
}
|
|
634
731
|
return true;
|
|
635
732
|
};
|
|
636
|
-
const executeValidators = (validatorFunctions,
|
|
733
|
+
const executeValidators = (validatorFunctions, element, validators) => {
|
|
637
734
|
for (const validator in validators) {
|
|
638
735
|
if (!validatorFunctions[validator]) {
|
|
639
736
|
continue;
|
|
640
737
|
}
|
|
641
|
-
if (!executeValidator(validatorFunctions, validatorFunctions[validator], validators[validator],
|
|
738
|
+
if (!executeValidator(validatorFunctions, validatorFunctions[validator], validators[validator], element)) {
|
|
642
739
|
return false;
|
|
643
740
|
}
|
|
644
741
|
}
|
|
645
742
|
return true;
|
|
646
743
|
};
|
|
647
|
-
const isMarkedForDestroy =
|
|
648
|
-
const element = $element[0];
|
|
744
|
+
const isMarkedForDestroy = element => {
|
|
649
745
|
const elementName = element.name;
|
|
650
|
-
|
|
746
|
+
const form = element.form;
|
|
747
|
+
if (form && /\[([^\]]*?)\]$/.test(elementName)) {
|
|
651
748
|
const destroyInputName = elementName.replace(/\[([^\]]*?)\]$/, '[_destroy]');
|
|
652
|
-
const destroyInputElement =
|
|
749
|
+
const destroyInputElement = form.querySelector("input[name=\"".concat(destroyInputName, "\"]"));
|
|
653
750
|
if (destroyInputElement && destroyInputElement.value === '1') {
|
|
654
751
|
return true;
|
|
655
752
|
}
|
|
656
753
|
}
|
|
657
754
|
return false;
|
|
658
755
|
};
|
|
659
|
-
const executeAllValidators = (
|
|
660
|
-
const element = $element[0];
|
|
756
|
+
const executeAllValidators = (element, validators) => {
|
|
661
757
|
if (element.dataset.csvChanged === 'false' || element.disabled) {
|
|
662
758
|
return;
|
|
663
759
|
}
|
|
664
760
|
element.dataset.csvChanged = 'false';
|
|
665
|
-
if (executeValidators(ClientSideValidations.validators.all(),
|
|
666
|
-
passElement(
|
|
761
|
+
if (executeValidators(ClientSideValidations.validators.all(), element, validators)) {
|
|
762
|
+
passElement(element);
|
|
667
763
|
}
|
|
668
764
|
};
|
|
669
|
-
const validateElement = (
|
|
670
|
-
|
|
671
|
-
if (isMarkedForDestroy(
|
|
672
|
-
passElement(
|
|
765
|
+
const validateElement = (element, validators) => {
|
|
766
|
+
dispatchCustomEvent(element, 'element:validate:before');
|
|
767
|
+
if (isMarkedForDestroy(element)) {
|
|
768
|
+
passElement(element);
|
|
673
769
|
} else {
|
|
674
|
-
executeAllValidators(
|
|
770
|
+
executeAllValidators(element, validators);
|
|
675
771
|
}
|
|
676
|
-
return afterValidate(
|
|
772
|
+
return afterValidate(element);
|
|
677
773
|
};
|
|
678
774
|
if (!window.ClientSideValidations) {
|
|
679
775
|
window.ClientSideValidations = ClientSideValidations;
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: client_side_validations
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 24.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geremia Taglialatela
|
|
@@ -102,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
103
|
version: '0'
|
|
104
104
|
requirements: []
|
|
105
|
-
rubygems_version: 4.0.
|
|
105
|
+
rubygems_version: 4.0.9
|
|
106
106
|
specification_version: 4
|
|
107
107
|
summary: Client Side Validations
|
|
108
108
|
test_files: []
|