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