form_props 0.0.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +714 -0
  3. data/lib/form_props/action_view_extensions/form_helper.rb +120 -0
  4. data/lib/form_props/form_builder.rb +223 -0
  5. data/lib/form_props/form_options_helper.rb +158 -0
  6. data/lib/form_props/inputs/base.rb +164 -0
  7. data/lib/form_props/inputs/check_box.rb +69 -0
  8. data/lib/form_props/inputs/collection_check_boxes.rb +36 -0
  9. data/lib/form_props/inputs/collection_helpers.rb +53 -0
  10. data/lib/form_props/inputs/collection_radio_buttons.rb +35 -0
  11. data/lib/form_props/inputs/collection_select.rb +30 -0
  12. data/lib/form_props/inputs/color_field.rb +27 -0
  13. data/lib/form_props/inputs/date_field.rb +17 -0
  14. data/lib/form_props/inputs/datetime_field.rb +32 -0
  15. data/lib/form_props/inputs/datetime_local_field.rb +17 -0
  16. data/lib/form_props/inputs/email_field.rb +13 -0
  17. data/lib/form_props/inputs/file_field.rb +13 -0
  18. data/lib/form_props/inputs/grouped_collection_select.rb +31 -0
  19. data/lib/form_props/inputs/hidden_field.rb +16 -0
  20. data/lib/form_props/inputs/month_field.rb +17 -0
  21. data/lib/form_props/inputs/number_field.rb +21 -0
  22. data/lib/form_props/inputs/password_field.rb +18 -0
  23. data/lib/form_props/inputs/radio_button.rb +48 -0
  24. data/lib/form_props/inputs/range_field.rb +13 -0
  25. data/lib/form_props/inputs/search_field.rb +28 -0
  26. data/lib/form_props/inputs/select.rb +42 -0
  27. data/lib/form_props/inputs/submit.rb +26 -0
  28. data/lib/form_props/inputs/tel_field.rb +13 -0
  29. data/lib/form_props/inputs/text_area.rb +37 -0
  30. data/lib/form_props/inputs/text_field.rb +28 -0
  31. data/lib/form_props/inputs/time_field.rb +17 -0
  32. data/lib/form_props/inputs/time_zone_select.rb +22 -0
  33. data/lib/form_props/inputs/url_field.rb +13 -0
  34. data/lib/form_props/inputs/week_field.rb +17 -0
  35. data/lib/form_props/inputs/weekday_select.rb +28 -0
  36. data/lib/form_props/version.rb +5 -0
  37. data/lib/form_props.rb +45 -0
  38. metadata +120 -0
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module ActionViewExtensions
5
+ module FormHelper
6
+ def form_props(model: nil, scope: nil, url: nil, format: nil, **options, &block)
7
+ json = @__json
8
+
9
+ options = {
10
+ allow_method_names_outside_object: true,
11
+ controlled: false
12
+ }.merge!(options)
13
+
14
+ options.delete(:remote)
15
+
16
+ options[:local] = true
17
+ options[:skip_default_ids] = false
18
+
19
+ if model
20
+ if url != false
21
+ url ||= if format.nil?
22
+ polymorphic_path(model, {})
23
+ else
24
+ polymorphic_path(model, format: format)
25
+ end
26
+ end
27
+
28
+ model = convert_to_model(_object_for_form_builder(model))
29
+ scope ||= model_name_from_record_or_class(model).param_key
30
+ end
31
+
32
+ if block
33
+ options[:builder] = FormProps::FormBuilder
34
+
35
+ builder = instantiate_builder(scope, model, options)
36
+ json.inputs do
37
+ capture(builder, &block)
38
+ end
39
+ options[:multipart] ||= builder.multipart?
40
+ end
41
+
42
+ html_options = html_options_for_form_with(url, model, **options)
43
+
44
+ json.extras do
45
+ extra_props_for_form(json, html_options)
46
+ end
47
+ json.props(html_options)
48
+ end
49
+
50
+ private
51
+
52
+ def token_props(json, token = nil, form_options: {})
53
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
54
+ token =
55
+ if token == true || token.nil?
56
+ form_authenticity_token(form_options: form_options.merge(authenticity_token: token))
57
+ else
58
+ token
59
+ end
60
+
61
+ json.set!("csrf") do
62
+ json.name request_forgery_protection_token.to_s
63
+ json.type "hidden"
64
+ json.default_value token
65
+ json.autocomplete "off"
66
+ end
67
+ end
68
+ end
69
+
70
+ def method_props(json, method)
71
+ json.set!("method") do
72
+ json.name "_method"
73
+ json.type "hidden"
74
+ json.default_value method.to_s
75
+ json.autocomplete "off"
76
+ end
77
+ end
78
+
79
+ def utf8_enforcer_props(json)
80
+ json.set!("utf8") do
81
+ json.name "utf8"
82
+ json.type "hidden"
83
+ json.default_value "✓"
84
+ json.autocomplete "off"
85
+ end
86
+ end
87
+
88
+ def extra_props_for_form(json, html_options)
89
+ authenticity_token = html_options.delete("authenticity_token")
90
+ method = html_options.delete("method").to_s.downcase
91
+
92
+ case method
93
+ when "get"
94
+ html_options["method"] = "get"
95
+ when "post", ""
96
+ html_options["method"] = "post"
97
+ token_props(json, authenticity_token, form_options: {
98
+ action: html_options["action"],
99
+ method: "post"
100
+ })
101
+ else
102
+ html_options["method"] = "post"
103
+ method_props(json, method)
104
+ token_props(json, authenticity_token, form_options: {
105
+ action: html_options["action"],
106
+ method: method
107
+ })
108
+ end
109
+
110
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
111
+ utf8_enforcer_props(json)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ ActiveSupport.on_load(:action_view) do
119
+ include FormProps::ActionViewExtensions::FormHelper
120
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ undef_method :button,
6
+ :label,
7
+ :datetime_select,
8
+ :time_select,
9
+ :date_select
10
+ # :rich_text_area
11
+
12
+ def initialize(*args)
13
+ super
14
+ options = args.last || {}
15
+ @default_options[:controlled] = options[:controlled]
16
+ @default_html_options = @default_html_options.except(:controlled)
17
+ end
18
+
19
+ def file_field(method, options = {})
20
+ Inputs::FileField.new(
21
+ @object_name,
22
+ method,
23
+ @template,
24
+ @template.send(:convert_direct_upload_option_to_url, objectify_options(options).dup)
25
+ ).render
26
+ end
27
+
28
+ def select(method, choices = [], options = {}, html_options = {})
29
+ Inputs::Select.new(@object_name, method, @template, choices, objectify_options(options), @default_html_options.merge(html_options)).render
30
+ end
31
+
32
+ def hidden_field(method, options = {})
33
+ Inputs::HiddenField.new(@object_name, method, @template, objectify_options(options)).render
34
+ end
35
+
36
+ def text_field(method, options = {})
37
+ Inputs::TextField.new(@object_name, method, @template, objectify_options(options)).render
38
+ end
39
+
40
+ def date_field(method, options = {})
41
+ Inputs::DateField.new(@object_name, method, @template, objectify_options(options)).render
42
+ end
43
+
44
+ def time_field(method, options = {})
45
+ Inputs::TimeField.new(@object_name, method, @template, objectify_options(options)).render
46
+ end
47
+
48
+ def week_field(method, options = {})
49
+ Inputs::WeekField.new(@object_name, method, @template, objectify_options(options)).render
50
+ end
51
+
52
+ def month_field(method, options = {})
53
+ Inputs::MonthField.new(@object_name, method, @template, objectify_options(options)).render
54
+ end
55
+
56
+ def datetime_field(method, options = {})
57
+ Inputs::DatetimeLocalField.new(@object_name, method, @template, objectify_options(options)).render
58
+ end
59
+
60
+ def datetime_local_field(method, options = {})
61
+ Inputs::DatetimeLocalField.new(@object_name, method, @template, objectify_options(options)).render
62
+ end
63
+
64
+ def url_field(method, options = {})
65
+ Inputs::UrlField.new(@object_name, method, @template, objectify_options(options)).render
66
+ end
67
+
68
+ def tel_field(method, options = {})
69
+ Inputs::TelField.new(@object_name, method, @template, objectify_options(options)).render
70
+ end
71
+
72
+ def color_field(method, options = {})
73
+ Inputs::ColorField.new(@object_name, method, @template, objectify_options(options)).render
74
+ end
75
+
76
+ def password_field(method, options = {})
77
+ Inputs::PasswordField.new(@object_name, method, @template, objectify_options(options)).render
78
+ end
79
+
80
+ def number_field(method, options = {})
81
+ Inputs::NumberField.new(@object_name, method, @template, objectify_options(options)).render
82
+ end
83
+
84
+ def range_field(method, options = {})
85
+ Inputs::RangeField.new(@object_name, method, @template, objectify_options(options)).render
86
+ end
87
+
88
+ def email_field(method, options = {})
89
+ Inputs::EmailField.new(@object_name, method, @template, objectify_options(options)).render
90
+ end
91
+
92
+ def search_field(method, options = {})
93
+ Inputs::SearchField.new(@object_name, method, @template, objectify_options(options)).render
94
+ end
95
+
96
+ def text_area(method, options = {})
97
+ Inputs::TextArea.new(@object_name, method, @template, objectify_options(options)).render
98
+ end
99
+
100
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
101
+ Inputs::CheckBox.new(@object_name, method, @template, checked_value, unchecked_value, objectify_options(options)).render
102
+ end
103
+
104
+ def radio_button(method, tag_value, options = {})
105
+ Inputs::RadioButton.new(@object_name, method, @template, tag_value, options).render
106
+ end
107
+
108
+ def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
109
+ Inputs::CollectionSelect.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options)).render
110
+ end
111
+
112
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
113
+ Inputs::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block).render
114
+ end
115
+
116
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
117
+ Inputs::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block).render
118
+ end
119
+
120
+ def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
121
+ Inputs::GroupedCollectionSelect.new(@object_name, method, @template, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options)).render
122
+ end
123
+
124
+ def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
125
+ Inputs::TimeZoneSelect.new(@object_name, method, @template, priority_zones, objectify_options(options), @default_html_options.merge(html_options)).render
126
+ end
127
+
128
+ def weekday_select(method, options = {}, html_options = {})
129
+ Inputs::WeekdaySelect.new(@object_name, method, @template, objectify_options(options), @default_html_options.merge(html_options)).render
130
+ end
131
+
132
+ def submit(value = nil, options = {})
133
+ value, options = nil, value if value.is_a?(Hash)
134
+ value ||= submit_default_value
135
+ options = {name: "commit", value: value}.update(options)
136
+
137
+ Inputs::Submit.new(@template, options).render
138
+ end
139
+
140
+ def fields_for_with_nested_attributes(association_name, association, options, block)
141
+ name = "#{object_name}[#{association_name}_attributes]"
142
+ association = convert_to_model(association)
143
+
144
+ if association.respond_to?(:persisted?)
145
+ association = [association] if @object.public_send(association_name).respond_to?(:to_ary)
146
+ elsif !association.respond_to?(:to_ary)
147
+ association = @object.public_send(association_name)
148
+ end
149
+
150
+ if association.respond_to?(:to_ary)
151
+ explicit_child_index = options[:child_index]
152
+
153
+ @template.json.set!("#{association_name}_attributes") do
154
+ @template.json.array! association do |child|
155
+ if explicit_child_index
156
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
157
+ else
158
+ options[:child_index] = nested_child_index(name)
159
+ end
160
+
161
+ fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
162
+ end
163
+ end
164
+ elsif association
165
+ @template.json.set!("#{association_name}_attributes") do
166
+ fields_for_nested_model(name, association, options, block)
167
+ end
168
+ end
169
+ end
170
+
171
+ def fields_for_nested_model(name, object, fields_options, block)
172
+ object = convert_to_model(object)
173
+ emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) {
174
+ options.fetch(:include_id, true)
175
+ }
176
+
177
+ @template.fields_for(name, object, fields_options) do |f|
178
+ block.call(f)
179
+ f.hidden_field(:id) if emit_hidden_id && !f.emitted_hidden_id?
180
+ end
181
+ end
182
+
183
+ def fields_for(record_name, record_object = nil, fields_options = {}, &block)
184
+ fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
185
+ fields_options[:builder] ||= self.class
186
+ fields_options[:namespace] = options[:namespace]
187
+ fields_options[:parent_builder] = self
188
+
189
+ case record_name
190
+ when String, Symbol
191
+ if nested_attributes_association?(record_name)
192
+ return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
193
+ end
194
+ else
195
+ record_object = record_name.is_a?(Array) ? record_name.last : record_name
196
+ record_name = model_name_from_record_or_class(record_object).param_key
197
+ end
198
+
199
+ object_name = @object_name
200
+ index = if options.has_key?(:index)
201
+ options[:index]
202
+ elsif defined?(@auto_index)
203
+ object_name = object_name.to_s.delete_suffix("[]")
204
+ @auto_index
205
+ end
206
+
207
+ record_name = if index
208
+ "#{object_name}[#{index}][#{record_name}]"
209
+ elsif record_name.end_with?("[]")
210
+ "#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
211
+ else
212
+ "#{object_name}[#{record_name}]"
213
+ end
214
+ fields_options[:child_index] = index
215
+
216
+ @template.fields_for(record_name, record_object, fields_options, &block)
217
+ end
218
+
219
+ def default_form_builder_class
220
+ self.class
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module FormOptionsHelper
5
+ def selected_values
6
+ @selected_values ||= []
7
+ @selected_values
8
+ end
9
+
10
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
11
+ prompt = options[:prompt]
12
+ divider = options[:divider]
13
+
14
+ options = []
15
+
16
+ if prompt
17
+ options.push({
18
+ label: prompt_text(prompt),
19
+ value: ""
20
+ })
21
+ end
22
+
23
+ grouped_options.each do |container|
24
+ html_attributes = option_html_attributes(container)
25
+
26
+ if divider
27
+ label = divider
28
+ else
29
+ label, container = container
30
+ end
31
+
32
+ options.push({label: label, options: options_for_select(container, selected_key)}
33
+ .merge!(html_attributes))
34
+ end
35
+
36
+ options
37
+ end
38
+
39
+ def option_html_attributes(element)
40
+ if Array === element
41
+ element.select { |e| Hash === e }.reduce({}, :merge!)
42
+ else
43
+ {}
44
+ end
45
+ end
46
+
47
+ def options_for_select(container, selected = nil)
48
+ selected, disabled = extract_selected_and_disabled(selected).map do |r|
49
+ Array(r).map(&:to_s)
50
+ end
51
+
52
+ container.map do |element|
53
+ html_attributes = option_html_attributes(element)
54
+ text, value = option_text_and_value(element).map(&:to_s)
55
+
56
+ if !html_attributes[:selected] && option_value_selected?(value, selected)
57
+ selected_values.push(value)
58
+ end
59
+
60
+ if html_attributes[:selected]
61
+ selected_values.push(value)
62
+ end
63
+
64
+ if !html_attributes[:disabled] && (disabled && option_value_selected?(value, disabled))
65
+ html_attributes[:disabled] = true
66
+ end
67
+
68
+ html_attributes[:value] = value
69
+ html_attributes[:label] = text
70
+
71
+ html_attributes
72
+ end
73
+ end
74
+
75
+ def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
76
+ collection.map do |group|
77
+ option_tags = options_from_collection_for_select(
78
+ value_for_collection(group, group_method),
79
+ option_key_method,
80
+ option_value_method,
81
+ selected_key
82
+ )
83
+
84
+ {
85
+ options: option_tags,
86
+ label: value_for_collection(group, group_label_method)
87
+ }
88
+ end
89
+ end
90
+
91
+ def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
92
+ options = collection.map do |element|
93
+ [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
94
+ end
95
+ selected, disabled = extract_selected_and_disabled(selected)
96
+ select_deselect = {
97
+ selected: extract_values_from_collection(collection, value_method, selected),
98
+ disabled: extract_values_from_collection(collection, value_method, disabled)
99
+ }
100
+
101
+ options_for_select(options, select_deselect)
102
+ end
103
+
104
+ def value_for_collection(item, value)
105
+ value.respond_to?(:call) ? value.call(item) : item.public_send(value)
106
+ end
107
+
108
+ def extract_selected_and_disabled(selected)
109
+ if selected.is_a?(Proc)
110
+ [selected, nil]
111
+ else
112
+ selected = Array.wrap(selected)
113
+ options = selected.extract_options!.symbolize_keys
114
+ selected_items = options.fetch(:selected, selected)
115
+ [selected_items, options[:disabled]]
116
+ end
117
+ end
118
+
119
+ def extract_values_from_collection(collection, value_method, selected)
120
+ if selected.is_a?(Proc)
121
+ collection.map do |element|
122
+ element.public_send(value_method) if selected.call(element)
123
+ end.compact
124
+ else
125
+ selected
126
+ end
127
+ end
128
+
129
+ def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
130
+ zone_options = []
131
+
132
+ zones = model.all
133
+ convert_zones = lambda { |list| list.map { |z| [z.to_s, z.name] } }
134
+
135
+ if priority_zones
136
+ if priority_zones.is_a?(Regexp)
137
+ priority_zones = zones.select { |z| z.match?(priority_zones) }
138
+ end
139
+
140
+ zone_options.concat(options_for_select(convert_zones[priority_zones], selected))
141
+ zone_options.push({label: "-------------", value: "", disabled: true})
142
+
143
+ zones -= priority_zones
144
+ end
145
+
146
+ zone_options.concat(options_for_select(convert_zones[zones], selected))
147
+ zone_options
148
+ end
149
+
150
+ def weekday_options_for_select(selected = nil, index_as_value: false, day_format: :day_names, beginning_of_week: Date.beginning_of_week)
151
+ day_names = I18n.translate("date.#{day_format}")
152
+ day_names = day_names.map.with_index.to_a if index_as_value
153
+ day_names = day_names.rotate(Date::DAYS_INTO_WEEK.fetch(beginning_of_week))
154
+
155
+ options_for_select(day_names, selected)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class Base < ::ActionView::Helpers::Tags::Base
6
+ def json
7
+ @json ||= @template_object.instance_variable_get(:@__json)
8
+ end
9
+
10
+ def initialize(object_name, method_name, template_object, options = {})
11
+ options = options.with_indifferent_access
12
+
13
+ @controlled = options.delete(:controlled)
14
+ super(object_name, method_name, template_object, options)
15
+ end
16
+
17
+ private
18
+
19
+ def add_options(option_tags, options, value = nil)
20
+ if options[:include_blank]
21
+ content = (options[:include_blank] if options[:include_blank].is_a?(String))
22
+ label = (" " unless content)
23
+ option_tags = [{label: content || label, value: ""}] + option_tags
24
+ end
25
+
26
+ if value.blank? && options[:prompt]
27
+ tag_options = {value: ""}.tap do |prompt_opts|
28
+ prompt_opts[:disabled] = true if options[:disabled] == ""
29
+ if options[:selected] == ""
30
+ selected_values.push("")
31
+ end
32
+ prompt_opts[:label] = prompt_text(options[:prompt])
33
+ end
34
+ option_tags = [tag_options] + option_tags
35
+ end
36
+
37
+ option_tags
38
+ end
39
+
40
+ def input_props(options)
41
+ return if options.blank?
42
+
43
+ options.each_pair do |key, value|
44
+ type = TAG_TYPES[key]
45
+
46
+ if type == :data && value.is_a?(Hash)
47
+ value.each_pair do |k, v|
48
+ next if v.nil?
49
+ prefix_tag_props(key, k, v)
50
+ end
51
+ elsif type == :aria && value.is_a?(Hash)
52
+ value.each_pair do |k, v|
53
+ next if v.nil?
54
+
55
+ case v
56
+ when Array, Hash
57
+ tokens = build_values(v)
58
+ next if tokens.none?
59
+
60
+ v = safe_join(tokens, " ")
61
+ else
62
+ v = v.to_s
63
+ end
64
+
65
+ prefix_tag_props(key, k, v)
66
+ end
67
+ elsif key == "class" || key == :class
68
+ value = build_values(value).join(" ")
69
+ key = "class_name"
70
+ tag_option(key, value)
71
+ elsif !value.nil?
72
+ if key.to_s == "value"
73
+ key = key.to_s
74
+ value = value.is_a?(Array) ? value : value.to_s
75
+ end
76
+ tag_option(key, value)
77
+ end
78
+ end
79
+ end
80
+
81
+ def tag_option(key, value)
82
+ if value.is_a? Regexp
83
+ value = value.source
84
+ end
85
+
86
+ @controlled ||= nil
87
+
88
+ if !@controlled
89
+ if key.to_sym == :value
90
+ key = "default_value"
91
+ end
92
+
93
+ if key.to_sym == :checked
94
+ key = "default_checked"
95
+ end
96
+ end
97
+
98
+ json.set!(key, value)
99
+ end
100
+
101
+ def prefix_tag_props(prefix, key, value)
102
+ key = "#{prefix}-#{key.to_s.dasherize}"
103
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
104
+ value = value.to_json
105
+ end
106
+ tag_option(key, value)
107
+ end
108
+
109
+ def build_values(*args)
110
+ tag_values = []
111
+
112
+ args.each do |tag_value|
113
+ case tag_value
114
+ when Hash
115
+ tag_value.each do |key, val|
116
+ tag_values << key.to_s if val && key.present?
117
+ end
118
+ when Array
119
+ tag_values.concat build_values(*tag_value)
120
+ else
121
+ tag_values << tag_value.to_s if tag_value.present?
122
+ end
123
+ end
124
+
125
+ tag_values
126
+ end
127
+
128
+ def select_content_props(option_tags, options, html_options)
129
+ html_options = html_options.stringify_keys
130
+ add_default_name_and_id(html_options)
131
+
132
+ if placeholder_required?(html_options)
133
+ raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
134
+ options[:include_blank] ||= true unless options[:prompt]
135
+ end
136
+
137
+ html_options["type"] = "select"
138
+ value_for_blank = options.fetch(:selected) { value }
139
+ option_tags = add_options(option_tags, options, value_for_blank)
140
+
141
+ if options[:multiple]
142
+ html_options["multiple"] = options[:multiple]
143
+ end
144
+
145
+ if selected_values.any?
146
+ html_options["value"] ||= if html_options["multiple"]
147
+ Array(selected_values)
148
+ else
149
+ selected_values.first
150
+ end
151
+ end
152
+
153
+ json.set!(sanitized_method_name) do
154
+ input_props(html_options)
155
+
156
+ if options.key?(:include_hidden)
157
+ json.include_hidden options[:include_hidden]
158
+ end
159
+ json.options(option_tags)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end