action_validator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +72 -0
- data/Rakefile +5 -0
- data/app/assets/config/action_validator_manifest.js +2 -0
- data/app/channels/action_validator/form_channel.rb +54 -0
- data/app/helpers/action_validator/form_helper.rb +47 -0
- data/app/javascript/action_validator/controllers/form_controller.js +151 -0
- data/config/importmap.rb +7 -0
- data/lib/action_validator/engine.rb +27 -0
- data/lib/action_validator/form_builder.rb +142 -0
- data/lib/action_validator/version.rb +5 -0
- data/lib/action_validator.rb +23 -0
- data/lib/tasks/action_validator_tasks.rake +8 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 532de573ba9a7413bdfc6ffa8740a6ee28226d95ade42b47855fc5ab269fb01c
|
4
|
+
data.tar.gz: db4a16bf7690207e95bcb04503ca38f13512cc04d0a7acca5252a135bc32de3a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b107cc5fdfef31a1b03cf762f4e18b3cf576de2c1bb060a5dbce9b3c577b8b1412e1c2329990b5e63ffd2d5e19bb658d146a249212213ef80c7617e646b4b183
|
7
|
+
data.tar.gz: 500aa58f0c13bde306b4bfe682cb285819e53ba3cfea8ea71e58210d527b8cab134c01e2fbecdcfde122c8b8778e821e90357ea2759ade1f89287f57176ce2e6
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Darin Haener
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# ActionValidator
|
2
|
+
|
3
|
+
ActionValidator is a Rails engine that provides a simple way to perform dynamic client and server side validations
|
4
|
+
for your Rails forms.
|
5
|
+
|
6
|
+
It leverages native HTML5 validations by default and will only perform server side validations if the HTML5 validation
|
7
|
+
passes.
|
8
|
+
|
9
|
+
Behind the scenes, ActionValidator uses ActionCable to perform the server side validations. Any validations that exist
|
10
|
+
on your model will be automatically validated on the server side, and if there are any errors, they will be returned
|
11
|
+
to the client and displayed in the appropriate form field.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Usage of this gem is very straightforward. There are two helper methods that you can use to render your forms.
|
16
|
+
|
17
|
+
### validator_form_for
|
18
|
+
|
19
|
+
This is the same as the standard Rails `form_for` helper, but it ensures your form uses the `ActionValidator::FormBuilder`
|
20
|
+
to build your forms and automatically adds all of the necessary attributes to the form.
|
21
|
+
|
22
|
+
If you have your own custom form builder, make sure to inherit from `ActionValidator::FormBuilder` and set that as
|
23
|
+
your default builder in your `application_controller.rb` file.
|
24
|
+
|
25
|
+
Also be sure to call `super` in any form builder methods that you override, otherwise you will not get the necessary
|
26
|
+
attributes added to your form.
|
27
|
+
|
28
|
+
### validator_form_with
|
29
|
+
|
30
|
+
This is the same as the standard Rails `form_with` helper and provides the same functionality as `validator_form_for`.
|
31
|
+
|
32
|
+
### validator_error
|
33
|
+
|
34
|
+
This helper method will render a div with the correct Stimulus data attributes on it so that errors can be displayed
|
35
|
+
on your form.
|
36
|
+
|
37
|
+
## Requirements
|
38
|
+
|
39
|
+
All of the JavaScript for ActionValidator is shipped with the gem and currently only supports integration when using
|
40
|
+
Importmaps.
|
41
|
+
|
42
|
+
## Example
|
43
|
+
|
44
|
+
Check the [dummy app](https://github.com/dphaener/action_validator/tree/main/test/dummy) for a working example of how the gem can be used.
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
Add this line to your application's Gemfile:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem "action_validator"
|
52
|
+
```
|
53
|
+
|
54
|
+
And then execute:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ bundle
|
58
|
+
```
|
59
|
+
|
60
|
+
Or install it yourself as:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
$ gem install action_validator
|
64
|
+
```
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
Contribution directions go here.
|
69
|
+
|
70
|
+
## License
|
71
|
+
|
72
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionValidator
|
4
|
+
class FormChannel < ApplicationCable::Channel
|
5
|
+
include ActiveSupport::Configurable
|
6
|
+
include ActionController::RequestForgeryProtection
|
7
|
+
|
8
|
+
def subscribed
|
9
|
+
stream_from "action_validator_form_channel"
|
10
|
+
end
|
11
|
+
|
12
|
+
def unsubscribed
|
13
|
+
stop_all_streams
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate(data)
|
17
|
+
model_class, params = parse_params(data["formData"])
|
18
|
+
if model_class.nil?
|
19
|
+
Rails.logger.warn("No valid model class found, cannot perform remote validation.")
|
20
|
+
ActionCable.server.broadcast("action_validator_form_channel", { errors: {} })
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
instance = validated_instance(model_class, params)
|
25
|
+
|
26
|
+
ActionCable.server.broadcast("action_validator_form_channel", { errors: parse_model_errors(instance) })
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validated_instance(model_class, params)
|
32
|
+
instance = model_class.new(params)
|
33
|
+
instance.validate
|
34
|
+
instance
|
35
|
+
end
|
36
|
+
|
37
|
+
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
|
+
instance.errors.each_with_object({}) { |error, hash| hash[error.attribute] = error.full_message }
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_params(data)
|
46
|
+
parsed_params = Rack::Utils.parse_nested_query(data).without("action", "authenticity_token")
|
47
|
+
params = ActionController::Parameters.new(parsed_params).to_unsafe_h
|
48
|
+
model_name = params.keys.first
|
49
|
+
model_class = model_name.classify.safe_constantize
|
50
|
+
|
51
|
+
[model_class, params[model_name]]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionValidator
|
4
|
+
# Form helpers that will render a standard Rails `form_for` or `form_with` that has all of the necessary attributes
|
5
|
+
# added for the ActionValidator Stimulus controller to work.
|
6
|
+
#
|
7
|
+
module FormHelper
|
8
|
+
# A wrapper around the Rails form_for helper. This method can be used exactly the same as the standard Rails helper.
|
9
|
+
#
|
10
|
+
def validator_form_for(record, options = {}, &block) merge_form_options(options)
|
11
|
+
|
12
|
+
form_for(record, options, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# A wrapper around the Rails form_with helper. This method can be used exactly the same as the standard Rails
|
16
|
+
# helper.
|
17
|
+
#
|
18
|
+
def validator_form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
|
19
|
+
merge_form_options(options)
|
20
|
+
|
21
|
+
form_with(model: model, scope: scope, url: url, format: format, **options, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Renders an unstyled div with the correct Stimulus data attributes on it.
|
25
|
+
#
|
26
|
+
def validator_error(attribute, options = {})
|
27
|
+
options[:data] ||= {}
|
28
|
+
options[:data][ActionValidator::STIMULUS_SELECTORS[:target]] = :error
|
29
|
+
options[:data][:attribute] = attribute
|
30
|
+
|
31
|
+
tag.div({ **options, id: attribute }) { '' }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def merge_form_options(options)
|
37
|
+
default_builder = ActionView::Base.default_form_builder
|
38
|
+
options[:builder] ||= default_builder || ActionValidator::FormBuilder
|
39
|
+
options[:data] ||= {}
|
40
|
+
options[:data].merge!(
|
41
|
+
controller: ActionValidator::STIMULUS_SELECTORS[:controller],
|
42
|
+
form_error_selector_value: ActionValidator.form_error_selector,
|
43
|
+
input_error_selector_value: ActionValidator.input_error_selector
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
import consumer from "channels/consumer";
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ['submit', 'input', 'error'];
|
6
|
+
static values = { modelName: String };
|
7
|
+
|
8
|
+
connect() {
|
9
|
+
// TODO: This might not JUST be inputs. It could be any DOM element. Maybe a better name is needed here.
|
10
|
+
// `remoteValidatableElements` or something. The current set up doesn't really work well for this though
|
11
|
+
// because the `remoteValidatableInputs` is directly tied to an actual input. Maybe the cableReceived method
|
12
|
+
// should check for another `target` that matches the `errors` key in the return value if the error element
|
13
|
+
// doesn't exist for that attribute...
|
14
|
+
//
|
15
|
+
this.remoteValidatableInputs = []
|
16
|
+
this.channel = consumer.subscriptions.create('ActionValidator::FormChannel', {
|
17
|
+
connected: this.#cableConnected.bind(this),
|
18
|
+
disconnected: this.#cableDisconnected.bind(this),
|
19
|
+
received: this.#cableReceived.bind(this),
|
20
|
+
});
|
21
|
+
|
22
|
+
this.inputTargets.forEach(input => {
|
23
|
+
if (input.dataset.remoteValidate === 'true') this.remoteValidatableInputs.push(input);
|
24
|
+
});
|
25
|
+
|
26
|
+
this.#disableSubmit();
|
27
|
+
}
|
28
|
+
|
29
|
+
validateForm() {
|
30
|
+
if (this.#formValid()) {
|
31
|
+
this.#enableSubmit();
|
32
|
+
} else {
|
33
|
+
this.#disableSubmit();
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
validate(ev) {
|
38
|
+
const inputElement = ev.target;
|
39
|
+
const { dataset: { remoteValidate } } = inputElement;
|
40
|
+
this.#checkAndSetDirty(inputElement);
|
41
|
+
if (inputElement.dataset.isDirty === 'false') return;
|
42
|
+
|
43
|
+
const htmlValid = this.validateInput(ev.target);
|
44
|
+
|
45
|
+
if (remoteValidate === 'true' && htmlValid) {
|
46
|
+
this.channel.perform('validate', { formData: this.#serializeForm() });
|
47
|
+
} else {
|
48
|
+
this.validateForm();
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
validateInput(input) {
|
53
|
+
input.setCustomValidity('');
|
54
|
+
const isValid = input.checkValidity();
|
55
|
+
const errorElement = this.errorTargets.find(target => input.dataset.attribute === target.dataset.attribute);
|
56
|
+
|
57
|
+
if (isValid) {
|
58
|
+
errorElement.innerHTML = '';
|
59
|
+
input.dataset.valid = true;
|
60
|
+
|
61
|
+
return true;
|
62
|
+
} else {
|
63
|
+
errorElement.innerHTML = input.validationMessage;
|
64
|
+
input.dataset.valid = false;
|
65
|
+
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
debouncedValidate(ev) {
|
71
|
+
clearTimeout(this.timeout);
|
72
|
+
|
73
|
+
this.timeout = setTimeout(this.validate.bind(this, ev), 500);
|
74
|
+
}
|
75
|
+
|
76
|
+
#checkAndSetDirty(element) {
|
77
|
+
const currentlyDirty = element.dataset.isDirty;
|
78
|
+
const value = element.value;
|
79
|
+
|
80
|
+
if (currentlyDirty === 'false' && value) element.dataset.isDirty = true;
|
81
|
+
}
|
82
|
+
|
83
|
+
#serializeForm() {
|
84
|
+
const formData = new FormData(this.element);
|
85
|
+
const queryString = new URLSearchParams(formData).toString()
|
86
|
+
|
87
|
+
return queryString;
|
88
|
+
}
|
89
|
+
|
90
|
+
#formValid() {
|
91
|
+
return this.inputTargets.every(element => element.dataset.valid === 'true');
|
92
|
+
}
|
93
|
+
|
94
|
+
// TODO: Add an option to just show errors globally somewhere and not next to each input. We should support arbitrary
|
95
|
+
// error messages that map to some sort of error container with a `errorTarget` that maps to the attribute name.
|
96
|
+
//
|
97
|
+
#cableReceived(data) {
|
98
|
+
const { errors } = data;
|
99
|
+
|
100
|
+
this.remoteValidatableInputs.forEach(inputElement => {
|
101
|
+
if (inputElement.dataset.isDirty === 'false') return;
|
102
|
+
|
103
|
+
const attributeName = inputElement.dataset.attribute;
|
104
|
+
const error = errors[attributeName];
|
105
|
+
const errorElement = this.errorTargets.find(target => attributeName === target.dataset.attribute);
|
106
|
+
if (!errorElement) {
|
107
|
+
Stimulus.logDebugActivity(
|
108
|
+
`No error element found for input with attribute '${attributeName}'. Cannot add error.`,
|
109
|
+
'cableReceived'
|
110
|
+
);
|
111
|
+
return;
|
112
|
+
}
|
113
|
+
|
114
|
+
if (error) {
|
115
|
+
errorElement.innerHTML = errors[attributeName];
|
116
|
+
inputElement.setCustomValidity(errors[attributeName]);
|
117
|
+
inputElement.dataset.valid = false;
|
118
|
+
} else {
|
119
|
+
errorElement.innerHTML = '';
|
120
|
+
inputElement.setCustomValidity('');
|
121
|
+
inputElement.dataset.valid = true;
|
122
|
+
}
|
123
|
+
})
|
124
|
+
|
125
|
+
this.validateForm();
|
126
|
+
}
|
127
|
+
|
128
|
+
#disableSubmit() {
|
129
|
+
if (this.hasSubmitTarget) {
|
130
|
+
this.submitTarget.setAttribute('disabled', true);
|
131
|
+
} else {
|
132
|
+
Stimulus.logDebugActivity('No submit target found for form', 'disableSubmit');
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
#enableSubmit() {
|
137
|
+
if (this.hasSubmitTarget) {
|
138
|
+
this.submitTarget.removeAttribute('disabled');
|
139
|
+
} else {
|
140
|
+
Stimulus.logDebugActivity('No submit target found for form', 'enableSubmit');
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
#cableConnected() {
|
145
|
+
Stimulus.logDebugActivity('ActionValidator connected to cable', 'cableConnected');
|
146
|
+
}
|
147
|
+
|
148
|
+
#cableDisconnected() {
|
149
|
+
Stimulus.logDebugActivity('ActionValidator disconnected from cable', 'cableDisconnected');
|
150
|
+
}
|
151
|
+
}
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
pin "application-action-validator", to: "action_validator/application.js", preload: true
|
2
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
3
|
+
pin_all_from(
|
4
|
+
ActionValidator::Engine.root.join("app/javascript/action_validator/controllers"),
|
5
|
+
under: "controllers/action_validator",
|
6
|
+
to: "action_validator/controllers"
|
7
|
+
)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionValidator
|
4
|
+
# The Rails engine for ActionValidator
|
5
|
+
#
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace ActionValidator
|
8
|
+
|
9
|
+
initializer "action_validator.helpers", before: :load_config_initializers do
|
10
|
+
ActiveSupport.on_load(:action_controller_base) do
|
11
|
+
helper ActionValidator::Engine.helpers
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "action_validator.assets" do |app|
|
16
|
+
app.config.assets.paths << root.join("app/javascript")
|
17
|
+
app.config.assets.precompile += %w[action_validator_manifest]
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer "action_validator.importmap", before: "importmap" do |app|
|
21
|
+
if app.config.respond_to?(:importmap)
|
22
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
23
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionValidator
|
4
|
+
# A custom FormBuilder class that renders inputs with all the necessary attributes for the ActionValidator Stimulus
|
5
|
+
# controller to work.
|
6
|
+
#
|
7
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
8
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block) # :nodoc:
|
9
|
+
options[:data] ||= {}
|
10
|
+
options[:data].merge!(input_data_options(attribute, options))
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def error(attribute, options = {}) # :nodoc:
|
16
|
+
options[:data] ||= {}
|
17
|
+
options[:data][ActionValidator::STIMULUS_SELECTORS[:target]] = :error
|
18
|
+
options[:data][:attribute] = attribute
|
19
|
+
|
20
|
+
@template.tag(:div, { **options, id: attribute })
|
21
|
+
end
|
22
|
+
|
23
|
+
def submit(value = nil, options = {}, &block) # :nodoc:
|
24
|
+
if value.is_a?(Hash)
|
25
|
+
options = value
|
26
|
+
value = nil
|
27
|
+
end
|
28
|
+
options[:data] ||= {}
|
29
|
+
options[:data].merge!("action-validator--form-target" => :submit)
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def text_field(attribute, options = {}) # :nodoc:
|
35
|
+
render_field(attribute, options) { super }
|
36
|
+
end
|
37
|
+
|
38
|
+
def number_field(attribute, options = {}) # :nodoc:
|
39
|
+
render_field(attribute, options) { super }
|
40
|
+
end
|
41
|
+
|
42
|
+
def date_field(attribute, options = {}) # :nodoc:
|
43
|
+
render_field(attribute, options) { super }
|
44
|
+
end
|
45
|
+
|
46
|
+
def text_area(attribute, options = {}) # :nodoc:
|
47
|
+
render_field(attribute, options) { super }
|
48
|
+
end
|
49
|
+
|
50
|
+
def email_field(attribute, options = {}) # :nodoc:
|
51
|
+
render_field(attribute, options) { super }
|
52
|
+
end
|
53
|
+
|
54
|
+
def phone_field(attribute, options = {}) # :nodoc:
|
55
|
+
render_field(attribute, options) { super }
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0") # :nodoc:
|
59
|
+
options[:data] ||= {}
|
60
|
+
options[:data].merge!(input_data_options(attribute, options))
|
61
|
+
|
62
|
+
super(attribute, options, checked_value, unchecked_value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def datetime_field(attribute, options = {}) # :nodoc:
|
66
|
+
render_field(attribute, options) { super }
|
67
|
+
end
|
68
|
+
|
69
|
+
def datetime_local_field(attribute, options = {}) # :nodoc:
|
70
|
+
render_field(attribute, options) { super }
|
71
|
+
end
|
72
|
+
|
73
|
+
def file_field(attribute, options = {}) # :nodoc:
|
74
|
+
render_field(attribute, options) { super }
|
75
|
+
end
|
76
|
+
|
77
|
+
def hidden_field(attribute, options = {}) # :nodoc:
|
78
|
+
render_field(attribute, options) { super }
|
79
|
+
end
|
80
|
+
|
81
|
+
def month_field(attribute, options = {}) # :nodoc:
|
82
|
+
render_field(attribute, options) { super }
|
83
|
+
end
|
84
|
+
|
85
|
+
def password_field(attribute, options = {}) # :nodoc:
|
86
|
+
render_field(attribute, options) { super }
|
87
|
+
end
|
88
|
+
|
89
|
+
def radio_button(attribute, options = {}) # :nodoc:
|
90
|
+
render_field(attribute, options) { super }
|
91
|
+
end
|
92
|
+
|
93
|
+
def range_field(attribute, options = {}) # :nodoc:
|
94
|
+
render_field(attribute, options) { super }
|
95
|
+
end
|
96
|
+
|
97
|
+
def rich_text_area(attribute, options = {}) # :nodoc:
|
98
|
+
render_field(attribute, options) { super }
|
99
|
+
end
|
100
|
+
|
101
|
+
def search_field(attribute, options = {}) # :nodoc:
|
102
|
+
render_field(attribute, options) { super }
|
103
|
+
end
|
104
|
+
|
105
|
+
def telephone_field(attribute, options = {}) # :nodoc:
|
106
|
+
render_field(attribute, options) { super }
|
107
|
+
end
|
108
|
+
|
109
|
+
def time_field(attribute, options = {}) # :nodoc:
|
110
|
+
render_field(attribute, options) { super }
|
111
|
+
end
|
112
|
+
|
113
|
+
def url_field(attribute, options = {}) # :nodoc:
|
114
|
+
render_field(attribute, options) { super }
|
115
|
+
end
|
116
|
+
|
117
|
+
def week_field(attribute, options = {}) # :nodoc:
|
118
|
+
render_field(attribute, options) { super }
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def render_field(attribute, options = {})
|
124
|
+
options[:data] ||= {}
|
125
|
+
options[:data].merge!(input_data_options(attribute, options))
|
126
|
+
|
127
|
+
yield
|
128
|
+
end
|
129
|
+
|
130
|
+
def input_data_options(attribute, options)
|
131
|
+
validate_event = options.delete(:validate_event) || ActionValidator.default_validate_event
|
132
|
+
{
|
133
|
+
ActionValidator::STIMULUS_SELECTORS[:target] => :input,
|
134
|
+
"is-dirty" => false,
|
135
|
+
"remote-validate" => options.delete(:remote_validate) || false,
|
136
|
+
"action" => "#{validate_event}->#{ActionValidator::STIMULUS_SELECTORS[:validate_action]}",
|
137
|
+
"attribute" => attribute,
|
138
|
+
"valid" => !options[:required]
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_validator/version"
|
4
|
+
require "action_validator/engine"
|
5
|
+
require "action_validator/form_builder"
|
6
|
+
|
7
|
+
# The main module and configuration for ActionValidator.
|
8
|
+
#
|
9
|
+
module ActionValidator
|
10
|
+
mattr_accessor :form_error_selector, default: :form_errors
|
11
|
+
mattr_accessor :input_error_selector, default: :input_error
|
12
|
+
mattr_accessor :default_validate_event, default: :blur
|
13
|
+
|
14
|
+
STIMULUS_SELECTORS = {
|
15
|
+
target: "action-validator--form-target",
|
16
|
+
controller: "action-validator--form",
|
17
|
+
validate_action: "action-validator--form#validate"
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def self.setup
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_validator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Darin Haener
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.0.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: importmap-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.5
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.5
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: puma
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sassc-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: stimulus-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.1.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.1.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: turbo-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.2
|
111
|
+
description: Validate your Rails forms remotely in real time using ActionCable. Also
|
112
|
+
validate client side using native HTML validations and present them in a much 'prettier'
|
113
|
+
format.
|
114
|
+
email:
|
115
|
+
- darin.haener@hey.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- MIT-LICENSE
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- app/assets/config/action_validator_manifest.js
|
124
|
+
- app/channels/action_validator/form_channel.rb
|
125
|
+
- app/helpers/action_validator/form_helper.rb
|
126
|
+
- app/javascript/action_validator/controllers/form_controller.js
|
127
|
+
- config/importmap.rb
|
128
|
+
- lib/action_validator.rb
|
129
|
+
- lib/action_validator/engine.rb
|
130
|
+
- lib/action_validator/form_builder.rb
|
131
|
+
- lib/action_validator/version.rb
|
132
|
+
- lib/tasks/action_validator_tasks.rake
|
133
|
+
homepage: https://www.github.com/dphaener/action_validator
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata:
|
137
|
+
homepage_uri: https://www.github.com/dphaener/action_validator
|
138
|
+
source_code_uri: https://www.github.com/dphaener/action_validator
|
139
|
+
changelog_uri: https://www.github.com/dphaener/action_validator/CHANGELOG.md
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubygems_version: 3.5.22
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Simple client and server side form validation.
|
159
|
+
test_files: []
|