form_props 0.0.1

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