simple_form 1.2.0 → 1.4.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.
- data/.gitignore +2 -0
- data/.gitmodules +3 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.rdoc +109 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +82 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +180 -53
- data/Rakefile +27 -0
- data/lib/generators/simple_form/USAGE +1 -1
- data/lib/generators/simple_form/install_generator.rb +5 -6
- data/lib/generators/simple_form/templates/_form.html.erb +2 -12
- data/lib/generators/simple_form/templates/_form.html.haml +10 -0
- data/lib/generators/simple_form/templates/_form.html.slim +10 -0
- data/lib/generators/simple_form/templates/en.yml +14 -0
- data/lib/generators/simple_form/templates/simple_form.rb +66 -12
- data/lib/simple_form/action_view_extensions/builder.rb +99 -40
- data/lib/simple_form/action_view_extensions/form_helper.rb +29 -6
- data/lib/simple_form/components/errors.rb +16 -6
- data/lib/simple_form/components/hints.rb +2 -2
- data/lib/simple_form/components/label_input.rb +13 -0
- data/lib/simple_form/components/labels.rb +5 -7
- data/lib/simple_form/components/placeholders.rb +22 -0
- data/lib/simple_form/components/wrapper.rb +19 -2
- data/lib/simple_form/components.rb +6 -4
- data/lib/simple_form/error_notification.rb +42 -0
- data/lib/simple_form/form_builder.rb +187 -72
- data/lib/simple_form/has_errors.rb +14 -0
- data/lib/simple_form/inputs/base.rb +106 -24
- data/lib/simple_form/inputs/block_input.rb +3 -2
- data/lib/simple_form/inputs/boolean_input.rb +22 -0
- data/lib/simple_form/inputs/collection_input.rb +46 -17
- data/lib/simple_form/inputs/date_time_input.rb +11 -5
- data/lib/simple_form/inputs/hidden_input.rb +11 -2
- data/lib/simple_form/inputs/mapping_input.rb +12 -6
- data/lib/simple_form/inputs/numeric_input.rb +55 -6
- data/lib/simple_form/inputs/priority_input.rb +5 -1
- data/lib/simple_form/inputs/string_input.rb +25 -11
- data/lib/simple_form/inputs.rb +1 -0
- data/lib/simple_form/map_type.rb +9 -6
- data/lib/simple_form/version.rb +1 -1
- data/lib/simple_form.rb +92 -8
- data/simple_form.gemspec +22 -0
- data/test/action_view_extensions/builder_test.rb +187 -51
- data/test/action_view_extensions/form_helper_test.rb +17 -5
- data/test/components/error_test.rb +23 -12
- data/test/components/hint_test.rb +4 -8
- data/test/components/label_test.rb +79 -11
- data/test/components/wrapper_test.rb +63 -0
- data/test/discovery_inputs.rb +21 -0
- data/test/error_notification_test.rb +62 -0
- data/test/form_builder_test.rb +332 -35
- data/test/inputs_test.rb +516 -53
- data/test/support/misc_helpers.rb +32 -4
- data/test/support/mock_controller.rb +4 -4
- data/test/support/models.rb +75 -11
- data/test/test_helper.rb +28 -12
- metadata +51 -11
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
3
|
-
attr_reader :template, :object_name, :object
|
|
4
|
-
:reflection, :input_type, :options
|
|
3
|
+
attr_reader :template, :object_name, :object
|
|
5
4
|
|
|
6
5
|
extend MapType
|
|
7
6
|
include SimpleForm::Inputs
|
|
8
7
|
|
|
9
|
-
map_type :
|
|
10
|
-
map_type :string, :email, :url,
|
|
11
|
-
map_type :integer, :decimal, :float,
|
|
12
|
-
map_type :select, :radio, :check_boxes,
|
|
13
|
-
map_type :date, :time, :datetime,
|
|
14
|
-
map_type :country, :time_zone,
|
|
8
|
+
map_type :text, :file, :to => SimpleForm::Inputs::MappingInput
|
|
9
|
+
map_type :string, :password, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
|
|
10
|
+
map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
|
|
11
|
+
map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
|
|
12
|
+
map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
|
|
13
|
+
map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
|
|
14
|
+
map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
|
|
15
|
+
|
|
16
|
+
def self.discovery_cache
|
|
17
|
+
@discovery_cache ||= {}
|
|
18
|
+
end
|
|
15
19
|
|
|
16
20
|
# Basic input helper, combines all components in the stack to generate
|
|
17
21
|
# input html based on options the user define and some guesses through
|
|
@@ -79,18 +83,37 @@ module SimpleForm
|
|
|
79
83
|
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
|
|
80
84
|
#
|
|
81
85
|
def input(attribute_name, options={}, &block)
|
|
82
|
-
|
|
86
|
+
column = find_attribute_column(attribute_name)
|
|
87
|
+
input_type = default_input_type(attribute_name, column, options)
|
|
83
88
|
|
|
84
89
|
if block_given?
|
|
85
|
-
SimpleForm::Inputs::BlockInput.new(self, block).render
|
|
90
|
+
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
|
|
86
91
|
else
|
|
87
|
-
|
|
88
|
-
self.class.const_get(:"#{input_type.to_s.camelize}Input")
|
|
89
|
-
klass.new(self).render
|
|
92
|
+
find_mapping(input_type).new(self, attribute_name, column, input_type, options).render
|
|
90
93
|
end
|
|
91
94
|
end
|
|
92
95
|
alias :attribute :input
|
|
93
96
|
|
|
97
|
+
# Creates a input tag for the given attribute. All the given options
|
|
98
|
+
# are sent as :input_html.
|
|
99
|
+
#
|
|
100
|
+
# == Examples
|
|
101
|
+
#
|
|
102
|
+
# simple_form_for @user do |f|
|
|
103
|
+
# f.input_field :name
|
|
104
|
+
# end
|
|
105
|
+
#
|
|
106
|
+
# This is the output html (only the input portion, not the form):
|
|
107
|
+
#
|
|
108
|
+
# <input class="string required" id="user_name" maxlength="100"
|
|
109
|
+
# name="user[name]" size="100" type="text" value="Carlos" />
|
|
110
|
+
#
|
|
111
|
+
def input_field(attribute_name, options={})
|
|
112
|
+
options[:input_html] = options.except(:as)
|
|
113
|
+
options.merge!(:components => [:input], :wrapper => false)
|
|
114
|
+
input(attribute_name, options)
|
|
115
|
+
end
|
|
116
|
+
|
|
94
117
|
# Helper for dealing with association selects/radios, generating the
|
|
95
118
|
# collection automatically. It's just a wrapper to input, so all options
|
|
96
119
|
# supported in input are also supported by association. Some extra options
|
|
@@ -123,30 +146,34 @@ module SimpleForm
|
|
|
123
146
|
|
|
124
147
|
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
|
|
125
148
|
|
|
149
|
+
reflection = find_association_reflection(association)
|
|
150
|
+
raise "Association #{association.inspect} not found" unless reflection
|
|
151
|
+
|
|
126
152
|
options[:as] ||= :select
|
|
127
|
-
|
|
128
|
-
raise "Association #{association.inspect} not found" unless @reflection
|
|
153
|
+
options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
|
|
129
154
|
|
|
130
|
-
case
|
|
155
|
+
attribute = case reflection.macro
|
|
131
156
|
when :belongs_to
|
|
132
|
-
|
|
157
|
+
reflection.options[:foreign_key] || :"#{reflection.name}_id"
|
|
133
158
|
when :has_one
|
|
134
|
-
raise ":has_one
|
|
159
|
+
raise ":has_one associations are not supported by f.association"
|
|
135
160
|
else
|
|
136
|
-
attribute = :"#{@reflection.name.to_s.singularize}_ids"
|
|
137
|
-
|
|
138
161
|
if options[:as] == :select
|
|
139
162
|
html_options = options[:input_html] ||= {}
|
|
140
163
|
html_options[:size] ||= 5
|
|
141
164
|
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
142
165
|
end
|
|
143
|
-
end
|
|
144
166
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
# Force the association to be preloaded for performance.
|
|
168
|
+
if options[:preload] != false && object.respond_to?(association)
|
|
169
|
+
target = object.send(association)
|
|
170
|
+
target.to_a if target.respond_to?(:to_a)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
:"#{reflection.name.to_s.singularize}_ids"
|
|
174
|
+
end
|
|
148
175
|
|
|
149
|
-
|
|
176
|
+
input(attribute, options.merge(:reflection => reflection))
|
|
150
177
|
end
|
|
151
178
|
|
|
152
179
|
# Creates a button:
|
|
@@ -158,8 +185,11 @@ module SimpleForm
|
|
|
158
185
|
# It just acts as a proxy to method name given.
|
|
159
186
|
#
|
|
160
187
|
def button(type, *args, &block)
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
options = args.extract_options!
|
|
189
|
+
options[:class] = "button #{options[:class]}".strip
|
|
190
|
+
args << options
|
|
191
|
+
if respond_to?("#{type}_button")
|
|
192
|
+
send("#{type}_button", *args, &block)
|
|
163
193
|
else
|
|
164
194
|
send(type, *args, &block)
|
|
165
195
|
end
|
|
@@ -174,8 +204,27 @@ module SimpleForm
|
|
|
174
204
|
# f.error :name, :id => "cool_error"
|
|
175
205
|
#
|
|
176
206
|
def error(attribute_name, options={})
|
|
177
|
-
|
|
178
|
-
|
|
207
|
+
options[:error_html] = options.dup
|
|
208
|
+
column = find_attribute_column(attribute_name)
|
|
209
|
+
input_type = default_input_type(attribute_name, column, options)
|
|
210
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Return the error but also considering its name. This is used
|
|
214
|
+
# when errors for a hidden field need to be shown.
|
|
215
|
+
#
|
|
216
|
+
# == Examples
|
|
217
|
+
#
|
|
218
|
+
# f.full_error :token #=> <span class="error">Token is invalid</span>
|
|
219
|
+
#
|
|
220
|
+
def full_error(attribute_name, options={})
|
|
221
|
+
options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
|
|
222
|
+
object.class.human_attribute_name(attribute_name.to_s)
|
|
223
|
+
else
|
|
224
|
+
attribute_name.to_s.humanize
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
error(attribute_name, options)
|
|
179
228
|
end
|
|
180
229
|
|
|
181
230
|
# Creates a hint tag for the given attribute. Accepts a symbol indicating
|
|
@@ -189,9 +238,15 @@ module SimpleForm
|
|
|
189
238
|
# f.hint "Don't forget to accept this"
|
|
190
239
|
#
|
|
191
240
|
def hint(attribute_name, options={})
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
241
|
+
options[:hint_html] = options.dup
|
|
242
|
+
if attribute_name.is_a?(String)
|
|
243
|
+
options[:hint] = attribute_name
|
|
244
|
+
attribute_name, column, input_type = nil, nil, nil
|
|
245
|
+
else
|
|
246
|
+
column = find_attribute_column(attribute_name)
|
|
247
|
+
input_type = default_input_type(attribute_name, column, options)
|
|
248
|
+
end
|
|
249
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
|
|
195
250
|
end
|
|
196
251
|
|
|
197
252
|
# Creates a default label tag for the given attribute. You can give a label
|
|
@@ -210,65 +265,125 @@ module SimpleForm
|
|
|
210
265
|
def label(attribute_name, *args)
|
|
211
266
|
return super if args.first.is_a?(String)
|
|
212
267
|
options = args.extract_options!
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
268
|
+
options[:label_html] = options.dup
|
|
269
|
+
options[:label] = options.delete(:label)
|
|
270
|
+
options[:required] = options.delete(:required)
|
|
271
|
+
column = find_attribute_column(attribute_name)
|
|
272
|
+
input_type = default_input_type(attribute_name, column, options)
|
|
273
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
|
|
216
274
|
end
|
|
217
275
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
276
|
+
# Creates an error notification message that only appears when the form object
|
|
277
|
+
# has some error. You can give a specific message with the :message option,
|
|
278
|
+
# otherwise it will look for a message using I18n. All other options given are
|
|
279
|
+
# passed straight as html options to the html tag.
|
|
280
|
+
#
|
|
281
|
+
# == Examples
|
|
282
|
+
#
|
|
283
|
+
# f.error_notification
|
|
284
|
+
# f.error_notification :message => 'Something went wrong'
|
|
285
|
+
# f.error_notification :id => 'user_error_message', :class => 'form_error'
|
|
286
|
+
#
|
|
287
|
+
def error_notification(options={})
|
|
288
|
+
SimpleForm::ErrorNotification.new(self, options).render
|
|
228
289
|
end
|
|
229
290
|
|
|
291
|
+
private
|
|
292
|
+
|
|
230
293
|
# Attempt to guess the better input type given the defined options. By
|
|
231
294
|
# default alwayls fallback to the user :as option, or to a :select when a
|
|
232
295
|
# collection is given.
|
|
233
|
-
def default_input_type #:nodoc:
|
|
234
|
-
return
|
|
235
|
-
return :select
|
|
236
|
-
|
|
237
|
-
input_type = @column.try(:type)
|
|
296
|
+
def default_input_type(attribute_name, column, options) #:nodoc:
|
|
297
|
+
return options[:as].to_sym if options[:as]
|
|
298
|
+
return :select if options[:collection]
|
|
299
|
+
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
|
|
238
300
|
|
|
301
|
+
input_type = column.try(:type)
|
|
239
302
|
case input_type
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
match || input_type || file_method? || :string
|
|
303
|
+
when :timestamp
|
|
304
|
+
:datetime
|
|
305
|
+
when :string, nil
|
|
306
|
+
case attribute_name.to_s
|
|
307
|
+
when /password/ then :password
|
|
308
|
+
when /time_zone/ then :time_zone
|
|
309
|
+
when /country/ then :country
|
|
310
|
+
when /email/ then :email
|
|
311
|
+
when /phone/ then :tel
|
|
312
|
+
when /url/ then :url
|
|
252
313
|
else
|
|
253
|
-
input_type
|
|
314
|
+
file_method?(attribute_name) ? :file : (input_type || :string)
|
|
315
|
+
end
|
|
316
|
+
else
|
|
317
|
+
input_type
|
|
254
318
|
end
|
|
255
319
|
end
|
|
256
320
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
:
|
|
321
|
+
def find_custom_type(attribute_name) #:nodoc:
|
|
322
|
+
SimpleForm.input_mappings.find { |match, type|
|
|
323
|
+
attribute_name =~ match
|
|
324
|
+
}.try(:last) if SimpleForm.input_mappings
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def file_method?(attribute_name) #:nodoc:
|
|
328
|
+
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
|
329
|
+
file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
|
261
330
|
end
|
|
262
331
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
332
|
+
def find_attribute_column(attribute_name) #:nodoc:
|
|
333
|
+
if @object.respond_to?(:column_for_attribute)
|
|
334
|
+
@object.column_for_attribute(attribute_name)
|
|
335
|
+
end
|
|
266
336
|
end
|
|
267
337
|
|
|
268
|
-
# Find reflection related to association
|
|
269
338
|
def find_association_reflection(association) #:nodoc:
|
|
270
|
-
|
|
339
|
+
if @object.class.respond_to?(:reflect_on_association)
|
|
340
|
+
@object.class.reflect_on_association(association)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Attempts to find a mapping. It follows the following rules:
|
|
345
|
+
#
|
|
346
|
+
# 1) It tries to find a registered mapping, if succeeds:
|
|
347
|
+
# a) Try to find an alternative with the same name in the Object scope
|
|
348
|
+
# b) Or use the found mapping
|
|
349
|
+
# 2) If not, fallbacks to #{input_type}Input
|
|
350
|
+
# 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
|
|
351
|
+
def find_mapping(input_type) #:nodoc:
|
|
352
|
+
discovery_cache[input_type] ||=
|
|
353
|
+
if mapping = self.class.mappings[input_type]
|
|
354
|
+
mapping_override(mapping) || mapping
|
|
355
|
+
else
|
|
356
|
+
camelized = "#{input_type.to_s.camelize}Input"
|
|
357
|
+
attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
|
|
358
|
+
raise("No input found for #{input_type}")
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# If cache_discovery is enabled, use the class level cache that persists
|
|
363
|
+
# between requests, otherwise use the instance one.
|
|
364
|
+
def discovery_cache #:nodoc:
|
|
365
|
+
if SimpleForm.cache_discovery
|
|
366
|
+
self.class.discovery_cache
|
|
367
|
+
else
|
|
368
|
+
@discovery_cache ||= {}
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def mapping_override(klass) #:nodoc:
|
|
373
|
+
name = klass.name
|
|
374
|
+
if name =~ /^SimpleForm::Inputs/
|
|
375
|
+
attempt_mapping name.split("::").last, Object
|
|
376
|
+
end
|
|
271
377
|
end
|
|
272
378
|
|
|
379
|
+
def attempt_mapping(mapping, at) #:nodoc:
|
|
380
|
+
return if SimpleForm.inputs_discovery == false && at == Object
|
|
381
|
+
|
|
382
|
+
begin
|
|
383
|
+
at.const_get(mapping)
|
|
384
|
+
rescue NameError => e
|
|
385
|
+
e.message =~ /#{mapping}$/ ? nil : raise
|
|
386
|
+
end
|
|
387
|
+
end
|
|
273
388
|
end
|
|
274
389
|
end
|
|
@@ -11,14 +11,27 @@ module SimpleForm
|
|
|
11
11
|
|
|
12
12
|
include SimpleForm::Components::Errors
|
|
13
13
|
include SimpleForm::Components::Hints
|
|
14
|
-
include SimpleForm::Components::
|
|
14
|
+
include SimpleForm::Components::LabelInput
|
|
15
|
+
include SimpleForm::Components::Placeholders
|
|
15
16
|
include SimpleForm::Components::Wrapper
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
attr_reader :attribute_name, :column, :input_type, :reflection,
|
|
19
|
+
:options, :input_html_options
|
|
20
|
+
|
|
21
|
+
delegate :template, :object, :object_name, :to => :@builder
|
|
22
|
+
|
|
23
|
+
def initialize(builder, attribute_name, column, input_type, options = {})
|
|
24
|
+
@builder = builder
|
|
25
|
+
@attribute_name = attribute_name
|
|
26
|
+
@column = column
|
|
27
|
+
@input_type = input_type
|
|
28
|
+
@reflection = options.delete(:reflection)
|
|
29
|
+
@options = options
|
|
30
|
+
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
|
|
31
|
+
o[:required] = true if has_required? # Don't make this conditional on HTML5 here, because we want the CSS class to be set
|
|
32
|
+
o[:disabled] = true if disabled?
|
|
33
|
+
o[:autofocus] = true if has_autofocus? && SimpleForm.html5
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
|
|
24
37
|
def input
|
|
@@ -29,35 +42,61 @@ module SimpleForm
|
|
|
29
42
|
options
|
|
30
43
|
end
|
|
31
44
|
|
|
32
|
-
def
|
|
33
|
-
|
|
45
|
+
def input_html_classes
|
|
46
|
+
[input_type, required_class]
|
|
34
47
|
end
|
|
35
48
|
|
|
36
49
|
def render
|
|
37
|
-
content =
|
|
50
|
+
content = "".html_safe
|
|
51
|
+
components_list.each do |component|
|
|
38
52
|
next if options[component] == false
|
|
39
|
-
send(component)
|
|
53
|
+
rendered = send(component)
|
|
54
|
+
content.safe_concat rendered.to_s if rendered
|
|
40
55
|
end
|
|
41
|
-
content
|
|
42
|
-
wrap(content.join.html_safe)
|
|
56
|
+
wrap(content)
|
|
43
57
|
end
|
|
44
58
|
|
|
45
59
|
protected
|
|
46
60
|
|
|
47
61
|
def components_list
|
|
48
|
-
SimpleForm.components
|
|
62
|
+
options[:components] || SimpleForm.components
|
|
49
63
|
end
|
|
50
64
|
|
|
51
65
|
def attribute_required?
|
|
52
|
-
if options.
|
|
66
|
+
if !options[:required].nil?
|
|
53
67
|
options[:required]
|
|
54
|
-
elsif
|
|
55
|
-
|
|
68
|
+
elsif has_validators?
|
|
69
|
+
(attribute_validators + reflection_validators).any? { |v| v.kind == :presence }
|
|
56
70
|
else
|
|
57
|
-
|
|
71
|
+
attribute_required_by_default?
|
|
58
72
|
end
|
|
59
73
|
end
|
|
60
74
|
|
|
75
|
+
# Whether this input is valid for HTML 5 required attribute.
|
|
76
|
+
def has_required?
|
|
77
|
+
attribute_required? && SimpleForm.html5
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def has_autofocus?
|
|
81
|
+
options[:autofocus]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def has_validators?
|
|
85
|
+
attribute_name && object.class.respond_to?(:validators_on)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def attribute_validators
|
|
89
|
+
object.class.validators_on(attribute_name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def reflection_validators
|
|
93
|
+
reflection ? object.class.validators_on(reflection.name) : []
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def attribute_required_by_default?
|
|
97
|
+
SimpleForm.required_by_default
|
|
98
|
+
end
|
|
99
|
+
|
|
61
100
|
def required_class
|
|
62
101
|
attribute_required? ? :required : :optional
|
|
63
102
|
end
|
|
@@ -68,12 +107,16 @@ module SimpleForm
|
|
|
68
107
|
end
|
|
69
108
|
|
|
70
109
|
# Retrieve options for the given namespace from the options hash
|
|
71
|
-
def html_options_for(namespace,
|
|
110
|
+
def html_options_for(namespace, extra)
|
|
72
111
|
html_options = options[:"#{namespace}_html"] || {}
|
|
73
112
|
html_options[:class] = (extra << html_options[:class]).join(' ').strip if extra.present?
|
|
74
113
|
html_options
|
|
75
114
|
end
|
|
76
115
|
|
|
116
|
+
def disabled?
|
|
117
|
+
options[:disabled]
|
|
118
|
+
end
|
|
119
|
+
|
|
77
120
|
# Lookup translations for the given namespace using I18n, based on object name,
|
|
78
121
|
# actual action and attribute name. Lookup priority as follows:
|
|
79
122
|
#
|
|
@@ -87,6 +130,15 @@ module SimpleForm
|
|
|
87
130
|
# Action is the action being rendered, usually :new or :edit.
|
|
88
131
|
# And attribute is the attribute itself, :name for example.
|
|
89
132
|
#
|
|
133
|
+
# The lookup for nested attributes is also done in a nested format using
|
|
134
|
+
# both model and nested object names, such as follow:
|
|
135
|
+
#
|
|
136
|
+
# simple_form.{namespace}.{model}.{nested}.{action}.{attribute}
|
|
137
|
+
# simple_form.{namespace}.{model}.{nested}.{attribute}
|
|
138
|
+
# simple_form.{namespace}.{nested}.{action}.{attribute}
|
|
139
|
+
# simple_form.{namespace}.{nested}.{attribute}
|
|
140
|
+
# simple_form.{namespace}.{attribute}
|
|
141
|
+
#
|
|
90
142
|
# Example:
|
|
91
143
|
#
|
|
92
144
|
# simple_form:
|
|
@@ -99,19 +151,49 @@ module SimpleForm
|
|
|
99
151
|
#
|
|
100
152
|
# Take a look at our locale example file.
|
|
101
153
|
def translate(namespace, default='')
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
154
|
+
return nil unless SimpleForm.translate
|
|
155
|
+
|
|
156
|
+
model_names = lookup_model_names
|
|
157
|
+
lookups = []
|
|
158
|
+
|
|
159
|
+
while !model_names.empty?
|
|
160
|
+
joined_model_names = model_names.join(".")
|
|
161
|
+
model_names.shift
|
|
162
|
+
|
|
163
|
+
lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
|
|
164
|
+
lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
|
|
165
|
+
end
|
|
105
166
|
lookups << :"#{reflection_or_attribute_name}"
|
|
106
167
|
lookups << default
|
|
107
|
-
|
|
168
|
+
|
|
169
|
+
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Extract the model names from the object_name mess.
|
|
173
|
+
#
|
|
174
|
+
# Example:
|
|
175
|
+
#
|
|
176
|
+
# route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
|
|
177
|
+
# ["route", "blocks", "blocks_learning_object", "foo"]
|
|
178
|
+
#
|
|
179
|
+
def lookup_model_names
|
|
180
|
+
object_name.to_s.scan(/([a-zA-Z_]+)/).flatten.map do |x|
|
|
181
|
+
x.gsub('_attributes', '')
|
|
182
|
+
end
|
|
108
183
|
end
|
|
109
184
|
|
|
110
185
|
# The action to be used in lookup.
|
|
111
186
|
def lookup_action
|
|
112
|
-
action = template.controller.action_name
|
|
187
|
+
action = template.controller.action_name
|
|
188
|
+
return unless action
|
|
189
|
+
action = action.to_sym
|
|
113
190
|
ACTIONS[action] || action
|
|
114
191
|
end
|
|
192
|
+
|
|
193
|
+
def input_method
|
|
194
|
+
self.class.mappings[input_type] or
|
|
195
|
+
raise("Could not find method for #{input_type.inspect}")
|
|
196
|
+
end
|
|
115
197
|
end
|
|
116
198
|
end
|
|
117
|
-
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Inputs
|
|
3
|
+
class BooleanInput < Base
|
|
4
|
+
def input
|
|
5
|
+
@builder.check_box(attribute_name, input_html_options)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def label_input
|
|
9
|
+
input + (options[:label] == false ? "" : label)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
# Booleans are not required by default because in most of the cases
|
|
15
|
+
# it makes no sense marking them as required. The only exception is
|
|
16
|
+
# Terms of Use usually presented at most sites sign up screen.
|
|
17
|
+
def attribute_required_by_default?
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -13,10 +13,9 @@ module SimpleForm
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def input
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
options[:label_method], input_options, input_html_options)
|
|
16
|
+
label_method, value_method = detect_collection_methods
|
|
17
|
+
@builder.send(:"collection_#{input_type}", attribute_name, collection,
|
|
18
|
+
value_method, label_method, input_options, input_html_options)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def input_options
|
|
@@ -27,6 +26,15 @@ module SimpleForm
|
|
|
27
26
|
|
|
28
27
|
protected
|
|
29
28
|
|
|
29
|
+
def collection
|
|
30
|
+
@collection ||= (options.delete(:collection) || self.class.boolean_collection).to_a
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Select components does not allow the required html tag.
|
|
34
|
+
def has_required?
|
|
35
|
+
super && input_type != :select
|
|
36
|
+
end
|
|
37
|
+
|
|
30
38
|
# Check if :include_blank must be included by default.
|
|
31
39
|
def skip_include_blank?
|
|
32
40
|
(options.keys & [:prompt, :include_blank, :default, :selected]).any? ||
|
|
@@ -38,21 +46,42 @@ module SimpleForm
|
|
|
38
46
|
# on default label and value methods that can be configured through
|
|
39
47
|
# SimpleForm.collection_label_methods and
|
|
40
48
|
# SimpleForm.collection_value_methods.
|
|
41
|
-
def detect_collection_methods
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
def detect_collection_methods
|
|
50
|
+
label, value = options.delete(:label_method), options.delete(:value_method)
|
|
51
|
+
|
|
52
|
+
unless label && value
|
|
53
|
+
common_method_for = detect_common_display_methods
|
|
54
|
+
label ||= common_method_for[:label]
|
|
55
|
+
value ||= common_method_for[:value]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
[label, value]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def detect_common_display_methods
|
|
62
|
+
collection_classes = detect_collection_classes
|
|
63
|
+
|
|
64
|
+
if collection_classes.include?(Array)
|
|
65
|
+
{ :label => :first, :value => :last }
|
|
66
|
+
elsif collection_includes_basic_objects?(collection_classes)
|
|
67
|
+
{ :label => :to_s, :value => :to_s }
|
|
68
|
+
else
|
|
69
|
+
sample = collection.first || collection.last
|
|
70
|
+
|
|
71
|
+
{ :label => SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
|
|
72
|
+
:value => SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
|
|
51
73
|
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def detect_collection_classes
|
|
77
|
+
collection.map { |e| e.class }.uniq
|
|
78
|
+
end
|
|
52
79
|
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
def collection_includes_basic_objects?(collection_classes)
|
|
81
|
+
(collection_classes & [
|
|
82
|
+
String, Integer, Fixnum, Bignum, Float, NilClass, Symbol, TrueClass, FalseClass
|
|
83
|
+
]).any?
|
|
55
84
|
end
|
|
56
85
|
end
|
|
57
86
|
end
|
|
58
|
-
end
|
|
87
|
+
end
|