action_validator 0.1.0 → 0.1.1
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/app/channels/action_validator/form_channel.rb +11 -12
- data/app/helpers/action_validator/form_helper.rb +5 -4
- data/app/javascript/action_validator/controllers/form_controller.js +59 -41
- data/config/importmap.rb +0 -1
- data/lib/action_validator/engine.rb +0 -1
- data/lib/action_validator/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4aaba049d97346476208fb2f0f732a068c6ab921e369424507f45360fffe45e6
|
4
|
+
data.tar.gz: 370ca30364a45c315341897690e5e7abb5789f49e4b4325d3fe2db717c024a8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d875711628f3f58dc72fd06d4269a8a639a86022e40035dd799b75f4f548c7c0c5311fc83afc415bac190d0c895f3cbe71b52a7589362bc268004c253640c0d8
|
7
|
+
data.tar.gz: fc7e2dffa723cfd2b685cb7898b1d2b2cd2eb155dc8140264764cc9057ea43dc00b54fc89d2831aaf00a62d8aad582545861795852703baaab323c284e6e3e8e
|
@@ -14,16 +14,19 @@ module ActionValidator
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def validate(data)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
Rails.logger.tagged("ActionValidator") do
|
18
|
+
model_class, params = parse_params(data["formData"])
|
19
|
+
|
20
|
+
if model_class.nil?
|
21
|
+
Rails.logger.warn("No valid model class found, cannot perform remote validation.")
|
22
|
+
ActionCable.server.broadcast("action_validator_form_channel", { errors: {} })
|
23
|
+
return
|
24
|
+
end
|
23
25
|
|
24
|
-
|
26
|
+
instance = validated_instance(model_class, params)
|
25
27
|
|
26
|
-
|
28
|
+
ActionCable.server.broadcast("action_validator_form_channel", { errors: parse_model_errors(instance) })
|
29
|
+
end
|
27
30
|
end
|
28
31
|
|
29
32
|
private
|
@@ -35,10 +38,6 @@ module ActionValidator
|
|
35
38
|
end
|
36
39
|
|
37
40
|
def parse_model_errors(instance)
|
38
|
-
# TODO: What about errors on :base? Or any other random shit people might add?
|
39
|
-
# I think we can just tell people to add a div with the proper attribute?
|
40
|
-
# Or just don't allow it?
|
41
|
-
#
|
42
41
|
instance.errors.each_with_object({}) { |error, hash| hash[error.attribute] = error.full_message }
|
43
42
|
end
|
44
43
|
|
@@ -7,7 +7,8 @@ module ActionValidator
|
|
7
7
|
module FormHelper
|
8
8
|
# A wrapper around the Rails form_for helper. This method can be used exactly the same as the standard Rails helper.
|
9
9
|
#
|
10
|
-
def validator_form_for(record, options = {}, &block)
|
10
|
+
def validator_form_for(record, options = {}, &block)
|
11
|
+
merge_form_options(options)
|
11
12
|
|
12
13
|
form_for(record, options, &block)
|
13
14
|
end
|
@@ -15,7 +16,7 @@ module ActionValidator
|
|
15
16
|
# A wrapper around the Rails form_with helper. This method can be used exactly the same as the standard Rails
|
16
17
|
# helper.
|
17
18
|
#
|
18
|
-
def validator_form_with(model:
|
19
|
+
def validator_form_with(model: false, scope: nil, url: nil, format: nil, **options, &block)
|
19
20
|
merge_form_options(options)
|
20
21
|
|
21
22
|
form_with(model: model, scope: scope, url: url, format: format, **options, &block)
|
@@ -28,13 +29,13 @@ module ActionValidator
|
|
28
29
|
options[:data][ActionValidator::STIMULUS_SELECTORS[:target]] = :error
|
29
30
|
options[:data][:attribute] = attribute
|
30
31
|
|
31
|
-
tag.div(
|
32
|
+
tag.div(id: attribute, **options)
|
32
33
|
end
|
33
34
|
|
34
35
|
private
|
35
36
|
|
36
37
|
def merge_form_options(options)
|
37
|
-
default_builder =
|
38
|
+
default_builder = self.class.default_form_builder
|
38
39
|
options[:builder] ||= default_builder || ActionValidator::FormBuilder
|
39
40
|
options[:data] ||= {}
|
40
41
|
options[:data].merge!(
|
@@ -1,26 +1,24 @@
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
2
|
import consumer from "channels/consumer";
|
3
3
|
|
4
4
|
export default class extends Controller {
|
5
|
-
static targets = [
|
5
|
+
static targets = ["submit", "input", "error"];
|
6
6
|
static values = { modelName: String };
|
7
7
|
|
8
8
|
connect() {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
this.inputTargets.forEach(input => {
|
23
|
-
if (input.dataset.remoteValidate === 'true') this.remoteValidatableInputs.push(input);
|
9
|
+
this.remoteValidatableInputs = [];
|
10
|
+
this.channel = consumer.subscriptions.create(
|
11
|
+
"ActionValidator::FormChannel",
|
12
|
+
{
|
13
|
+
connected: this.#cableConnected.bind(this),
|
14
|
+
disconnected: this.#cableDisconnected.bind(this),
|
15
|
+
received: this.#cableReceived.bind(this),
|
16
|
+
},
|
17
|
+
);
|
18
|
+
|
19
|
+
this.inputTargets.forEach((input) => {
|
20
|
+
if (input.dataset.remoteValidate === "true")
|
21
|
+
this.remoteValidatableInputs.push(input);
|
24
22
|
});
|
25
23
|
|
26
24
|
this.#disableSubmit();
|
@@ -36,31 +34,35 @@ export default class extends Controller {
|
|
36
34
|
|
37
35
|
validate(ev) {
|
38
36
|
const inputElement = ev.target;
|
39
|
-
const {
|
37
|
+
const {
|
38
|
+
dataset: { remoteValidate },
|
39
|
+
} = inputElement;
|
40
40
|
this.#checkAndSetDirty(inputElement);
|
41
|
-
if (inputElement.dataset.isDirty ===
|
41
|
+
if (inputElement.dataset.isDirty === "false") return;
|
42
42
|
|
43
43
|
const htmlValid = this.validateInput(ev.target);
|
44
44
|
|
45
|
-
if (remoteValidate ===
|
46
|
-
|
45
|
+
if (remoteValidate === "true" && htmlValid) {
|
46
|
+
this.channel.perform("validate", { formData: this.#serializeForm() });
|
47
47
|
} else {
|
48
48
|
this.validateForm();
|
49
49
|
}
|
50
50
|
}
|
51
51
|
|
52
52
|
validateInput(input) {
|
53
|
-
input.setCustomValidity(
|
53
|
+
input.setCustomValidity("");
|
54
54
|
const isValid = input.checkValidity();
|
55
|
-
const errorElement = this.errorTargets.find(
|
55
|
+
const errorElement = this.errorTargets.find(
|
56
|
+
(target) => input.dataset.attribute === target.dataset.attribute,
|
57
|
+
);
|
56
58
|
|
57
59
|
if (isValid) {
|
58
|
-
errorElement.innerHTML =
|
60
|
+
if (errorElement) errorElement.innerHTML = "";
|
59
61
|
input.dataset.valid = true;
|
60
62
|
|
61
63
|
return true;
|
62
64
|
} else {
|
63
|
-
errorElement.innerHTML = input.validationMessage;
|
65
|
+
if (errorElement) errorElement.innerHTML = input.validationMessage;
|
64
66
|
input.dataset.valid = false;
|
65
67
|
|
66
68
|
return false;
|
@@ -77,18 +79,20 @@ export default class extends Controller {
|
|
77
79
|
const currentlyDirty = element.dataset.isDirty;
|
78
80
|
const value = element.value;
|
79
81
|
|
80
|
-
if (currentlyDirty ===
|
82
|
+
if (currentlyDirty === "false" && value) element.dataset.isDirty = true;
|
81
83
|
}
|
82
84
|
|
83
85
|
#serializeForm() {
|
84
86
|
const formData = new FormData(this.element);
|
85
|
-
const queryString = new URLSearchParams(formData).toString()
|
87
|
+
const queryString = new URLSearchParams(formData).toString();
|
86
88
|
|
87
89
|
return queryString;
|
88
90
|
}
|
89
91
|
|
90
92
|
#formValid() {
|
91
|
-
return this.inputTargets.every(
|
93
|
+
return this.inputTargets.every(
|
94
|
+
(element) => element.dataset.valid === "true",
|
95
|
+
);
|
92
96
|
}
|
93
97
|
|
94
98
|
// TODO: Add an option to just show errors globally somewhere and not next to each input. We should support arbitrary
|
@@ -97,16 +101,18 @@ export default class extends Controller {
|
|
97
101
|
#cableReceived(data) {
|
98
102
|
const { errors } = data;
|
99
103
|
|
100
|
-
this.remoteValidatableInputs.forEach(inputElement => {
|
101
|
-
if (inputElement.dataset.isDirty ===
|
104
|
+
this.remoteValidatableInputs.forEach((inputElement) => {
|
105
|
+
if (inputElement.dataset.isDirty === "false") return;
|
102
106
|
|
103
107
|
const attributeName = inputElement.dataset.attribute;
|
104
108
|
const error = errors[attributeName];
|
105
|
-
const errorElement = this.errorTargets.find(
|
109
|
+
const errorElement = this.errorTargets.find(
|
110
|
+
(target) => attributeName === target.dataset.attribute,
|
111
|
+
);
|
106
112
|
if (!errorElement) {
|
107
113
|
Stimulus.logDebugActivity(
|
108
114
|
`No error element found for input with attribute '${attributeName}'. Cannot add error.`,
|
109
|
-
|
115
|
+
"cableReceived",
|
110
116
|
);
|
111
117
|
return;
|
112
118
|
}
|
@@ -116,36 +122,48 @@ export default class extends Controller {
|
|
116
122
|
inputElement.setCustomValidity(errors[attributeName]);
|
117
123
|
inputElement.dataset.valid = false;
|
118
124
|
} else {
|
119
|
-
errorElement.innerHTML =
|
120
|
-
inputElement.setCustomValidity(
|
125
|
+
errorElement.innerHTML = "";
|
126
|
+
inputElement.setCustomValidity("");
|
121
127
|
inputElement.dataset.valid = true;
|
122
128
|
}
|
123
|
-
})
|
129
|
+
});
|
124
130
|
|
125
131
|
this.validateForm();
|
126
132
|
}
|
127
133
|
|
128
134
|
#disableSubmit() {
|
129
135
|
if (this.hasSubmitTarget) {
|
130
|
-
this.submitTarget.setAttribute(
|
136
|
+
this.submitTarget.setAttribute("disabled", true);
|
131
137
|
} else {
|
132
|
-
Stimulus.logDebugActivity(
|
138
|
+
Stimulus.logDebugActivity(
|
139
|
+
"No submit target found for form",
|
140
|
+
"disableSubmit",
|
141
|
+
);
|
133
142
|
}
|
134
143
|
}
|
135
144
|
|
136
145
|
#enableSubmit() {
|
137
146
|
if (this.hasSubmitTarget) {
|
138
|
-
this.submitTarget.removeAttribute(
|
147
|
+
this.submitTarget.removeAttribute("disabled");
|
139
148
|
} else {
|
140
|
-
Stimulus.logDebugActivity(
|
149
|
+
Stimulus.logDebugActivity(
|
150
|
+
"No submit target found for form",
|
151
|
+
"enableSubmit",
|
152
|
+
);
|
141
153
|
}
|
142
154
|
}
|
143
155
|
|
144
156
|
#cableConnected() {
|
145
|
-
Stimulus.logDebugActivity(
|
157
|
+
Stimulus.logDebugActivity(
|
158
|
+
"ActionValidator connected to cable",
|
159
|
+
"cableConnected",
|
160
|
+
);
|
146
161
|
}
|
147
162
|
|
148
163
|
#cableDisconnected() {
|
149
|
-
Stimulus.logDebugActivity(
|
164
|
+
Stimulus.logDebugActivity(
|
165
|
+
"ActionValidator disconnected from cable",
|
166
|
+
"cableDisconnected",
|
167
|
+
);
|
150
168
|
}
|
151
169
|
}
|
data/config/importmap.rb
CHANGED
@@ -14,7 +14,6 @@ module ActionValidator
|
|
14
14
|
|
15
15
|
initializer "action_validator.assets" do |app|
|
16
16
|
app.config.assets.paths << root.join("app/javascript")
|
17
|
-
app.config.assets.precompile += %w[action_validator_manifest]
|
18
17
|
end
|
19
18
|
|
20
19
|
initializer "action_validator.importmap", before: "importmap" do |app|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_validator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darin Haener
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-08 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rails
|
@@ -137,7 +136,6 @@ metadata:
|
|
137
136
|
homepage_uri: https://www.github.com/dphaener/action_validator
|
138
137
|
source_code_uri: https://www.github.com/dphaener/action_validator
|
139
138
|
changelog_uri: https://www.github.com/dphaener/action_validator/CHANGELOG.md
|
140
|
-
post_install_message:
|
141
139
|
rdoc_options: []
|
142
140
|
require_paths:
|
143
141
|
- lib
|
@@ -152,8 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
150
|
- !ruby/object:Gem::Version
|
153
151
|
version: '0'
|
154
152
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
156
|
-
signing_key:
|
153
|
+
rubygems_version: 3.6.2
|
157
154
|
specification_version: 4
|
158
155
|
summary: Simple client and server side form validation.
|
159
156
|
test_files: []
|