view_component-form 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|