view_component-form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +170 -0
  5. data/app/components/view_component/form/base_component.rb +53 -0
  6. data/app/components/view_component/form/button_component.rb +19 -0
  7. data/app/components/view_component/form/check_box_component.rb +27 -0
  8. data/app/components/view_component/form/collection_check_boxes_component.rb +50 -0
  9. data/app/components/view_component/form/collection_radio_buttons_component.rb +50 -0
  10. data/app/components/view_component/form/collection_select_component.rb +49 -0
  11. data/app/components/view_component/form/color_field_component.rb +9 -0
  12. data/app/components/view_component/form/date_field_component.rb +9 -0
  13. data/app/components/view_component/form/date_select_component.rb +34 -0
  14. data/app/components/view_component/form/datetime_field_component.rb +9 -0
  15. data/app/components/view_component/form/datetime_local_field_component.rb +9 -0
  16. data/app/components/view_component/form/datetime_select_component.rb +34 -0
  17. data/app/components/view_component/form/email_field_component.rb +9 -0
  18. data/app/components/view_component/form/field_component.rb +65 -0
  19. data/app/components/view_component/form/file_field_component.rb +9 -0
  20. data/app/components/view_component/form/grouped_collection_select_component.rb +57 -0
  21. data/app/components/view_component/form/label_component.rb +42 -0
  22. data/app/components/view_component/form/month_field_component.rb +9 -0
  23. data/app/components/view_component/form/number_field_component.rb +9 -0
  24. data/app/components/view_component/form/password_field_component.rb +9 -0
  25. data/app/components/view_component/form/radio_button_component.rb +27 -0
  26. data/app/components/view_component/form/range_field_component.rb +9 -0
  27. data/app/components/view_component/form/rich_text_area_component.rb +9 -0
  28. data/app/components/view_component/form/search_field_component.rb +9 -0
  29. data/app/components/view_component/form/select_component.rb +37 -0
  30. data/app/components/view_component/form/submit_component.rb +19 -0
  31. data/app/components/view_component/form/telephone_field_component.rb +9 -0
  32. data/app/components/view_component/form/text_area_component.rb +9 -0
  33. data/app/components/view_component/form/text_field_component.rb +9 -0
  34. data/app/components/view_component/form/time_field_component.rb +9 -0
  35. data/app/components/view_component/form/time_select_component.rb +34 -0
  36. data/app/components/view_component/form/time_zone_select_component.rb +36 -0
  37. data/app/components/view_component/form/url_field_component.rb +9 -0
  38. data/app/components/view_component/form/week_field_component.rb +9 -0
  39. data/lib/generators/vcf/builder/builder_generator.rb +32 -0
  40. data/lib/generators/vcf/builder/templates/builder.rb.erb +4 -0
  41. data/lib/view_component/form/builder.rb +212 -0
  42. data/lib/view_component/form/class_names_helper.rb +37 -0
  43. data/lib/view_component/form/engine.rb +15 -0
  44. data/lib/view_component/form/test_helpers.rb +29 -0
  45. data/lib/view_component/form/version.rb +7 -0
  46. data/lib/view_component/form.rb +5 -0
  47. metadata +152 -0
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TelephoneFieldComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::TelField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TextAreaComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::TextArea
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TextFieldComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::TextField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TimeFieldComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::TimeField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TimeSelectComponent < FieldComponent
6
+ attr_reader :html_options
7
+
8
+ def initialize(form, object_name, method_name, options = {}, html_options = {})
9
+ @html_options = html_options
10
+
11
+ super(form, object_name, method_name, options)
12
+
13
+ set_html_options!
14
+ end
15
+
16
+ def call
17
+ ActionView::Helpers::Tags::TimeSelect.new(
18
+ object_name,
19
+ method_name,
20
+ @view_context,
21
+ options,
22
+ html_options
23
+ ).render
24
+ end
25
+
26
+ protected
27
+
28
+ def set_html_options!
29
+ @html_options[:class] = class_names(html_options[:class], html_class)
30
+ @html_options.delete(:class) if @html_options[:class].blank?
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class TimeZoneSelectComponent < FieldComponent
6
+ attr_reader :priority_zones, :html_options
7
+
8
+ def initialize(form, object_name, method_name, priority_zones, options = {}, html_options = {}) # rubocop:disable Metrics/ParameterLists
9
+ @priority_zones = priority_zones
10
+ @html_options = html_options
11
+
12
+ super(form, object_name, method_name, options)
13
+
14
+ set_html_options!
15
+ end
16
+
17
+ def call
18
+ ActionView::Helpers::Tags::TimeZoneSelect.new(
19
+ object_name,
20
+ method_name,
21
+ @view_context,
22
+ priority_zones,
23
+ options,
24
+ html_options
25
+ ).render
26
+ end
27
+
28
+ protected
29
+
30
+ def set_html_options!
31
+ @html_options[:class] = class_names(html_options[:class], html_class)
32
+ @html_options.delete(:class) if @html_options[:class].blank?
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class UrlFieldComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::UrlField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Form
5
+ class WeekFieldComponent < FieldComponent
6
+ self.tag_klass = ActionView::Helpers::Tags::WeekField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Vcf
6
+ module Generators
7
+ class BuilderGenerator < Rails::Generators::NamedBase
8
+ source_root File.join(File.dirname(__FILE__), "templates")
9
+
10
+ class_option :namespace, default: "Form"
11
+ class_option :path, default: "lib"
12
+
13
+ def create_builder_from_template
14
+ template "builder.rb.erb", destination
15
+ end
16
+
17
+ protected
18
+
19
+ def class_name
20
+ name.camelize
21
+ end
22
+
23
+ def components_namespace
24
+ options[:namespace].camelize
25
+ end
26
+
27
+ def destination
28
+ "#{options[:path]}/#{name.underscore}.rb"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ class <%= class_name %> < ViewComponent::Form::Builder
2
+ # Set the namespace you want to use for your own components
3
+ namespace "<%= components_namespace %>"
4
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+
5
+ module ViewComponent
6
+ module Form
7
+ class Builder < ActionView::Helpers::FormBuilder
8
+ class Error < StandardError; end
9
+
10
+ class NotImplementedComponentError < Error; end
11
+
12
+ class NamespaceAlreadyAddedError < Error; end
13
+
14
+ class_attribute :lookup_namespaces, default: [ViewComponent::Form]
15
+
16
+ class << self
17
+ def inherited(base)
18
+ base.lookup_namespaces = lookup_namespaces.dup
19
+
20
+ super
21
+ end
22
+
23
+ def namespace(namespace)
24
+ if lookup_namespaces.include?(namespace)
25
+ raise NamespaceAlreadyAddedError, "The component namespace '#{namespace}' is already added"
26
+ end
27
+
28
+ lookup_namespaces.prepend namespace
29
+ end
30
+ end
31
+
32
+ def initialize(*)
33
+ @__component_klass_cache = {}
34
+
35
+ super
36
+ end
37
+
38
+ (field_helpers - %i[label check_box radio_button fields_for fields hidden_field file_field]).each do |selector|
39
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
40
+ def #{selector}(method, options = {}) # def text_field(method, options = {})
41
+ render_component( # render_component(
42
+ :#{selector}, # :text_field,
43
+ @object_name, # @object_name,
44
+ method, # method,
45
+ objectify_options(options), # objectify_options(options),
46
+ ) # )
47
+ end # end
48
+ RUBY_EVAL
49
+ end
50
+
51
+ # See: https://github.com/rails/rails/blob/33d60cb02dcac26d037332410eabaeeb0bdc384c/actionview/lib/action_view/helpers/form_helper.rb#L2280
52
+ def label(method, text = nil, options = {}, &block)
53
+ render_component(:label, @object_name, method, text, objectify_options(options), &block)
54
+ end
55
+
56
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
57
+ render_component(
58
+ :check_box, @object_name, method, checked_value, unchecked_value, objectify_options(options)
59
+ )
60
+ end
61
+
62
+ def radio_button(method, tag_value, options = {})
63
+ render_component(
64
+ :radio_button, @object_name, method, tag_value, objectify_options(options)
65
+ )
66
+ end
67
+
68
+ def file_field(method, options = {})
69
+ self.multipart = true
70
+ render_component(:file_field, @object_name, method, objectify_options(options))
71
+ end
72
+
73
+ def submit(value = nil, options = {})
74
+ if value.is_a?(Hash)
75
+ options = value
76
+ value = nil
77
+ end
78
+ value ||= submit_default_value
79
+ render_component(:submit, value, options)
80
+ end
81
+
82
+ def button(value = nil, options = {}, &block)
83
+ if value.is_a?(Hash)
84
+ options = value
85
+ value = nil
86
+ end
87
+ value ||= submit_default_value
88
+ render_component(:button, value, options, &block)
89
+ end
90
+
91
+ # SELECTORS.each do |selector|
92
+ # class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
93
+ # def #{selector}(*args)
94
+ # render_component(
95
+ # :#{selector},
96
+ # *args,
97
+ # super,
98
+ # )
99
+ # end
100
+ # RUBY_EVAL
101
+ # end
102
+
103
+ # See: https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/actionview/lib/action_view/helpers/form_options_helper.rb
104
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
105
+ render_component(
106
+ :select, @object_name, method, choices, objectify_options(options),
107
+ @default_html_options.merge(html_options), &block
108
+ )
109
+ end
110
+
111
+ # rubocop:disable Metrics/ParameterLists
112
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
113
+ render_component(
114
+ :collection_select, @object_name, method, collection, value_method, text_method,
115
+ objectify_options(options), @default_html_options.merge(html_options)
116
+ )
117
+ end
118
+
119
+ def grouped_collection_select(
120
+ method, collection,
121
+ group_method, group_label_method, option_key_method, option_value_method,
122
+ options = {}, html_options = {}
123
+ )
124
+ render_component(
125
+ :grouped_collection_select, @object_name, method, collection, group_method,
126
+ group_label_method, option_key_method, option_value_method,
127
+ objectify_options(options), @default_html_options.merge(html_options)
128
+ )
129
+ end
130
+
131
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
132
+ render_component(
133
+ :collection_check_boxes, @object_name, method, collection, value_method, text_method,
134
+ objectify_options(options), @default_html_options.merge(html_options), &block
135
+ )
136
+ end
137
+
138
+ def collection_radio_buttons(
139
+ method, collection,
140
+ value_method, text_method,
141
+ options = {}, html_options = {},
142
+ &block
143
+ )
144
+ render_component(
145
+ :collection_radio_buttons, @object_name, method, collection, value_method, text_method,
146
+ objectify_options(options), @default_html_options.merge(html_options), &block
147
+ )
148
+ end
149
+ # rubocop:enable Metrics/ParameterLists
150
+
151
+ def date_select(method, options = {}, html_options = {})
152
+ render_component(
153
+ :date_select, @object_name, method,
154
+ objectify_options(options), @default_html_options.merge(html_options)
155
+ )
156
+ end
157
+
158
+ def datetime_select(method, options = {}, html_options = {})
159
+ render_component(
160
+ :datetime_select, @object_name, method,
161
+ objectify_options(options), @default_html_options.merge(html_options)
162
+ )
163
+ end
164
+
165
+ def time_select(method, options = {}, html_options = {})
166
+ render_component(
167
+ :time_select, @object_name, method,
168
+ objectify_options(options), @default_html_options.merge(html_options)
169
+ )
170
+ end
171
+
172
+ def time_zone_select(method, options = {}, html_options = {})
173
+ render_component(
174
+ :time_zone_select, @object_name, method,
175
+ objectify_options(options), @default_html_options.merge(html_options)
176
+ )
177
+ end
178
+
179
+ if defined?(ActionView::Helpers::Tags::ActionText)
180
+ def rich_text_area(method, options = {})
181
+ render_component(:rich_text_area, @object_name, method, objectify_options(options))
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def render_component(component_name, *args, &block)
188
+ component = component_klass(component_name).new(self, *args)
189
+ component.render_in(@template, &block)
190
+ end
191
+
192
+ def objectify_options(options)
193
+ @default_options.merge(options.merge(object: @object))
194
+ end
195
+
196
+ def component_klass(component_name)
197
+ @__component_klass_cache[component_name] ||= begin
198
+ component_klass = self.class.lookup_namespaces.filter_map do |namespace|
199
+ "#{namespace}::#{component_name.to_s.camelize}Component".safe_constantize || false
200
+ end.first
201
+
202
+ unless component_klass.is_a?(Class) && component_klass < ViewComponent::Base
203
+ raise NotImplementedComponentError, "Component named #{component_name} doesn't exist" \
204
+ " or is not a ViewComponent::Base class"
205
+ end
206
+
207
+ component_klass
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Backport of https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-class_names
4
+ module ViewComponent
5
+ module Form
6
+ module ClassNamesHelper
7
+ # rubocop:disable Metrics/CyclomaticComplexity
8
+ # rubocop:disable Metrics/MethodLength
9
+ def build_tag_values(*args)
10
+ tag_values = []
11
+
12
+ args.each do |tag_value|
13
+ case tag_value
14
+ when Hash
15
+ tag_value.each do |key, val|
16
+ tag_values << key.to_s if val && key.present?
17
+ end
18
+ when Array
19
+ tag_values.concat build_tag_values(*tag_value)
20
+ else
21
+ tag_values << tag_value.to_s if tag_value.present?
22
+ end
23
+ end
24
+
25
+ tag_values
26
+ end
27
+ # rubocop:enable Metrics/CyclomaticComplexity
28
+ # rubocop:enable Metrics/MethodLength
29
+
30
+ def class_names(*args)
31
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
32
+
33
+ safe_join(tokens, " ")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+
5
+ module ViewComponent
6
+ module Form
7
+ # :nodoc:
8
+ class Engine < ::Rails::Engine
9
+ config.autoload_once_paths = %W[
10
+ #{root}/app/components
11
+ #{root}/app/lib
12
+ ]
13
+ end
14
+ end
15
+ end