view_component-form 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/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/app/components/view_component/form/base_component.rb +53 -0
- data/app/components/view_component/form/button_component.rb +19 -0
- data/app/components/view_component/form/check_box_component.rb +27 -0
- data/app/components/view_component/form/collection_check_boxes_component.rb +50 -0
- data/app/components/view_component/form/collection_radio_buttons_component.rb +50 -0
- data/app/components/view_component/form/collection_select_component.rb +49 -0
- data/app/components/view_component/form/color_field_component.rb +9 -0
- data/app/components/view_component/form/date_field_component.rb +9 -0
- data/app/components/view_component/form/date_select_component.rb +34 -0
- data/app/components/view_component/form/datetime_field_component.rb +9 -0
- data/app/components/view_component/form/datetime_local_field_component.rb +9 -0
- data/app/components/view_component/form/datetime_select_component.rb +34 -0
- data/app/components/view_component/form/email_field_component.rb +9 -0
- data/app/components/view_component/form/field_component.rb +65 -0
- data/app/components/view_component/form/file_field_component.rb +9 -0
- data/app/components/view_component/form/grouped_collection_select_component.rb +57 -0
- data/app/components/view_component/form/label_component.rb +42 -0
- data/app/components/view_component/form/month_field_component.rb +9 -0
- data/app/components/view_component/form/number_field_component.rb +9 -0
- data/app/components/view_component/form/password_field_component.rb +9 -0
- data/app/components/view_component/form/radio_button_component.rb +27 -0
- data/app/components/view_component/form/range_field_component.rb +9 -0
- data/app/components/view_component/form/rich_text_area_component.rb +9 -0
- data/app/components/view_component/form/search_field_component.rb +9 -0
- data/app/components/view_component/form/select_component.rb +37 -0
- data/app/components/view_component/form/submit_component.rb +19 -0
- data/app/components/view_component/form/telephone_field_component.rb +9 -0
- data/app/components/view_component/form/text_area_component.rb +9 -0
- data/app/components/view_component/form/text_field_component.rb +9 -0
- data/app/components/view_component/form/time_field_component.rb +9 -0
- data/app/components/view_component/form/time_select_component.rb +34 -0
- data/app/components/view_component/form/time_zone_select_component.rb +36 -0
- data/app/components/view_component/form/url_field_component.rb +9 -0
- data/app/components/view_component/form/week_field_component.rb +9 -0
- data/lib/generators/vcf/builder/builder_generator.rb +32 -0
- data/lib/generators/vcf/builder/templates/builder.rb.erb +4 -0
- data/lib/view_component/form/builder.rb +212 -0
- data/lib/view_component/form/class_names_helper.rb +37 -0
- data/lib/view_component/form/engine.rb +15 -0
- data/lib/view_component/form/test_helpers.rb +29 -0
- data/lib/view_component/form/version.rb +7 -0
- data/lib/view_component/form.rb +5 -0
- metadata +152 -0
@@ -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,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,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
|