simple_form 2.0.0 → 3.5.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.
Potentially problematic release.
This version of simple_form might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +97 -198
- data/MIT-LICENSE +1 -1
- data/README.md +572 -296
- data/lib/generators/simple_form/install_generator.rb +17 -7
- data/lib/generators/simple_form/templates/README +3 -4
- data/lib/generators/simple_form/templates/_form.html.erb +1 -0
- data/lib/generators/simple_form/templates/_form.html.haml +1 -0
- data/lib/generators/simple_form/templates/config/initializers/{simple_form.rb.tt → simple_form.rb} +57 -63
- data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +155 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +111 -0
- data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +14 -7
- data/lib/simple_form/action_view_extensions/builder.rb +5 -305
- data/lib/simple_form/action_view_extensions/form_helper.rb +18 -20
- data/lib/simple_form/components/errors.rb +30 -3
- data/lib/simple_form/components/hints.rb +10 -3
- data/lib/simple_form/components/html5.rb +17 -3
- data/lib/simple_form/components/label_input.rb +21 -2
- data/lib/simple_form/components/labels.rb +16 -11
- data/lib/simple_form/components/maxlength.rb +19 -12
- data/lib/simple_form/components/min_max.rb +4 -2
- data/lib/simple_form/components/minlength.rb +48 -0
- data/lib/simple_form/components/pattern.rb +5 -4
- data/lib/simple_form/components/placeholders.rb +3 -2
- data/lib/simple_form/components/readonly.rb +3 -2
- data/lib/simple_form/components.rb +15 -11
- data/lib/simple_form/error_notification.rb +4 -3
- data/lib/simple_form/form_builder.rb +283 -105
- data/lib/simple_form/helpers/autofocus.rb +1 -0
- data/lib/simple_form/helpers/disabled.rb +1 -0
- data/lib/simple_form/helpers/readonly.rb +1 -0
- data/lib/simple_form/helpers/required.rb +1 -0
- data/lib/simple_form/helpers/validators.rb +4 -3
- data/lib/simple_form/helpers.rb +7 -6
- data/lib/simple_form/i18n_cache.rb +1 -0
- data/lib/simple_form/inputs/base.rb +76 -23
- data/lib/simple_form/inputs/block_input.rb +3 -2
- data/lib/simple_form/inputs/boolean_input.rb +55 -16
- data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
- data/lib/simple_form/inputs/collection_input.rb +41 -18
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +11 -19
- data/lib/simple_form/inputs/collection_select_input.rb +5 -2
- data/lib/simple_form/inputs/date_time_input.rb +23 -12
- data/lib/simple_form/inputs/file_input.rb +5 -2
- data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
- data/lib/simple_form/inputs/hidden_input.rb +5 -2
- data/lib/simple_form/inputs/numeric_input.rb +4 -8
- data/lib/simple_form/inputs/password_input.rb +6 -4
- data/lib/simple_form/inputs/priority_input.rb +5 -2
- data/lib/simple_form/inputs/range_input.rb +2 -1
- data/lib/simple_form/inputs/string_input.rb +6 -4
- data/lib/simple_form/inputs/text_input.rb +6 -3
- data/lib/simple_form/inputs.rb +20 -17
- data/lib/simple_form/map_type.rb +1 -0
- data/lib/simple_form/railtie.rb +15 -0
- data/lib/simple_form/tags.rb +69 -0
- data/lib/simple_form/version.rb +2 -1
- data/lib/simple_form/wrappers/builder.rb +12 -35
- data/lib/simple_form/wrappers/leaf.rb +29 -0
- data/lib/simple_form/wrappers/many.rb +12 -7
- data/lib/simple_form/wrappers/root.rb +7 -4
- data/lib/simple_form/wrappers/single.rb +12 -3
- data/lib/simple_form/wrappers.rb +3 -1
- data/lib/simple_form.rb +118 -63
- data/test/action_view_extensions/builder_test.rb +230 -164
- data/test/action_view_extensions/form_helper_test.rb +107 -39
- data/test/components/label_test.rb +105 -87
- data/test/form_builder/association_test.rb +131 -62
- data/test/form_builder/button_test.rb +15 -14
- data/test/form_builder/error_notification_test.rb +11 -10
- data/test/form_builder/error_test.rb +188 -34
- data/test/form_builder/general_test.rb +247 -102
- data/test/form_builder/hint_test.rb +59 -32
- data/test/form_builder/input_field_test.rb +138 -25
- data/test/form_builder/label_test.rb +84 -13
- data/test/form_builder/wrapper_test.rb +236 -33
- data/test/generators/simple_form_generator_test.rb +15 -4
- data/test/inputs/boolean_input_test.rb +147 -13
- data/test/inputs/collection_check_boxes_input_test.rb +166 -71
- data/test/inputs/collection_radio_buttons_input_test.rb +229 -113
- data/test/inputs/collection_select_input_test.rb +222 -85
- data/test/inputs/datetime_input_test.rb +134 -47
- data/test/inputs/disabled_test.rb +62 -21
- data/test/inputs/discovery_test.rb +70 -10
- data/test/inputs/file_input_test.rb +4 -3
- data/test/inputs/general_test.rb +90 -26
- data/test/inputs/grouped_collection_select_input_test.rb +88 -23
- data/test/inputs/hidden_input_test.rb +7 -5
- data/test/inputs/numeric_input_test.rb +56 -46
- data/test/inputs/priority_input_test.rb +31 -16
- data/test/inputs/readonly_test.rb +68 -27
- data/test/inputs/required_test.rb +63 -18
- data/test/inputs/string_input_test.rb +76 -51
- data/test/inputs/text_input_test.rb +21 -8
- data/test/simple_form_test.rb +9 -0
- data/test/support/discovery_inputs.rb +39 -2
- data/test/support/misc_helpers.rb +176 -20
- data/test/support/mock_controller.rb +13 -7
- data/test/support/models.rb +187 -71
- data/test/test_helper.rb +38 -39
- metadata +53 -39
- data/lib/simple_form/core_ext/hash.rb +0 -16
- data/test/support/mock_response.rb +0 -14
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'active_support/core_ext/object/deep_dup'
|
|
3
|
+
require 'simple_form/map_type'
|
|
4
|
+
require 'simple_form/tags'
|
|
2
5
|
|
|
3
6
|
module SimpleForm
|
|
4
7
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
@@ -6,26 +9,29 @@ module SimpleForm
|
|
|
6
9
|
|
|
7
10
|
# When action is create or update, we still should use new and edit
|
|
8
11
|
ACTIONS = {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
'create' => 'new',
|
|
13
|
+
'update' => 'edit'
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
|
|
17
|
+
|
|
13
18
|
extend MapType
|
|
14
19
|
include SimpleForm::Inputs
|
|
15
20
|
|
|
16
|
-
map_type :text,
|
|
17
|
-
map_type :file,
|
|
18
|
-
map_type :string, :email, :search, :tel, :url, :to
|
|
19
|
-
map_type :password,
|
|
20
|
-
map_type :integer, :decimal, :float,
|
|
21
|
-
map_type :range,
|
|
22
|
-
map_type :check_boxes,
|
|
23
|
-
map_type :radio_buttons,
|
|
24
|
-
map_type :select,
|
|
25
|
-
map_type :grouped_select,
|
|
26
|
-
map_type :date, :time, :datetime,
|
|
27
|
-
map_type :country, :time_zone,
|
|
28
|
-
map_type :boolean,
|
|
21
|
+
map_type :text, to: SimpleForm::Inputs::TextInput
|
|
22
|
+
map_type :file, to: SimpleForm::Inputs::FileInput
|
|
23
|
+
map_type :string, :email, :search, :tel, :url, :uuid, to: SimpleForm::Inputs::StringInput
|
|
24
|
+
map_type :password, to: SimpleForm::Inputs::PasswordInput
|
|
25
|
+
map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
|
|
26
|
+
map_type :range, to: SimpleForm::Inputs::RangeInput
|
|
27
|
+
map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
|
|
28
|
+
map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
|
|
29
|
+
map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
|
|
30
|
+
map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
|
|
31
|
+
map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
|
|
32
|
+
map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
|
|
33
|
+
map_type :boolean, to: SimpleForm::Inputs::BooleanInput
|
|
34
|
+
map_type :hidden, to: SimpleForm::Inputs::HiddenInput
|
|
29
35
|
|
|
30
36
|
def self.discovery_cache
|
|
31
37
|
@discovery_cache ||= {}
|
|
@@ -33,6 +39,7 @@ module SimpleForm
|
|
|
33
39
|
|
|
34
40
|
def initialize(*) #:nodoc:
|
|
35
41
|
super
|
|
42
|
+
@object = convert_to_model(@object)
|
|
36
43
|
@defaults = options[:defaults]
|
|
37
44
|
@wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
|
|
38
45
|
end
|
|
@@ -43,11 +50,16 @@ module SimpleForm
|
|
|
43
50
|
# label + input + hint (when defined) + errors (when exists), and all can
|
|
44
51
|
# be configured inside a wrapper html.
|
|
45
52
|
#
|
|
53
|
+
# If a block is given, the contents of the block will replace the input
|
|
54
|
+
# field that would otherwise be generated automatically. The content will
|
|
55
|
+
# be given a label and wrapper div to make it consistent with the other
|
|
56
|
+
# elements in the form.
|
|
57
|
+
#
|
|
46
58
|
# == Examples
|
|
47
59
|
#
|
|
48
60
|
# # Imagine @user has error "can't be blank" on name
|
|
49
61
|
# simple_form_for @user do |f|
|
|
50
|
-
# f.input :name, :
|
|
62
|
+
# f.input :name, hint: 'My hint'
|
|
51
63
|
# end
|
|
52
64
|
#
|
|
53
65
|
# This is the output html (only the input portion, not the form):
|
|
@@ -56,7 +68,7 @@ module SimpleForm
|
|
|
56
68
|
# <abbr title="required">*</abbr> Super User Name!
|
|
57
69
|
# </label>
|
|
58
70
|
# <input class="string required" id="user_name" maxlength="100"
|
|
59
|
-
# name="user[name]"
|
|
71
|
+
# name="user[name]" type="text" value="Carlos" />
|
|
60
72
|
# <span class="hint">My hint</span>
|
|
61
73
|
# <span class="error">can't be blank</span>
|
|
62
74
|
#
|
|
@@ -65,15 +77,15 @@ module SimpleForm
|
|
|
65
77
|
#
|
|
66
78
|
# You have some options for the input to enable/disable some functions:
|
|
67
79
|
#
|
|
68
|
-
# :
|
|
80
|
+
# as: allows you to define the input type you want, for instance you
|
|
69
81
|
# can use it to generate a text field for a date column.
|
|
70
82
|
#
|
|
71
|
-
# :
|
|
83
|
+
# required: defines whether this attribute is required or not. True
|
|
72
84
|
# by default.
|
|
73
85
|
#
|
|
74
86
|
# The fact SimpleForm is built in components allow the interface to be unified.
|
|
75
87
|
# So, for instance, if you need to disable :hint for a given input, you can pass
|
|
76
|
-
# :
|
|
88
|
+
# hint: false. The same works for :error, :label and :wrapper.
|
|
77
89
|
#
|
|
78
90
|
# Besides the html for any component can be changed. So, if you want to change
|
|
79
91
|
# the label html you just need to give a hash to :label_html. To configure the
|
|
@@ -84,35 +96,31 @@ module SimpleForm
|
|
|
84
96
|
# Some inputs, as datetime, time and select allow you to give extra options, like
|
|
85
97
|
# prompt and/or include blank. Such options are given in plainly:
|
|
86
98
|
#
|
|
87
|
-
# f.input :created_at, :
|
|
99
|
+
# f.input :created_at, include_blank: true
|
|
88
100
|
#
|
|
89
101
|
# == Collection
|
|
90
102
|
#
|
|
91
103
|
# When playing with collections (:radio_buttons, :check_boxes and :select
|
|
92
104
|
# inputs), you have three extra options:
|
|
93
105
|
#
|
|
94
|
-
# :
|
|
106
|
+
# collection: use to determine the collection to generate the radio or select
|
|
95
107
|
#
|
|
96
|
-
# :
|
|
108
|
+
# label_method: the method to apply on the array collection to get the label
|
|
97
109
|
#
|
|
98
|
-
# :
|
|
110
|
+
# value_method: the method to apply on the array collection to get the value
|
|
99
111
|
#
|
|
100
112
|
# == Priority
|
|
101
113
|
#
|
|
102
114
|
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
|
|
103
|
-
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used
|
|
115
|
+
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
|
|
104
116
|
#
|
|
105
|
-
def input(attribute_name, options={}, &block)
|
|
117
|
+
def input(attribute_name, options = {}, &block)
|
|
106
118
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
|
111
|
-
else
|
|
112
|
-
wrapper
|
|
113
|
-
end
|
|
120
|
+
input = find_input(attribute_name, options, &block)
|
|
121
|
+
wrapper = find_wrapper(input.input_type, options)
|
|
114
122
|
|
|
115
|
-
|
|
123
|
+
wrapper.render input
|
|
116
124
|
end
|
|
117
125
|
alias :attribute :input
|
|
118
126
|
|
|
@@ -128,12 +136,20 @@ module SimpleForm
|
|
|
128
136
|
# This is the output html (only the input portion, not the form):
|
|
129
137
|
#
|
|
130
138
|
# <input class="string required" id="user_name" maxlength="100"
|
|
131
|
-
# name="user[name]"
|
|
139
|
+
# name="user[name]" type="text" value="Carlos" />
|
|
132
140
|
#
|
|
133
|
-
def input_field(attribute_name, options={})
|
|
141
|
+
def input_field(attribute_name, options = {})
|
|
142
|
+
components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
|
|
143
|
+
|
|
134
144
|
options = options.dup
|
|
135
|
-
options[:input_html] = options.except(:as, :collection, :label_method, :value_method)
|
|
136
|
-
|
|
145
|
+
options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, :prompt, *components)
|
|
146
|
+
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
147
|
+
|
|
148
|
+
input = find_input(attribute_name, options)
|
|
149
|
+
wrapper = find_wrapper(input.input_type, options)
|
|
150
|
+
components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
|
|
151
|
+
|
|
152
|
+
SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
|
|
137
153
|
end
|
|
138
154
|
|
|
139
155
|
# Helper for dealing with association selects/radios, generating the
|
|
@@ -147,7 +163,7 @@ module SimpleForm
|
|
|
147
163
|
# f.association :company # Company.all
|
|
148
164
|
# end
|
|
149
165
|
#
|
|
150
|
-
# f.association :company, :
|
|
166
|
+
# f.association :company, collection: Company.all(order: 'name')
|
|
151
167
|
# # Same as using :order option, but overriding collection
|
|
152
168
|
#
|
|
153
169
|
# == Block
|
|
@@ -162,7 +178,9 @@ module SimpleForm
|
|
|
162
178
|
#
|
|
163
179
|
# From the options above, only :collection can also be supplied.
|
|
164
180
|
#
|
|
165
|
-
|
|
181
|
+
# Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
|
|
182
|
+
#
|
|
183
|
+
def association(association, options = {}, &block)
|
|
166
184
|
options = options.dup
|
|
167
185
|
|
|
168
186
|
return simple_fields_for(*[association,
|
|
@@ -174,30 +192,11 @@ module SimpleForm
|
|
|
174
192
|
raise "Association #{association.inspect} not found" unless reflection
|
|
175
193
|
|
|
176
194
|
options[:as] ||= :select
|
|
177
|
-
options[:collection] ||=
|
|
178
|
-
|
|
179
|
-
attribute = case reflection.macro
|
|
180
|
-
when :belongs_to
|
|
181
|
-
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
|
182
|
-
when :has_one
|
|
183
|
-
raise ":has_one associations are not supported by f.association"
|
|
184
|
-
else
|
|
185
|
-
if options[:as] == :select
|
|
186
|
-
html_options = options[:input_html] ||= {}
|
|
187
|
-
html_options[:size] ||= 5
|
|
188
|
-
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
189
|
-
end
|
|
195
|
+
options[:collection] ||= fetch_association_collection(reflection, options)
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
if options[:preload] != false && object.respond_to?(association)
|
|
193
|
-
target = object.send(association)
|
|
194
|
-
target.to_a if target.respond_to?(:to_a)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
:"#{reflection.name.to_s.singularize}_ids"
|
|
198
|
-
end
|
|
197
|
+
attribute = build_association_attribute(reflection, association, options)
|
|
199
198
|
|
|
200
|
-
input(attribute, options.merge(:
|
|
199
|
+
input(attribute, options.merge(reflection: reflection))
|
|
201
200
|
end
|
|
202
201
|
|
|
203
202
|
# Creates a button:
|
|
@@ -210,14 +209,13 @@ module SimpleForm
|
|
|
210
209
|
# button implementation (3.2 forward (to delegate to the original when
|
|
211
210
|
# calling `f.button :button`.
|
|
212
211
|
#
|
|
213
|
-
|
|
214
|
-
alias_method :button_button, :button if method_defined?(:button)
|
|
212
|
+
alias_method :button_button, :button
|
|
215
213
|
def button(type, *args, &block)
|
|
216
214
|
options = args.extract_options!.dup
|
|
217
215
|
options[:class] = [SimpleForm.button_class, options[:class]].compact
|
|
218
216
|
args << options
|
|
219
|
-
if respond_to?("#{type}_button")
|
|
220
|
-
send("#{type}_button", *args, &block)
|
|
217
|
+
if respond_to?(:"#{type}_button")
|
|
218
|
+
send(:"#{type}_button", *args, &block)
|
|
221
219
|
else
|
|
222
220
|
send(type, *args, &block)
|
|
223
221
|
end
|
|
@@ -229,9 +227,9 @@ module SimpleForm
|
|
|
229
227
|
# == Examples
|
|
230
228
|
#
|
|
231
229
|
# f.error :name
|
|
232
|
-
# f.error :name, :
|
|
230
|
+
# f.error :name, id: "cool_error"
|
|
233
231
|
#
|
|
234
|
-
def error(attribute_name, options={})
|
|
232
|
+
def error(attribute_name, options = {})
|
|
235
233
|
options = options.dup
|
|
236
234
|
|
|
237
235
|
options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
|
|
@@ -248,7 +246,7 @@ module SimpleForm
|
|
|
248
246
|
#
|
|
249
247
|
# f.full_error :token #=> <span class="error">Token is invalid</span>
|
|
250
248
|
#
|
|
251
|
-
def full_error(attribute_name, options={})
|
|
249
|
+
def full_error(attribute_name, options = {})
|
|
252
250
|
options = options.dup
|
|
253
251
|
|
|
254
252
|
options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
|
|
@@ -267,10 +265,10 @@ module SimpleForm
|
|
|
267
265
|
# == Examples
|
|
268
266
|
#
|
|
269
267
|
# f.hint :name # Do I18n lookup
|
|
270
|
-
# f.hint :name, :
|
|
268
|
+
# f.hint :name, id: "cool_hint"
|
|
271
269
|
# f.hint "Don't forget to accept this"
|
|
272
270
|
#
|
|
273
|
-
def hint(attribute_name, options={})
|
|
271
|
+
def hint(attribute_name, options = {})
|
|
274
272
|
options = options.dup
|
|
275
273
|
|
|
276
274
|
options[:hint_html] = options.except(:hint_tag, :hint)
|
|
@@ -294,16 +292,16 @@ module SimpleForm
|
|
|
294
292
|
#
|
|
295
293
|
# f.label :name # Do I18n lookup
|
|
296
294
|
# f.label :name, "Name" # Same behavior as Rails, do not add required tag
|
|
297
|
-
# f.label :name, :
|
|
295
|
+
# f.label :name, label: "Name" # Same as above, but adds required tag
|
|
298
296
|
#
|
|
299
|
-
# f.label :name, :
|
|
300
|
-
# f.label :name, :
|
|
297
|
+
# f.label :name, required: false
|
|
298
|
+
# f.label :name, id: "cool_label"
|
|
301
299
|
#
|
|
302
300
|
def label(attribute_name, *args)
|
|
303
301
|
return super if args.first.is_a?(String) || block_given?
|
|
304
302
|
|
|
305
303
|
options = args.extract_options!.dup
|
|
306
|
-
options[:label_html] = options.except(:label, :required)
|
|
304
|
+
options[:label_html] = options.except(:label, :label_text, :required, :as)
|
|
307
305
|
|
|
308
306
|
column = find_attribute_column(attribute_name)
|
|
309
307
|
input_type = default_input_type(attribute_name, column, options)
|
|
@@ -318,13 +316,118 @@ module SimpleForm
|
|
|
318
316
|
# == Examples
|
|
319
317
|
#
|
|
320
318
|
# f.error_notification
|
|
321
|
-
# f.error_notification :
|
|
322
|
-
# f.error_notification :
|
|
319
|
+
# f.error_notification message: 'Something went wrong'
|
|
320
|
+
# f.error_notification id: 'user_error_message', class: 'form_error'
|
|
323
321
|
#
|
|
324
|
-
def error_notification(options={})
|
|
322
|
+
def error_notification(options = {})
|
|
325
323
|
SimpleForm::ErrorNotification.new(self, options).render
|
|
326
324
|
end
|
|
327
325
|
|
|
326
|
+
# Create a collection of radio inputs for the attribute. Basically this
|
|
327
|
+
# helper will create a radio input associated with a label for each
|
|
328
|
+
# text/value option in the collection, using value_method and text_method
|
|
329
|
+
# to convert these text/value. You can give a symbol or a proc to both
|
|
330
|
+
# value_method and text_method, that will be evaluated for each item in
|
|
331
|
+
# the collection.
|
|
332
|
+
#
|
|
333
|
+
# == Examples
|
|
334
|
+
#
|
|
335
|
+
# form_for @user do |f|
|
|
336
|
+
# f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
337
|
+
# end
|
|
338
|
+
#
|
|
339
|
+
# <input id="user_options_true" name="user[options]" type="radio" value="true" />
|
|
340
|
+
# <label class="collection_radio_buttons" for="user_options_true">Yes</label>
|
|
341
|
+
# <input id="user_options_false" name="user[options]" type="radio" value="false" />
|
|
342
|
+
# <label class="collection_radio_buttons" for="user_options_false">No</label>
|
|
343
|
+
#
|
|
344
|
+
# It is also possible to give a block that should generate the radio +
|
|
345
|
+
# label. To wrap the radio with the label, for instance:
|
|
346
|
+
#
|
|
347
|
+
# form_for @user do |f|
|
|
348
|
+
# f.collection_radio_buttons(
|
|
349
|
+
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
350
|
+
# ) do |b|
|
|
351
|
+
# b.label { b.radio_button + b.text }
|
|
352
|
+
# end
|
|
353
|
+
# end
|
|
354
|
+
#
|
|
355
|
+
# == Options
|
|
356
|
+
#
|
|
357
|
+
# Collection radio accepts some extra options:
|
|
358
|
+
#
|
|
359
|
+
# * checked => the value that should be checked initially.
|
|
360
|
+
#
|
|
361
|
+
# * disabled => the value or values that should be disabled. Accepts a single
|
|
362
|
+
# item or an array of items.
|
|
363
|
+
#
|
|
364
|
+
# * collection_wrapper_tag => the tag to wrap the entire collection.
|
|
365
|
+
#
|
|
366
|
+
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
|
|
367
|
+
#
|
|
368
|
+
# * item_wrapper_tag => the tag to wrap each item in the collection.
|
|
369
|
+
#
|
|
370
|
+
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
|
|
371
|
+
#
|
|
372
|
+
# * a block => to generate the label + radio or any other component.
|
|
373
|
+
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
|
374
|
+
SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Creates a collection of check boxes for each item in the collection,
|
|
378
|
+
# associated with a clickable label. Use value_method and text_method to
|
|
379
|
+
# convert items in the collection for use as text/value in check boxes.
|
|
380
|
+
# You can give a symbol or a proc to both value_method and text_method,
|
|
381
|
+
# that will be evaluated for each item in the collection.
|
|
382
|
+
#
|
|
383
|
+
# == Examples
|
|
384
|
+
#
|
|
385
|
+
# form_for @user do |f|
|
|
386
|
+
# f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
387
|
+
# end
|
|
388
|
+
#
|
|
389
|
+
# <input name="user[options][]" type="hidden" value="" />
|
|
390
|
+
# <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
|
|
391
|
+
# <label class="collection_check_boxes" for="user_options_true">Yes</label>
|
|
392
|
+
# <input name="user[options][]" type="hidden" value="" />
|
|
393
|
+
# <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
|
|
394
|
+
# <label class="collection_check_boxes" for="user_options_false">No</label>
|
|
395
|
+
#
|
|
396
|
+
# It is also possible to give a block that should generate the check box +
|
|
397
|
+
# label. To wrap the check box with the label, for instance:
|
|
398
|
+
#
|
|
399
|
+
# form_for @user do |f|
|
|
400
|
+
# f.collection_check_boxes(
|
|
401
|
+
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
402
|
+
# ) do |b|
|
|
403
|
+
# b.label { b.check_box + b.text }
|
|
404
|
+
# end
|
|
405
|
+
# end
|
|
406
|
+
#
|
|
407
|
+
# == Options
|
|
408
|
+
#
|
|
409
|
+
# Collection check box accepts some extra options:
|
|
410
|
+
#
|
|
411
|
+
# * checked => the value or values that should be checked initially. Accepts
|
|
412
|
+
# a single item or an array of items. It overrides existing associations.
|
|
413
|
+
#
|
|
414
|
+
# * disabled => the value or values that should be disabled. Accepts a single
|
|
415
|
+
# item or an array of items.
|
|
416
|
+
#
|
|
417
|
+
# * collection_wrapper_tag => the tag to wrap the entire collection.
|
|
418
|
+
#
|
|
419
|
+
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
|
|
420
|
+
# is ignored if the :collection_wrapper_tag option is blank.
|
|
421
|
+
#
|
|
422
|
+
# * item_wrapper_tag => the tag to wrap each item in the collection.
|
|
423
|
+
#
|
|
424
|
+
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
|
|
425
|
+
#
|
|
426
|
+
# * a block => to generate the label + check box or any other component.
|
|
427
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
|
428
|
+
SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
|
|
429
|
+
end
|
|
430
|
+
|
|
328
431
|
# Extract the model names from the object_name mess, ignoring numeric and
|
|
329
432
|
# explicit child indexes.
|
|
330
433
|
#
|
|
@@ -333,10 +436,10 @@ module SimpleForm
|
|
|
333
436
|
# route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
|
|
334
437
|
# ["route", "blocks", "blocks_learning_object", "foo"]
|
|
335
438
|
#
|
|
336
|
-
def lookup_model_names
|
|
439
|
+
def lookup_model_names #:nodoc:
|
|
337
440
|
@lookup_model_names ||= begin
|
|
338
441
|
child_index = options[:child_index]
|
|
339
|
-
names = object_name.to_s.scan(/(
|
|
442
|
+
names = object_name.to_s.scan(/(?!\d)\w+/).flatten
|
|
340
443
|
names.delete(child_index) if child_index
|
|
341
444
|
names.each { |name| name.gsub!('_attributes', '') }
|
|
342
445
|
names.freeze
|
|
@@ -344,28 +447,67 @@ module SimpleForm
|
|
|
344
447
|
end
|
|
345
448
|
|
|
346
449
|
# The action to be used in lookup.
|
|
347
|
-
def lookup_action
|
|
450
|
+
def lookup_action #:nodoc:
|
|
348
451
|
@lookup_action ||= begin
|
|
349
|
-
action = template.controller.action_name
|
|
452
|
+
action = template.controller && template.controller.action_name
|
|
350
453
|
return unless action
|
|
351
|
-
action = action.
|
|
454
|
+
action = action.to_s
|
|
352
455
|
ACTIONS[action] || action
|
|
353
456
|
end
|
|
354
457
|
end
|
|
355
458
|
|
|
356
459
|
private
|
|
357
460
|
|
|
461
|
+
def fetch_association_collection(reflection, options)
|
|
462
|
+
options.fetch(:collection) do
|
|
463
|
+
relation = reflection.klass.all
|
|
464
|
+
|
|
465
|
+
if reflection.respond_to?(:scope) && reflection.scope
|
|
466
|
+
if reflection.scope.parameters.any?
|
|
467
|
+
relation = reflection.klass.instance_exec(object, &reflection.scope)
|
|
468
|
+
else
|
|
469
|
+
relation = reflection.klass.instance_exec(&reflection.scope)
|
|
470
|
+
end
|
|
471
|
+
else
|
|
472
|
+
order = reflection.options[:order]
|
|
473
|
+
conditions = reflection.options[:conditions]
|
|
474
|
+
conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
|
|
475
|
+
|
|
476
|
+
relation = relation.where(conditions) if relation.respond_to?(:where)
|
|
477
|
+
relation = relation.order(order) if relation.respond_to?(:order)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
relation
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def build_association_attribute(reflection, association, options)
|
|
485
|
+
case reflection.macro
|
|
486
|
+
when :belongs_to
|
|
487
|
+
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
|
488
|
+
when :has_one
|
|
489
|
+
raise ArgumentError, ":has_one associations are not supported by f.association"
|
|
490
|
+
else
|
|
491
|
+
if options[:as] == :select
|
|
492
|
+
html_options = options[:input_html] ||= {}
|
|
493
|
+
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Force the association to be preloaded for performance.
|
|
497
|
+
if options[:preload] != false && object.respond_to?(association)
|
|
498
|
+
target = object.send(association)
|
|
499
|
+
target.to_a if target.respond_to?(:to_a)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
:"#{reflection.name.to_s.singularize}_ids"
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
358
506
|
# Find an input based on the attribute name.
|
|
359
|
-
def find_input(attribute_name, options={}, &block)
|
|
507
|
+
def find_input(attribute_name, options = {}, &block)
|
|
360
508
|
column = find_attribute_column(attribute_name)
|
|
361
509
|
input_type = default_input_type(attribute_name, column, options)
|
|
362
510
|
|
|
363
|
-
if input_type == :radio
|
|
364
|
-
SimpleForm.deprecation_warn "Using `:as => :radio` as input type is " \
|
|
365
|
-
"deprecated, please change it to `:as => :radio_buttons`."
|
|
366
|
-
input_type = :radio_buttons
|
|
367
|
-
end
|
|
368
|
-
|
|
369
511
|
if block_given?
|
|
370
512
|
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
|
|
371
513
|
else
|
|
@@ -374,12 +516,12 @@ module SimpleForm
|
|
|
374
516
|
end
|
|
375
517
|
|
|
376
518
|
# Attempt to guess the better input type given the defined options. By
|
|
377
|
-
# default
|
|
519
|
+
# default always fallback to the user :as option, or to a :select when a
|
|
378
520
|
# collection is given.
|
|
379
|
-
def default_input_type(attribute_name, column, options)
|
|
521
|
+
def default_input_type(attribute_name, column, options)
|
|
380
522
|
return options[:as].to_sym if options[:as]
|
|
381
|
-
return :select if options[:collection]
|
|
382
523
|
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
|
|
524
|
+
return :select if options[:collection]
|
|
383
525
|
|
|
384
526
|
input_type = column.try(:type)
|
|
385
527
|
case input_type
|
|
@@ -401,24 +543,26 @@ module SimpleForm
|
|
|
401
543
|
end
|
|
402
544
|
end
|
|
403
545
|
|
|
404
|
-
def find_custom_type(attribute_name)
|
|
546
|
+
def find_custom_type(attribute_name)
|
|
405
547
|
SimpleForm.input_mappings.find { |match, type|
|
|
406
548
|
attribute_name =~ match
|
|
407
549
|
}.try(:last) if SimpleForm.input_mappings
|
|
408
550
|
end
|
|
409
551
|
|
|
410
|
-
def file_method?(attribute_name)
|
|
552
|
+
def file_method?(attribute_name)
|
|
411
553
|
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
|
412
554
|
file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
|
413
555
|
end
|
|
414
556
|
|
|
415
|
-
def find_attribute_column(attribute_name)
|
|
416
|
-
if @object.respond_to?(:
|
|
557
|
+
def find_attribute_column(attribute_name)
|
|
558
|
+
if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
|
|
559
|
+
@object.type_for_attribute(attribute_name.to_s)
|
|
560
|
+
elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
|
|
417
561
|
@object.column_for_attribute(attribute_name)
|
|
418
562
|
end
|
|
419
563
|
end
|
|
420
564
|
|
|
421
|
-
def find_association_reflection(association)
|
|
565
|
+
def find_association_reflection(association)
|
|
422
566
|
if @object.class.respond_to?(:reflect_on_association)
|
|
423
567
|
@object.class.reflect_on_association(association)
|
|
424
568
|
end
|
|
@@ -431,20 +575,42 @@ module SimpleForm
|
|
|
431
575
|
# b) Or use the found mapping
|
|
432
576
|
# 2) If not, fallbacks to #{input_type}Input
|
|
433
577
|
# 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
|
|
434
|
-
def find_mapping(input_type)
|
|
578
|
+
def find_mapping(input_type)
|
|
435
579
|
discovery_cache[input_type] ||=
|
|
436
580
|
if mapping = self.class.mappings[input_type]
|
|
437
581
|
mapping_override(mapping) || mapping
|
|
438
582
|
else
|
|
439
583
|
camelized = "#{input_type.to_s.camelize}Input"
|
|
440
|
-
|
|
584
|
+
attempt_mapping_with_custom_namespace(camelized) ||
|
|
585
|
+
attempt_mapping(camelized, Object) ||
|
|
586
|
+
attempt_mapping(camelized, self.class) ||
|
|
441
587
|
raise("No input found for #{input_type}")
|
|
442
588
|
end
|
|
443
589
|
end
|
|
444
590
|
|
|
591
|
+
# Attempts to find a wrapper mapping. It follows the following rules:
|
|
592
|
+
#
|
|
593
|
+
# 1) It tries to find a wrapper for the current form
|
|
594
|
+
# 2) If not, it tries to find a config
|
|
595
|
+
def find_wrapper_mapping(input_type)
|
|
596
|
+
if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
|
|
597
|
+
options[:wrapper_mappings][input_type]
|
|
598
|
+
else
|
|
599
|
+
SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def find_wrapper(input_type, options)
|
|
604
|
+
if name = options[:wrapper] || find_wrapper_mapping(input_type)
|
|
605
|
+
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
|
606
|
+
else
|
|
607
|
+
wrapper
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
445
611
|
# If cache_discovery is enabled, use the class level cache that persists
|
|
446
612
|
# between requests, otherwise use the instance one.
|
|
447
|
-
def discovery_cache
|
|
613
|
+
def discovery_cache
|
|
448
614
|
if SimpleForm.cache_discovery
|
|
449
615
|
self.class.discovery_cache
|
|
450
616
|
else
|
|
@@ -452,21 +618,33 @@ module SimpleForm
|
|
|
452
618
|
end
|
|
453
619
|
end
|
|
454
620
|
|
|
455
|
-
def mapping_override(klass)
|
|
621
|
+
def mapping_override(klass)
|
|
456
622
|
name = klass.name
|
|
457
623
|
if name =~ /^SimpleForm::Inputs/
|
|
458
|
-
|
|
624
|
+
input_name = name.split("::").last
|
|
625
|
+
attempt_mapping_with_custom_namespace(input_name) ||
|
|
626
|
+
attempt_mapping(input_name, Object)
|
|
459
627
|
end
|
|
460
628
|
end
|
|
461
629
|
|
|
462
|
-
def attempt_mapping(mapping, at)
|
|
630
|
+
def attempt_mapping(mapping, at)
|
|
463
631
|
return if SimpleForm.inputs_discovery == false && at == Object
|
|
464
632
|
|
|
465
633
|
begin
|
|
466
634
|
at.const_get(mapping)
|
|
467
635
|
rescue NameError => e
|
|
468
|
-
e.message
|
|
636
|
+
raise if e.message !~ /#{mapping}$/
|
|
469
637
|
end
|
|
470
638
|
end
|
|
639
|
+
|
|
640
|
+
def attempt_mapping_with_custom_namespace(input_name)
|
|
641
|
+
SimpleForm.custom_inputs_namespaces.each do |namespace|
|
|
642
|
+
if (mapping = attempt_mapping(input_name, namespace.constantize))
|
|
643
|
+
return mapping
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
nil
|
|
648
|
+
end
|
|
471
649
|
end
|
|
472
650
|
end
|