view_component-form 0.2.4 → 0.2.6
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 +20 -3
- data/README.md +34 -5
- data/app/components/concerns/view_component/form/element_proc.rb +27 -0
- data/app/components/view_component/form/collection_check_boxes_component.rb +3 -1
- data/app/components/view_component/form/collection_radio_buttons_component.rb +3 -1
- data/app/components/view_component/form/field_component.rb +2 -2
- data/lib/generators/vcf/builder/builder_generator.rb +1 -1
- data/lib/generators/vcf/builder/templates/builder.rb.erb +21 -0
- data/lib/view_component/form/builder.rb +5 -240
- data/lib/view_component/form/engine.rb +1 -0
- data/lib/view_component/form/helpers/custom.rb +17 -0
- data/lib/view_component/form/helpers/rails.rb +172 -0
- data/lib/view_component/form/helpers/rails_7_backports.rb +25 -0
- data/lib/view_component/form/renderer.rb +69 -0
- data/lib/view_component/form/validation_context.rb +21 -0
- data/lib/view_component/form/version.rb +1 -1
- metadata +15 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 128d4c769f1fa3e25cf83830db0b8279413dd8da3e9c7e312aeee06f0456336a
|
4
|
+
data.tar.gz: 63d592e4fa33d6bb77262bea0526761d5b251f26f37c6fac42a771f2506b3250
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f584160cf5560bf80869e4b98ea86d4ac0ce35b25c97dcbb5f38d4fbbacddfce7c64734697327d500b36e537391ac5c34f81470491b2ed75a3347e6664b53b08
|
7
|
+
data.tar.gz: 9fd72c0345d99a0207c006d916cf7e4f75224a88b4bc0175d3f56ffad491ebc42da14efa3619796896c12d37a8eef88dbd70911f47700eaa5fe520ea9f1277e0
|
data/CHANGELOG.md
CHANGED
@@ -5,11 +5,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
|
-
|
8
|
+
|
9
|
+
## [0.2.6] - 2023-10-11
|
10
|
+
### Added
|
11
|
+
- Support for Rails 7.1 (#151)
|
12
|
+
- Add `element_proc` option to `CollectionCheckBoxesComponent` and `CollectionRadioButtonsComponent` to customize the way the elements will be shown (#142)
|
13
|
+
|
14
|
+
## [0.2.5] - 2023-05-01
|
15
|
+
### Changed
|
16
|
+
- Split `Form::Builder` into modules, to allow including only some modules instead of inheriting the whole class (#134)
|
17
|
+
- Using the `Form::Builder` generator now creates the file in `app/helpers` by default, instead of `lib` previously, so that it's autoloaded by Rails without further configuration (#137)
|
18
|
+
- Support for `view_component` 3.0 (#136, #147)
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
- Update dependencies (#128)
|
22
|
+
- Add Ruby 3.2 to CI (#128)
|
23
|
+
- Fix specs for Rails 7.1 (#128)
|
9
24
|
|
10
25
|
## [0.2.4] - 2022-04-27
|
11
26
|
### Changed
|
12
|
-
- Add
|
27
|
+
- Add Ruby 3.1 to CI (#123)
|
13
28
|
|
14
29
|
### Fixed
|
15
30
|
- Fix `FileFieldComponent` options for `direct_upload` and `include_hidden` (#122)
|
@@ -88,7 +103,9 @@ Nothing yet
|
|
88
103
|
- Add CHANGELOG (#50)
|
89
104
|
- Add CI (#2)
|
90
105
|
|
91
|
-
[Unreleased]: https://github.com/pantographe/view_component-form/compare/v0.2.
|
106
|
+
[Unreleased]: https://github.com/pantographe/view_component-form/compare/v0.2.5...HEAD
|
107
|
+
[0.2.5]: https://github.com/pantographe/view_component-form/compare/v0.2.4...v0.2.5
|
108
|
+
[0.2.4]: https://github.com/pantographe/view_component-form/compare/v0.2.3...v0.2.4
|
92
109
|
[0.2.3]: https://github.com/pantographe/view_component-form/compare/v0.2.2...v0.2.3
|
93
110
|
[0.2.2]: https://github.com/pantographe/view_component-form/compare/v0.2.1...v0.2.2
|
94
111
|
[0.2.1]: https://github.com/pantographe/view_component-form/compare/v0.2.0...v0.2.1
|
data/README.md
CHANGED
@@ -4,6 +4,10 @@
|
|
4
4
|
|
5
5
|
:warning: **This is an early release: the API is subject to change until we reach `v1.0.0`.**
|
6
6
|
|
7
|
+
Development of this gem is sponsored by:
|
8
|
+
|
9
|
+
<a href="https://etamin.studio/?ref=view_component-form"><img src="https://etamin.studio/images/logo.svg" alt="Sponsored by Etamin Studio" width="184" height="22"></a> <a href="https://pantographe.studio/?ref=view_component-form"><img src="https://static.s3.office.pantographe.cloud/logofull.svg" alt="Sponsored by Pantographe" width="156" height="25"></a>
|
10
|
+
|
7
11
|
## Compatibility
|
8
12
|
|
9
13
|
This gem is tested on:
|
@@ -91,16 +95,16 @@ First, generate your own `FormBuilder`:
|
|
91
95
|
```console
|
92
96
|
bin/rails generate vcf:builder CustomFormBuilder
|
93
97
|
|
94
|
-
create
|
98
|
+
create app/helpers/custom_form_builder.rb
|
95
99
|
```
|
96
100
|
|
97
101
|
This allows you to pick the namespace your components will be loaded from.
|
98
102
|
|
99
103
|
```rb
|
100
|
-
#
|
104
|
+
# app/helpers/custom_form_builder.rb
|
101
105
|
class CustomFormBuilder < ViewComponent::Form::Builder
|
102
106
|
# Set the namespace you want to use for your own components
|
103
|
-
namespace Custom::Form
|
107
|
+
namespace "Custom::Form"
|
104
108
|
end
|
105
109
|
```
|
106
110
|
|
@@ -116,7 +120,32 @@ bin/rails generate vcf:builder AnotherCustomFormBuilder --namespace AnotherCusto
|
|
116
120
|
# app/forms/another_custom_form_builder.rb
|
117
121
|
class AnotherCustomFormBuilder < ViewComponent::Form::Builder
|
118
122
|
# Set the namespace you want to use for your own components
|
119
|
-
namespace AnotherCustom::Form
|
123
|
+
namespace "AnotherCustom::Form"
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
Another approach is to include only some modules instead of inheriting from the whole class:
|
128
|
+
|
129
|
+
```rb
|
130
|
+
# app/forms/modular_custom_form_builder.rb
|
131
|
+
class ModularCustomFormBuilder < ActionView::Helpers::FormBuilder
|
132
|
+
# Provides `render_component` method and namespace management
|
133
|
+
include ViewComponent::Form::Renderer
|
134
|
+
|
135
|
+
# Exposes a `validation_context` to your components
|
136
|
+
include ViewComponent::Form::ValidationContext
|
137
|
+
|
138
|
+
# All standard Rails form helpers
|
139
|
+
include ViewComponent::Form::Helpers::Rails
|
140
|
+
|
141
|
+
# Backports of Rails 7 form helpers (can be removed if you're running Rails >= 7)
|
142
|
+
# include ViewComponent::Form::Helpers::Rails7Backports
|
143
|
+
|
144
|
+
# Additional form helpers provided by ViewComponent::Form
|
145
|
+
# include ViewComponent::Form::Helpers::Custom
|
146
|
+
|
147
|
+
# Set the namespace you want to use for your own components
|
148
|
+
namespace "AnotherCustom::Form"
|
120
149
|
end
|
121
150
|
```
|
122
151
|
|
@@ -384,7 +413,7 @@ For more complex components, we recommend the [`rspec-html-matchers` gem](https:
|
|
384
413
|
|
385
414
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
386
415
|
|
387
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
|
416
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `bin/release x.x.x`, which will update the `version.rb` file, open the changelog for edition, create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
388
417
|
|
389
418
|
## Contributing
|
390
419
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module ElementProc
|
6
|
+
attr_reader :element_proc
|
7
|
+
|
8
|
+
def initialize(*args, **kwargs)
|
9
|
+
super
|
10
|
+
set_element_proc!
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def set_element_proc!
|
16
|
+
options_element_proc = options.delete(:element_proc)
|
17
|
+
html_options_element_proc = html_options.delete(:element_proc)
|
18
|
+
|
19
|
+
if options_element_proc && html_options_element_proc
|
20
|
+
raise ArgumentError, "#{self.class.name} received :element_proc twice, expected only once"
|
21
|
+
end
|
22
|
+
|
23
|
+
@element_proc = options_element_proc || html_options_element_proc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module ViewComponent
|
4
4
|
module Form
|
5
5
|
class CollectionCheckBoxesComponent < FieldComponent
|
6
|
+
include ElementProc
|
7
|
+
|
6
8
|
attr_reader :collection, :value_method, :text_method, :html_options
|
7
9
|
|
8
10
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
@@ -36,7 +38,7 @@ module ViewComponent
|
|
36
38
|
options,
|
37
39
|
html_options,
|
38
40
|
&content
|
39
|
-
).render
|
41
|
+
).render(&element_proc)
|
40
42
|
end
|
41
43
|
|
42
44
|
protected
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module ViewComponent
|
4
4
|
module Form
|
5
5
|
class CollectionRadioButtonsComponent < FieldComponent
|
6
|
+
include ElementProc
|
7
|
+
|
6
8
|
attr_reader :collection, :value_method, :text_method, :html_options
|
7
9
|
|
8
10
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
@@ -36,7 +38,7 @@ module ViewComponent
|
|
36
38
|
options,
|
37
39
|
html_options,
|
38
40
|
&content
|
39
|
-
).render
|
41
|
+
).render(&element_proc)
|
40
42
|
end
|
41
43
|
|
42
44
|
protected
|
@@ -71,13 +71,13 @@ module ViewComponent
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def optional?(context: validation_context)
|
74
|
-
return
|
74
|
+
return false if object.nil?
|
75
75
|
|
76
76
|
!required?(context: context)
|
77
77
|
end
|
78
78
|
|
79
79
|
def required?(context: validation_context)
|
80
|
-
return
|
80
|
+
return false if object.nil?
|
81
81
|
|
82
82
|
validators(context: context).any?(ActiveModel::Validations::PresenceValidator)
|
83
83
|
end
|
@@ -6,7 +6,7 @@ module Vcf
|
|
6
6
|
source_root File.join(File.dirname(__FILE__), "templates")
|
7
7
|
|
8
8
|
class_option :namespace, default: "Form"
|
9
|
-
class_option :path, default: "
|
9
|
+
class_option :path, default: "app/helpers"
|
10
10
|
|
11
11
|
def create_builder_from_template
|
12
12
|
template "builder.rb.erb", destination
|
@@ -1,6 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class <%= class_name %> < ViewComponent::Form::Builder
|
4
|
+
# Instead of inheriting from ViewComponent::Form::Builder,
|
5
|
+
# you can also inherit from ActionView::Helpers::FormBuilder
|
6
|
+
# then include only the modules you need:
|
7
|
+
|
8
|
+
# Provides `render_component` method and namespace management
|
9
|
+
# include ViewComponent::Form::Renderer
|
10
|
+
|
11
|
+
# Exposes a `validation_context` to your components
|
12
|
+
# include ViewComponent::Form::ValidationContext
|
13
|
+
|
14
|
+
# All standard Rails form helpers
|
15
|
+
# include ViewComponent::Form::Helpers::Rails
|
16
|
+
|
17
|
+
# Backports of Rails 7 form helpers (can be removed if you're running Rails >= 7)
|
18
|
+
# include ViewComponent::Form::Helpers::Rails7Backports
|
19
|
+
|
20
|
+
# Additional form helpers provided by ViewComponent::Form
|
21
|
+
# include ViewComponent::Form::Helpers::Custom
|
22
|
+
|
4
23
|
# Set the namespace you want to use for your own components
|
24
|
+
# requires inheriting from ViewComponent::Form::Builder
|
25
|
+
# or including ViewComponent::Form::Renderer
|
5
26
|
namespace "<%= components_namespace %>"
|
6
27
|
end
|
@@ -3,246 +3,11 @@
|
|
3
3
|
module ViewComponent
|
4
4
|
module Form
|
5
5
|
class Builder < ActionView::Helpers::FormBuilder
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class_attribute :lookup_namespaces, default: [ViewComponent::Form]
|
13
|
-
|
14
|
-
class << self
|
15
|
-
def inherited(base)
|
16
|
-
base.lookup_namespaces = lookup_namespaces.dup
|
17
|
-
|
18
|
-
super
|
19
|
-
end
|
20
|
-
|
21
|
-
def namespace(namespace)
|
22
|
-
if lookup_namespaces.include?(namespace)
|
23
|
-
raise NamespaceAlreadyAddedError, "The component namespace '#{namespace}' is already added"
|
24
|
-
end
|
25
|
-
|
26
|
-
lookup_namespaces.prepend namespace
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_reader :validation_context
|
31
|
-
|
32
|
-
def initialize(*)
|
33
|
-
@__component_klass_cache = {}
|
34
|
-
|
35
|
-
super
|
36
|
-
|
37
|
-
@validation_context = options[:validation_context]
|
38
|
-
end
|
39
|
-
|
40
|
-
(field_helpers - %i[
|
41
|
-
check_box
|
42
|
-
datetime_field
|
43
|
-
datetime_local_field
|
44
|
-
fields
|
45
|
-
fields_for
|
46
|
-
file_field
|
47
|
-
hidden_field
|
48
|
-
label
|
49
|
-
phone_field
|
50
|
-
radio_button
|
51
|
-
]).each do |selector|
|
52
|
-
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
53
|
-
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
54
|
-
render_component( # render_component(
|
55
|
-
:#{selector}, # :text_field,
|
56
|
-
@object_name, # @object_name,
|
57
|
-
method, # method,
|
58
|
-
objectify_options(options), # objectify_options(options),
|
59
|
-
) # )
|
60
|
-
end # end
|
61
|
-
RUBY_EVAL
|
62
|
-
end
|
63
|
-
alias phone_field telephone_field
|
64
|
-
|
65
|
-
# See: https://github.com/rails/rails/blob/33d60cb02dcac26d037332410eabaeeb0bdc384c/actionview/lib/action_view/helpers/form_helper.rb#L2280
|
66
|
-
def label(method, text = nil, options = {}, &block)
|
67
|
-
render_component(:label, @object_name, method, text, objectify_options(options), &block)
|
68
|
-
end
|
69
|
-
|
70
|
-
def datetime_field(method, options = {})
|
71
|
-
render_component(
|
72
|
-
:datetime_local_field, @object_name, method, objectify_options(options)
|
73
|
-
)
|
74
|
-
end
|
75
|
-
alias datetime_locale_field datetime_field
|
76
|
-
|
77
|
-
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
78
|
-
render_component(
|
79
|
-
:check_box, @object_name, method, checked_value, unchecked_value, objectify_options(options)
|
80
|
-
)
|
81
|
-
end
|
82
|
-
|
83
|
-
def radio_button(method, tag_value, options = {})
|
84
|
-
render_component(
|
85
|
-
:radio_button, @object_name, method, tag_value, objectify_options(options)
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
|
-
def file_field(method, options = {})
|
90
|
-
self.multipart = true
|
91
|
-
render_component(:file_field, @object_name, method, objectify_options(options))
|
92
|
-
end
|
93
|
-
|
94
|
-
def submit(value = nil, options = {})
|
95
|
-
if value.is_a?(Hash)
|
96
|
-
options = value
|
97
|
-
value = nil
|
98
|
-
end
|
99
|
-
value ||= submit_default_value
|
100
|
-
render_component(:submit, value, options)
|
101
|
-
end
|
102
|
-
|
103
|
-
def button(value = nil, options = {}, &block)
|
104
|
-
if value.is_a?(Hash)
|
105
|
-
options = value
|
106
|
-
value = nil
|
107
|
-
end
|
108
|
-
value ||= submit_default_value
|
109
|
-
render_component(:button, value, options, &block)
|
110
|
-
end
|
111
|
-
|
112
|
-
# See: https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/actionview/lib/action_view/helpers/form_options_helper.rb
|
113
|
-
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
114
|
-
render_component(
|
115
|
-
:select, @object_name, method, choices, objectify_options(options),
|
116
|
-
@default_html_options.merge(html_options), &block
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
# rubocop:disable Metrics/ParameterLists
|
121
|
-
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
122
|
-
render_component(
|
123
|
-
:collection_select, @object_name, method, collection, value_method, text_method,
|
124
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
125
|
-
)
|
126
|
-
end
|
127
|
-
|
128
|
-
def grouped_collection_select(
|
129
|
-
method, collection,
|
130
|
-
group_method, group_label_method, option_key_method, option_value_method,
|
131
|
-
options = {}, html_options = {}
|
132
|
-
)
|
133
|
-
render_component(
|
134
|
-
:grouped_collection_select, @object_name, method, collection, group_method,
|
135
|
-
group_label_method, option_key_method, option_value_method,
|
136
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
137
|
-
)
|
138
|
-
end
|
139
|
-
|
140
|
-
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
141
|
-
render_component(
|
142
|
-
:collection_check_boxes, @object_name, method, collection, value_method, text_method,
|
143
|
-
objectify_options(options), @default_html_options.merge(html_options), &block
|
144
|
-
)
|
145
|
-
end
|
146
|
-
|
147
|
-
def collection_radio_buttons(
|
148
|
-
method, collection,
|
149
|
-
value_method, text_method,
|
150
|
-
options = {}, html_options = {},
|
151
|
-
&block
|
152
|
-
)
|
153
|
-
render_component(
|
154
|
-
:collection_radio_buttons, @object_name, method, collection, value_method, text_method,
|
155
|
-
objectify_options(options), @default_html_options.merge(html_options), &block
|
156
|
-
)
|
157
|
-
end
|
158
|
-
# rubocop:enable Metrics/ParameterLists
|
159
|
-
|
160
|
-
def date_select(method, options = {}, html_options = {})
|
161
|
-
render_component(
|
162
|
-
:date_select, @object_name, method,
|
163
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
164
|
-
)
|
165
|
-
end
|
166
|
-
|
167
|
-
def datetime_select(method, options = {}, html_options = {})
|
168
|
-
render_component(
|
169
|
-
:datetime_select, @object_name, method,
|
170
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
171
|
-
)
|
172
|
-
end
|
173
|
-
|
174
|
-
def time_select(method, options = {}, html_options = {})
|
175
|
-
render_component(
|
176
|
-
:time_select, @object_name, method,
|
177
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
178
|
-
)
|
179
|
-
end
|
180
|
-
|
181
|
-
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
182
|
-
render_component(
|
183
|
-
:time_zone_select, @object_name, method, priority_zones,
|
184
|
-
objectify_options(options), @default_html_options.merge(html_options)
|
185
|
-
)
|
186
|
-
end
|
187
|
-
|
188
|
-
if defined?(ActionView::Helpers::Tags::ActionText)
|
189
|
-
def rich_text_area(method, options = {})
|
190
|
-
render_component(:rich_text_area, @object_name, method, objectify_options(options))
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def error_message(method, options = {})
|
195
|
-
render_component(:error_message, @object_name, method, objectify_options(options))
|
196
|
-
end
|
197
|
-
|
198
|
-
def hint(method, text = nil, options = {}, &block)
|
199
|
-
render_component(:hint, @object_name, method, text, objectify_options(options), &block)
|
200
|
-
end
|
201
|
-
|
202
|
-
# Backport field_id from Rails 7.0
|
203
|
-
if Rails::VERSION::MAJOR < 7
|
204
|
-
def field_id(method_name, *suffixes, namespace: @options[:namespace], index: @index)
|
205
|
-
object_name = object_name.model_name.singular if object_name.respond_to?(:model_name)
|
206
|
-
|
207
|
-
sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
208
|
-
|
209
|
-
sanitized_method_name = method_name.to_s.delete_suffix("?")
|
210
|
-
|
211
|
-
[
|
212
|
-
namespace,
|
213
|
-
sanitized_object_name.presence,
|
214
|
-
(index unless sanitized_object_name.empty?),
|
215
|
-
sanitized_method_name,
|
216
|
-
*suffixes
|
217
|
-
].tap(&:compact!).join("_")
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
private
|
222
|
-
|
223
|
-
def render_component(component_name, *args, &block)
|
224
|
-
component = component_klass(component_name).new(self, *args)
|
225
|
-
component.render_in(@template, &block)
|
226
|
-
end
|
227
|
-
|
228
|
-
def objectify_options(options)
|
229
|
-
@default_options.merge(options.merge(object: @object))
|
230
|
-
end
|
231
|
-
|
232
|
-
def component_klass(component_name)
|
233
|
-
@__component_klass_cache[component_name] ||= begin
|
234
|
-
component_klass = self.class.lookup_namespaces.filter_map do |namespace|
|
235
|
-
"#{namespace}::#{component_name.to_s.camelize}Component".safe_constantize || false
|
236
|
-
end.first
|
237
|
-
|
238
|
-
unless component_klass.is_a?(Class) && component_klass < ViewComponent::Base
|
239
|
-
raise NotImplementedComponentError, "Component named #{component_name} doesn't exist" \
|
240
|
-
" or is not a ViewComponent::Base class"
|
241
|
-
end
|
242
|
-
|
243
|
-
component_klass
|
244
|
-
end
|
245
|
-
end
|
6
|
+
include ViewComponent::Form::Renderer
|
7
|
+
include ViewComponent::Form::ValidationContext
|
8
|
+
include ViewComponent::Form::Helpers::Rails
|
9
|
+
include ViewComponent::Form::Helpers::Rails7Backports if ::Rails::VERSION::MAJOR < 7
|
10
|
+
include ViewComponent::Form::Helpers::Custom
|
246
11
|
end
|
247
12
|
end
|
248
13
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module Helpers
|
6
|
+
module Custom
|
7
|
+
def error_message(method, options = {})
|
8
|
+
render_component(:error_message, @object_name, method, objectify_options(options))
|
9
|
+
end
|
10
|
+
|
11
|
+
def hint(method, text = nil, options = {}, &block)
|
12
|
+
render_component(:hint, @object_name, method, text, objectify_options(options), &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module Helpers
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
7
|
+
module Rails
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
(field_helpers - %i[
|
12
|
+
check_box
|
13
|
+
datetime_field
|
14
|
+
datetime_local_field
|
15
|
+
fields
|
16
|
+
fields_for
|
17
|
+
file_field
|
18
|
+
hidden_field
|
19
|
+
label
|
20
|
+
phone_field
|
21
|
+
radio_button
|
22
|
+
]).each do |selector|
|
23
|
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
24
|
+
def #{selector}(method, options = {}) # def text_field(method, options = {})
|
25
|
+
render_component( # render_component(
|
26
|
+
:#{selector}, # :text_field,
|
27
|
+
@object_name, # @object_name,
|
28
|
+
method, # method,
|
29
|
+
objectify_options(options), # objectify_options(options),
|
30
|
+
) # )
|
31
|
+
end # end
|
32
|
+
RUBY_EVAL
|
33
|
+
end
|
34
|
+
alias_method :phone_field, :telephone_field
|
35
|
+
end
|
36
|
+
end
|
37
|
+
# rubocop:enable Metrics/MethodLength
|
38
|
+
|
39
|
+
# See: https://github.com/rails/rails/blob/33d60cb02dcac26d037332410eabaeeb0bdc384c/actionview/lib/action_view/helpers/form_helper.rb#L2280
|
40
|
+
def label(method, text = nil, options = {}, &block)
|
41
|
+
render_component(:label, @object_name, method, text, objectify_options(options), &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def datetime_field(method, options = {})
|
45
|
+
render_component(
|
46
|
+
:datetime_local_field, @object_name, method, objectify_options(options)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
alias datetime_locale_field datetime_field
|
50
|
+
|
51
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
52
|
+
render_component(
|
53
|
+
:check_box, @object_name, method, checked_value, unchecked_value, objectify_options(options)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def radio_button(method, tag_value, options = {})
|
58
|
+
render_component(
|
59
|
+
:radio_button, @object_name, method, tag_value, objectify_options(options)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def file_field(method, options = {})
|
64
|
+
self.multipart = true
|
65
|
+
render_component(:file_field, @object_name, method, objectify_options(options))
|
66
|
+
end
|
67
|
+
|
68
|
+
def submit(value = nil, options = {})
|
69
|
+
if value.is_a?(Hash)
|
70
|
+
options = value
|
71
|
+
value = nil
|
72
|
+
end
|
73
|
+
value ||= submit_default_value
|
74
|
+
render_component(:submit, value, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def button(value = nil, options = {}, &block)
|
78
|
+
if value.is_a?(Hash)
|
79
|
+
options = value
|
80
|
+
value = nil
|
81
|
+
end
|
82
|
+
value ||= submit_default_value
|
83
|
+
render_component(:button, value, options, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# See: https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/actionview/lib/action_view/helpers/form_options_helper.rb
|
87
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
88
|
+
render_component(
|
89
|
+
:select, @object_name, method, choices, objectify_options(options),
|
90
|
+
@default_html_options.merge(html_options), &block
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
# rubocop:disable Metrics/ParameterLists
|
95
|
+
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
96
|
+
render_component(
|
97
|
+
:collection_select, @object_name, method, collection, value_method, text_method,
|
98
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def grouped_collection_select(
|
103
|
+
method, collection,
|
104
|
+
group_method, group_label_method, option_key_method, option_value_method,
|
105
|
+
options = {}, html_options = {}
|
106
|
+
)
|
107
|
+
render_component(
|
108
|
+
:grouped_collection_select, @object_name, method, collection, group_method,
|
109
|
+
group_label_method, option_key_method, option_value_method,
|
110
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {},
|
115
|
+
&block)
|
116
|
+
render_component(
|
117
|
+
:collection_check_boxes, @object_name, method, collection, value_method, text_method,
|
118
|
+
objectify_options(options), @default_html_options.merge(html_options), &block
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def collection_radio_buttons(
|
123
|
+
method, collection,
|
124
|
+
value_method, text_method,
|
125
|
+
options = {}, html_options = {},
|
126
|
+
&block
|
127
|
+
)
|
128
|
+
render_component(
|
129
|
+
:collection_radio_buttons, @object_name, method, collection, value_method, text_method,
|
130
|
+
objectify_options(options), @default_html_options.merge(html_options), &block
|
131
|
+
)
|
132
|
+
end
|
133
|
+
# rubocop:enable Metrics/ParameterLists
|
134
|
+
|
135
|
+
def date_select(method, options = {}, html_options = {})
|
136
|
+
render_component(
|
137
|
+
:date_select, @object_name, method,
|
138
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def datetime_select(method, options = {}, html_options = {})
|
143
|
+
render_component(
|
144
|
+
:datetime_select, @object_name, method,
|
145
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def time_select(method, options = {}, html_options = {})
|
150
|
+
render_component(
|
151
|
+
:time_select, @object_name, method,
|
152
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
157
|
+
render_component(
|
158
|
+
:time_zone_select, @object_name, method, priority_zones,
|
159
|
+
objectify_options(options), @default_html_options.merge(html_options)
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
if defined?(ActionView::Helpers::Tags::ActionText)
|
164
|
+
def rich_text_area(method, options = {})
|
165
|
+
render_component(:rich_text_area, @object_name, method, objectify_options(options))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
# rubocop:enable Metrics/ModuleLength
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module Helpers
|
6
|
+
module Rails7Backports
|
7
|
+
def field_id(method_name, *suffixes, namespace: @options[:namespace], index: @index)
|
8
|
+
object_name = object_name.model_name.singular if object_name.respond_to?(:model_name)
|
9
|
+
|
10
|
+
sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
11
|
+
|
12
|
+
sanitized_method_name = method_name.to_s.delete_suffix("?")
|
13
|
+
|
14
|
+
[
|
15
|
+
namespace,
|
16
|
+
sanitized_object_name.presence,
|
17
|
+
(index unless sanitized_object_name.empty?),
|
18
|
+
sanitized_method_name,
|
19
|
+
*suffixes
|
20
|
+
].tap(&:compact!).join("_")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module Renderer
|
6
|
+
class Error < StandardError; end
|
7
|
+
class NamespaceAlreadyAddedError < Error; end
|
8
|
+
class NotImplementedComponentError < Error; end
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/MethodLength
|
11
|
+
def self.included(base)
|
12
|
+
base.class_eval do
|
13
|
+
original_initialize_method = instance_method(:initialize)
|
14
|
+
|
15
|
+
define_method(:initialize) do |*args, &block|
|
16
|
+
@__component_klass_cache = {}
|
17
|
+
|
18
|
+
original_initialize_method.bind_call(self, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
class_attribute :lookup_namespaces, default: [ViewComponent::Form]
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def inherited(base)
|
25
|
+
base.lookup_namespaces = lookup_namespaces.dup
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def namespace(namespace)
|
31
|
+
if lookup_namespaces.include?(namespace)
|
32
|
+
raise NamespaceAlreadyAddedError, "The component namespace '#{namespace}' is already added"
|
33
|
+
end
|
34
|
+
|
35
|
+
lookup_namespaces.prepend namespace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def render_component(component_name, *args, &block)
|
45
|
+
component = component_klass(component_name).new(self, *args)
|
46
|
+
component.render_in(@template, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def objectify_options(options)
|
50
|
+
@default_options.merge(options.merge(object: @object))
|
51
|
+
end
|
52
|
+
|
53
|
+
def component_klass(component_name)
|
54
|
+
@__component_klass_cache[component_name] ||= begin
|
55
|
+
component_klass = self.class.lookup_namespaces.filter_map do |namespace|
|
56
|
+
"#{namespace}::#{component_name.to_s.camelize}Component".safe_constantize || false
|
57
|
+
end.first
|
58
|
+
|
59
|
+
unless component_klass.is_a?(Class) && component_klass < ViewComponent::Base
|
60
|
+
raise NotImplementedComponentError, "Component named #{component_name} doesn't exist " \
|
61
|
+
"or is not a ViewComponent::Base class"
|
62
|
+
end
|
63
|
+
|
64
|
+
component_klass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module Form
|
5
|
+
module ValidationContext
|
6
|
+
attr_reader :validation_context
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
original_initialize_method = instance_method(:initialize)
|
11
|
+
|
12
|
+
define_method(:initialize) do |*args, &block|
|
13
|
+
original_initialize_method.bind_call(self, *args, &block)
|
14
|
+
|
15
|
+
@validation_context = options[:validation_context]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component-form
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pantographe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionview
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: 6.0.0
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '7.
|
22
|
+
version: '7.2'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: 6.0.0
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '7.
|
32
|
+
version: '7.2'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: activesupport
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: 6.0.0
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '7.
|
42
|
+
version: '7.2'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,7 +49,7 @@ dependencies:
|
|
49
49
|
version: 6.0.0
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: '7.
|
52
|
+
version: '7.2'
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: view_component
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
version: 2.34.0
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
62
|
+
version: '4.0'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -69,7 +69,7 @@ dependencies:
|
|
69
69
|
version: 2.34.0
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: '
|
72
|
+
version: '4.0'
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: zeitwerk
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- CHANGELOG.md
|
95
95
|
- LICENSE.txt
|
96
96
|
- README.md
|
97
|
+
- app/components/concerns/view_component/form/element_proc.rb
|
97
98
|
- app/components/view_component/form/base_component.rb
|
98
99
|
- app/components/view_component/form/button_component.rb
|
99
100
|
- app/components/view_component/form/check_box_component.rb
|
@@ -136,7 +137,12 @@ files:
|
|
136
137
|
- lib/view_component/form/builder.rb
|
137
138
|
- lib/view_component/form/class_names_helper.rb
|
138
139
|
- lib/view_component/form/engine.rb
|
140
|
+
- lib/view_component/form/helpers/custom.rb
|
141
|
+
- lib/view_component/form/helpers/rails.rb
|
142
|
+
- lib/view_component/form/helpers/rails_7_backports.rb
|
143
|
+
- lib/view_component/form/renderer.rb
|
139
144
|
- lib/view_component/form/test_helpers.rb
|
145
|
+
- lib/view_component/form/validation_context.rb
|
140
146
|
- lib/view_component/form/version.rb
|
141
147
|
homepage: https://github.com/pantographe/view_component-form
|
142
148
|
licenses:
|
@@ -162,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
168
|
- !ruby/object:Gem::Version
|
163
169
|
version: '0'
|
164
170
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
171
|
+
rubygems_version: 3.4.10
|
166
172
|
signing_key:
|
167
173
|
specification_version: 4
|
168
174
|
summary: Rails FormBuilder for ViewComponent
|