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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e34a41ebc599bd19cfcb44df6531abea9f69d6e411654bd30f51214f5d9b5689
4
- data.tar.gz: 481a67aec41fb2ee179d9123f4140385a848ba2d1d92f0de0fdebb4eb8a07a8a
3
+ metadata.gz: 128d4c769f1fa3e25cf83830db0b8279413dd8da3e9c7e312aeee06f0456336a
4
+ data.tar.gz: 63d592e4fa33d6bb77262bea0526761d5b251f26f37c6fac42a771f2506b3250
5
5
  SHA512:
6
- metadata.gz: 1e7e7d6496ce356fd2409cd67822187d0b7619d9d56ae6c5172ef26dcfbb8f65af9c9bfe4025d5c5bf2b01ceb784bd6ef97263e508ac50e1a732d9bbf670ac97
7
- data.tar.gz: 449e48d08d88438c70be0888074f68871dce0ebbc20b7bcfa45d35526c60e3dd30b0c32a7114cc54574969505469eb1e3b013836c69b92b10edeb33bdd6305b9
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
- Nothing yet
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 ruby 3.1 to CI (#123)
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.3...HEAD
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 lib/custom_form_builder.rb
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
- # lib/custom_form_builder.rb
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, update the version number in `version.rb`, and then run `bundle exec rake release`, which will 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).
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 nil if object.nil?
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 nil if object.nil?
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: "lib"
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
- class Error < StandardError; end
7
-
8
- class NotImplementedComponentError < Error; end
9
-
10
- class NamespaceAlreadyAddedError < Error; end
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
@@ -6,6 +6,7 @@ module ViewComponent
6
6
  class Engine < ::Rails::Engine
7
7
  config.autoload_once_paths = %W[
8
8
  #{root}/app/components
9
+ #{root}/app/components/concerns
9
10
  #{root}/app/lib
10
11
  ]
11
12
  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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ViewComponent
4
4
  module Form
5
- VERSION = "0.2.4"
5
+ VERSION = "0.2.6"
6
6
  end
7
7
  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
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: 2022-04-27 00:00:00.000000000 Z
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.1'
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.1'
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.1'
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.1'
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: '3.0'
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: '3.0'
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.3.12
171
+ rubygems_version: 3.4.10
166
172
  signing_key:
167
173
  specification_version: 4
168
174
  summary: Rails FormBuilder for ViewComponent