simple_form 1.3.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 +73 -12
- data/Rakefile +27 -0
- data/lib/generators/simple_form/templates/_form.html.erb +1 -1
- data/lib/generators/simple_form/templates/_form.html.haml +2 -10
- data/lib/generators/simple_form/templates/_form.html.slim +10 -0
- data/lib/generators/simple_form/templates/simple_form.rb +20 -4
- data/lib/simple_form/action_view_extensions/builder.rb +39 -15
- data/lib/simple_form/action_view_extensions/form_helper.rb +5 -5
- data/lib/simple_form/components/errors.rb +5 -1
- data/lib/simple_form/components/labels.rb +2 -2
- data/lib/simple_form/form_builder.rb +131 -38
- data/lib/simple_form/inputs/base.rb +52 -6
- data/lib/simple_form/inputs/collection_input.rb +5 -1
- data/lib/simple_form/inputs/date_time_input.rb +4 -0
- data/lib/simple_form/inputs/mapping_input.rb +0 -6
- data/lib/simple_form/inputs/numeric_input.rb +15 -7
- data/lib/simple_form/inputs/priority_input.rb +5 -1
- data/lib/simple_form/inputs/string_input.rb +14 -3
- data/lib/simple_form/map_type.rb +6 -3
- data/lib/simple_form/version.rb +1 -1
- data/lib/simple_form.rb +28 -1
- data/simple_form.gemspec +22 -0
- data/test/action_view_extensions/builder_test.rb +39 -7
- data/test/action_view_extensions/form_helper_test.rb +12 -0
- data/test/components/label_test.rb +54 -0
- data/test/discovery_inputs.rb +21 -0
- data/test/form_builder_test.rb +230 -0
- data/test/inputs_test.rb +234 -14
- data/test/support/misc_helpers.rb +26 -0
- data/test/support/models.rb +41 -3
- data/test/test_helper.rb +13 -4
- metadata +24 -27
|
@@ -5,13 +5,17 @@ module SimpleForm
|
|
|
5
5
|
extend MapType
|
|
6
6
|
include SimpleForm::Inputs
|
|
7
7
|
|
|
8
|
-
map_type :
|
|
9
|
-
map_type :string, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
|
|
10
|
-
map_type :integer, :decimal, :float,
|
|
11
|
-
map_type :select, :radio, :check_boxes,
|
|
12
|
-
map_type :date, :time, :datetime,
|
|
13
|
-
map_type :country, :time_zone,
|
|
14
|
-
map_type :boolean,
|
|
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
|
|
@@ -85,12 +89,31 @@ module SimpleForm
|
|
|
85
89
|
if block_given?
|
|
86
90
|
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
|
|
87
91
|
else
|
|
88
|
-
|
|
89
|
-
klass.new(self, attribute_name, column, input_type, options).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
|
|
@@ -133,7 +156,7 @@ module SimpleForm
|
|
|
133
156
|
when :belongs_to
|
|
134
157
|
reflection.options[:foreign_key] || :"#{reflection.name}_id"
|
|
135
158
|
when :has_one
|
|
136
|
-
raise ":has_one
|
|
159
|
+
raise ":has_one associations are not supported by f.association"
|
|
137
160
|
else
|
|
138
161
|
if options[:as] == :select
|
|
139
162
|
html_options = options[:input_html] ||= {}
|
|
@@ -141,6 +164,12 @@ module SimpleForm
|
|
|
141
164
|
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
142
165
|
end
|
|
143
166
|
|
|
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
|
+
|
|
144
173
|
:"#{reflection.name.to_s.singularize}_ids"
|
|
145
174
|
end
|
|
146
175
|
|
|
@@ -175,12 +204,29 @@ module SimpleForm
|
|
|
175
204
|
# f.error :name, :id => "cool_error"
|
|
176
205
|
#
|
|
177
206
|
def error(attribute_name, options={})
|
|
178
|
-
options[:error_html] = options
|
|
207
|
+
options[:error_html] = options.dup
|
|
179
208
|
column = find_attribute_column(attribute_name)
|
|
180
209
|
input_type = default_input_type(attribute_name, column, options)
|
|
181
210
|
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
|
|
182
211
|
end
|
|
183
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)
|
|
228
|
+
end
|
|
229
|
+
|
|
184
230
|
# Creates a hint tag for the given attribute. Accepts a symbol indicating
|
|
185
231
|
# an attribute for I18n lookup or a string. All the given options are sent
|
|
186
232
|
# as :hint_html.
|
|
@@ -192,7 +238,7 @@ module SimpleForm
|
|
|
192
238
|
# f.hint "Don't forget to accept this"
|
|
193
239
|
#
|
|
194
240
|
def hint(attribute_name, options={})
|
|
195
|
-
options[:hint_html] = options
|
|
241
|
+
options[:hint_html] = options.dup
|
|
196
242
|
if attribute_name.is_a?(String)
|
|
197
243
|
options[:hint] = attribute_name
|
|
198
244
|
attribute_name, column, input_type = nil, nil, nil
|
|
@@ -219,8 +265,8 @@ module SimpleForm
|
|
|
219
265
|
def label(attribute_name, *args)
|
|
220
266
|
return super if args.first.is_a?(String)
|
|
221
267
|
options = args.extract_options!
|
|
268
|
+
options[:label_html] = options.dup
|
|
222
269
|
options[:label] = options.delete(:label)
|
|
223
|
-
options[:label_html] = options
|
|
224
270
|
options[:required] = options.delete(:required)
|
|
225
271
|
column = find_attribute_column(attribute_name)
|
|
226
272
|
input_type = default_input_type(attribute_name, column, options)
|
|
@@ -250,47 +296,94 @@ module SimpleForm
|
|
|
250
296
|
def default_input_type(attribute_name, column, options) #:nodoc:
|
|
251
297
|
return options[:as].to_sym if options[:as]
|
|
252
298
|
return :select if options[:collection]
|
|
299
|
+
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
|
|
253
300
|
|
|
254
301
|
input_type = column.try(:type)
|
|
255
|
-
|
|
256
302
|
case input_type
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
else
|
|
268
|
-
SimpleForm.input_mappings.find { |match, type|
|
|
269
|
-
attribute_name.to_s =~ match
|
|
270
|
-
}.try(:last) if SimpleForm.input_mappings
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
match || input_type || file_method?(attribute_name) || :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
|
|
274
313
|
else
|
|
275
|
-
input_type
|
|
314
|
+
file_method?(attribute_name) ? :file : (input_type || :string)
|
|
315
|
+
end
|
|
316
|
+
else
|
|
317
|
+
input_type
|
|
276
318
|
end
|
|
277
319
|
end
|
|
278
320
|
|
|
279
|
-
|
|
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
|
+
|
|
280
327
|
def file_method?(attribute_name) #:nodoc:
|
|
281
328
|
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
|
282
|
-
|
|
329
|
+
file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
|
283
330
|
end
|
|
284
331
|
|
|
285
|
-
# Finds the database column for the given attribute
|
|
286
332
|
def find_attribute_column(attribute_name) #:nodoc:
|
|
287
|
-
|
|
333
|
+
if @object.respond_to?(:column_for_attribute)
|
|
334
|
+
@object.column_for_attribute(attribute_name)
|
|
335
|
+
end
|
|
288
336
|
end
|
|
289
337
|
|
|
290
|
-
# Find reflection related to association
|
|
291
338
|
def find_association_reflection(association) #:nodoc:
|
|
292
|
-
|
|
339
|
+
if @object.class.respond_to?(:reflect_on_association)
|
|
340
|
+
@object.class.reflect_on_association(association)
|
|
341
|
+
end
|
|
293
342
|
end
|
|
294
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
|
|
377
|
+
end
|
|
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
|
|
295
388
|
end
|
|
296
389
|
end
|
|
@@ -28,8 +28,9 @@ module SimpleForm
|
|
|
28
28
|
@reflection = options.delete(:reflection)
|
|
29
29
|
@options = options
|
|
30
30
|
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
|
|
31
|
-
o[:required]
|
|
32
|
-
o[:disabled]
|
|
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
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -71,8 +72,17 @@ module SimpleForm
|
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
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
|
+
|
|
74
84
|
def has_validators?
|
|
75
|
-
object.class.respond_to?(:validators_on)
|
|
85
|
+
attribute_name && object.class.respond_to?(:validators_on)
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
def attribute_validators
|
|
@@ -120,6 +130,15 @@ module SimpleForm
|
|
|
120
130
|
# Action is the action being rendered, usually :new or :edit.
|
|
121
131
|
# And attribute is the attribute itself, :name for example.
|
|
122
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
|
+
#
|
|
123
142
|
# Example:
|
|
124
143
|
#
|
|
125
144
|
# simple_form:
|
|
@@ -133,14 +152,36 @@ module SimpleForm
|
|
|
133
152
|
# Take a look at our locale example file.
|
|
134
153
|
def translate(namespace, default='')
|
|
135
154
|
return nil unless SimpleForm.translate
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
lookups
|
|
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
|
|
139
166
|
lookups << :"#{reflection_or_attribute_name}"
|
|
140
167
|
lookups << default
|
|
168
|
+
|
|
141
169
|
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
|
|
142
170
|
end
|
|
143
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
|
|
183
|
+
end
|
|
184
|
+
|
|
144
185
|
# The action to be used in lookup.
|
|
145
186
|
def lookup_action
|
|
146
187
|
action = template.controller.action_name
|
|
@@ -148,6 +189,11 @@ module SimpleForm
|
|
|
148
189
|
action = action.to_sym
|
|
149
190
|
ACTIONS[action] || action
|
|
150
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
|
|
151
197
|
end
|
|
152
198
|
end
|
|
153
199
|
end
|
|
@@ -14,7 +14,6 @@ module SimpleForm
|
|
|
14
14
|
|
|
15
15
|
def input
|
|
16
16
|
label_method, value_method = detect_collection_methods
|
|
17
|
-
|
|
18
17
|
@builder.send(:"collection_#{input_type}", attribute_name, collection,
|
|
19
18
|
value_method, label_method, input_options, input_html_options)
|
|
20
19
|
end
|
|
@@ -31,6 +30,11 @@ module SimpleForm
|
|
|
31
30
|
@collection ||= (options.delete(:collection) || self.class.boolean_collection).to_a
|
|
32
31
|
end
|
|
33
32
|
|
|
33
|
+
# Select components does not allow the required html tag.
|
|
34
|
+
def has_required?
|
|
35
|
+
super && input_type != :select
|
|
36
|
+
end
|
|
37
|
+
|
|
34
38
|
# Check if :include_blank must be included by default.
|
|
35
39
|
def skip_include_blank?
|
|
36
40
|
(options.keys & [:prompt, :include_blank, :default, :selected]).any? ||
|
|
@@ -4,7 +4,6 @@ module SimpleForm
|
|
|
4
4
|
class MappingInput < Base
|
|
5
5
|
extend MapType
|
|
6
6
|
|
|
7
|
-
map_type :password, :to => :password_field
|
|
8
7
|
map_type :text, :to => :text_area
|
|
9
8
|
map_type :file, :to => :file_field
|
|
10
9
|
|
|
@@ -14,11 +13,6 @@ module SimpleForm
|
|
|
14
13
|
|
|
15
14
|
private
|
|
16
15
|
|
|
17
|
-
def input_method
|
|
18
|
-
self.class.mappings[input_type] or
|
|
19
|
-
raise("Could not find method for #{input_type.inspect}")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
16
|
def has_placeholder?
|
|
23
17
|
(text? || password?) && placeholder_present?
|
|
24
18
|
end
|
|
@@ -2,10 +2,10 @@ module SimpleForm
|
|
|
2
2
|
module Inputs
|
|
3
3
|
class NumericInput < Base
|
|
4
4
|
def input
|
|
5
|
-
input_html_options[:type] ||= "number"
|
|
5
|
+
input_html_options[:type] ||= "number" if SimpleForm.html5
|
|
6
6
|
input_html_options[:size] ||= SimpleForm.default_input_size
|
|
7
|
-
input_html_options[:step] ||= 1 if
|
|
8
|
-
infer_attributes_from_validations!
|
|
7
|
+
input_html_options[:step] ||= integer? ? 1 : "any" if SimpleForm.html5
|
|
8
|
+
infer_attributes_from_validations! if SimpleForm.html5
|
|
9
9
|
@builder.text_field(attribute_name, input_html_options)
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -35,23 +35,31 @@ module SimpleForm
|
|
|
35
35
|
|
|
36
36
|
def minimum_value(validator_options)
|
|
37
37
|
if integer? && validator_options.key?(:greater_than)
|
|
38
|
-
validator_options[:greater_than] + 1
|
|
38
|
+
evaluate_validator_option(validator_options[:greater_than]) + 1
|
|
39
39
|
else
|
|
40
|
-
validator_options[:greater_than_or_equal_to]
|
|
40
|
+
evaluate_validator_option(validator_options[:greater_than_or_equal_to])
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def maximum_value(validator_options)
|
|
45
45
|
if integer? && validator_options.key?(:less_than)
|
|
46
|
-
validator_options[:less_than] - 1
|
|
46
|
+
evaluate_validator_option(validator_options[:less_than]) - 1
|
|
47
47
|
else
|
|
48
|
-
validator_options[:less_than_or_equal_to]
|
|
48
|
+
evaluate_validator_option(validator_options[:less_than_or_equal_to])
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def find_numericality_validator
|
|
53
53
|
attribute_validators.find { |v| ActiveModel::Validations::NumericalityValidator === v }
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def evaluate_validator_option(option)
|
|
59
|
+
return option if option.is_a?(Numeric)
|
|
60
|
+
return object.send(option) if option.is_a?(Symbol)
|
|
61
|
+
return option.call(object) if option.respond_to?(:call)
|
|
62
|
+
end
|
|
55
63
|
end
|
|
56
64
|
end
|
|
57
65
|
end
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
3
|
class StringInput < Base
|
|
4
|
+
extend MapType
|
|
5
|
+
|
|
6
|
+
map_type :string, :email, :search, :tel, :url, :to => :text_field
|
|
7
|
+
map_type :password, :to => :password_field
|
|
8
|
+
|
|
4
9
|
def input
|
|
5
10
|
input_html_options[:size] ||= [limit, SimpleForm.default_input_size].compact.min
|
|
6
|
-
input_html_options[:maxlength] ||= limit if limit
|
|
7
|
-
|
|
11
|
+
input_html_options[:maxlength] ||= limit if limit && SimpleForm.html5
|
|
12
|
+
if password? || SimpleForm.html5
|
|
13
|
+
input_html_options[:type] ||= input_type unless string?
|
|
14
|
+
end
|
|
8
15
|
|
|
9
|
-
@builder.
|
|
16
|
+
@builder.send(input_method, attribute_name, input_html_options)
|
|
10
17
|
end
|
|
11
18
|
|
|
12
19
|
def input_html_classes
|
|
@@ -26,6 +33,10 @@ module SimpleForm
|
|
|
26
33
|
def string?
|
|
27
34
|
input_type == :string
|
|
28
35
|
end
|
|
36
|
+
|
|
37
|
+
def password?
|
|
38
|
+
input_type == :password
|
|
39
|
+
end
|
|
29
40
|
end
|
|
30
41
|
end
|
|
31
42
|
end
|
data/lib/simple_form/map_type.rb
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
|
2
|
+
|
|
1
3
|
module SimpleForm
|
|
2
4
|
module MapType
|
|
3
|
-
def
|
|
4
|
-
|
|
5
|
+
def self.extended(base)
|
|
6
|
+
base.class_attribute :mappings
|
|
7
|
+
base.mappings = {}
|
|
5
8
|
end
|
|
6
9
|
|
|
7
10
|
def map_type(*types)
|
|
8
11
|
map_to = types.extract_options![:to]
|
|
9
12
|
raise ArgumentError, "You need to give :to as option to map_type" unless map_to
|
|
10
|
-
types.
|
|
13
|
+
self.mappings = mappings.merge types.each_with_object({}) { |t, m| m[t] = map_to }
|
|
11
14
|
end
|
|
12
15
|
end
|
|
13
16
|
end
|
data/lib/simple_form/version.rb
CHANGED
data/lib/simple_form.rb
CHANGED
|
@@ -61,7 +61,7 @@ module SimpleForm
|
|
|
61
61
|
|
|
62
62
|
# You can wrap each item in a collection of radio/check boxes with a tag, defaulting to none.
|
|
63
63
|
mattr_accessor :item_wrapper_tag
|
|
64
|
-
@@item_wrapper_tag =
|
|
64
|
+
@@item_wrapper_tag = :span
|
|
65
65
|
|
|
66
66
|
# You can wrap all inputs in a pre-defined tag. Default is a div.
|
|
67
67
|
mattr_accessor :wrapper_tag
|
|
@@ -79,10 +79,29 @@ module SimpleForm
|
|
|
79
79
|
mattr_accessor :label_text
|
|
80
80
|
@@label_text = lambda { |label, required| "#{required} #{label}" }
|
|
81
81
|
|
|
82
|
+
# You can define the class to use on all labels. Default is nil.
|
|
83
|
+
mattr_accessor :label_class
|
|
84
|
+
@@label_class = nil
|
|
85
|
+
|
|
86
|
+
# You can define the class to use on all forms. Default is simple_form.
|
|
87
|
+
mattr_accessor :form_class
|
|
88
|
+
@@form_class = :simple_form
|
|
89
|
+
|
|
82
90
|
# Whether attributes are required by default (or not).
|
|
83
91
|
mattr_accessor :required_by_default
|
|
84
92
|
@@required_by_default = true
|
|
85
93
|
|
|
94
|
+
# Tell browsers whether to use default HTML5 validations (novalidate option).
|
|
95
|
+
mattr_accessor :browser_validations
|
|
96
|
+
@@browser_validations = true
|
|
97
|
+
|
|
98
|
+
# Determines whether HTML5 types (:email, :url, :search, :tel) and attributes
|
|
99
|
+
# (e.g. required) are used or not. True by default.
|
|
100
|
+
# Having this on in non-HTML5 compliant sites can cause odd behavior in
|
|
101
|
+
# HTML5-aware browsers such as Chrome.
|
|
102
|
+
mattr_accessor :html5
|
|
103
|
+
@@html5 = true
|
|
104
|
+
|
|
86
105
|
# Collection of methods to detect if a file type was given.
|
|
87
106
|
mattr_accessor :file_methods
|
|
88
107
|
@@file_methods = [ :mounted_as, :file?, :public_filename ]
|
|
@@ -110,6 +129,14 @@ module SimpleForm
|
|
|
110
129
|
mattr_accessor :translate
|
|
111
130
|
@@translate = true
|
|
112
131
|
|
|
132
|
+
# Automatically discover new inputs in Rails' autoload path.
|
|
133
|
+
mattr_accessor :inputs_discovery
|
|
134
|
+
@@inputs_discovery = true
|
|
135
|
+
|
|
136
|
+
# Cache simple form inputs discovery
|
|
137
|
+
mattr_accessor :cache_discovery
|
|
138
|
+
@@cache_discovery = !Rails.env.development?
|
|
139
|
+
|
|
113
140
|
# Default way to setup SimpleForm. Run rails generate simple_form:install
|
|
114
141
|
# to create a fresh initializer with all configuration values.
|
|
115
142
|
def self.setup
|
data/simple_form.gemspec
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "simple_form/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "simple_form"
|
|
7
|
+
s.version = SimpleForm::VERSION.dup
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.summary = "Forms made easy!"
|
|
10
|
+
s.email = "contact@plataformatec.com.br"
|
|
11
|
+
s.homepage = "http://github.com/plataformatec/simple_form"
|
|
12
|
+
s.description = "Forms made easy!"
|
|
13
|
+
s.authors = ['José Valim', 'Carlos Antônio']
|
|
14
|
+
|
|
15
|
+
s.files = `git ls-files`.split("\n")
|
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
|
+
s.test_files -= Dir["test/support/country_select/**/*"]
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
s.rubyforge_project = "simple_form"
|
|
22
|
+
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class BuilderTest < ActionView::TestCase
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
def with_custom_form_for(object, *args, &block)
|
|
5
|
+
with_concat_custom_form_for(object) do |f|
|
|
6
|
+
assert f.instance_of?(CustomFormBuilder)
|
|
7
|
+
yield f
|
|
8
|
+
end
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
def with_collection_radio(object, attribute, collection, value_method, text_method, options={}, html_options={})
|
|
@@ -116,10 +118,17 @@ class BuilderTest < ActionView::TestCase
|
|
|
116
118
|
assert_select 'form li input[type=radio][value=false]#user_active_false'
|
|
117
119
|
end
|
|
118
120
|
|
|
119
|
-
test 'collection radio
|
|
121
|
+
test 'collection radio wrap items in a span tag by default' do
|
|
120
122
|
with_collection_radio @user, :active, [true, false], :to_s, :to_s
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
assert_select 'form span input[type=radio][value=true]#user_active_true + label'
|
|
125
|
+
assert_select 'form span input[type=radio][value=false]#user_active_false + label'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
test 'collection radio does not wrap input inside the label' do
|
|
129
|
+
with_collection_radio @user, :active, [true, false], :to_s, :to_s
|
|
130
|
+
|
|
131
|
+
assert_no_select 'form label input'
|
|
123
132
|
end
|
|
124
133
|
|
|
125
134
|
# COLLECTION CHECK BOX
|
|
@@ -259,10 +268,17 @@ class BuilderTest < ActionView::TestCase
|
|
|
259
268
|
assert_select 'form li input[type=checkbox][value=false]#user_active_false'
|
|
260
269
|
end
|
|
261
270
|
|
|
262
|
-
test 'collection check box
|
|
271
|
+
test 'collection check box wrap items in a span tag by default' do
|
|
272
|
+
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
|
|
273
|
+
|
|
274
|
+
assert_select 'form span input[type=checkbox][value=true]#user_active_true + label'
|
|
275
|
+
assert_select 'form span input[type=checkbox][value=false]#user_active_false + label'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
test 'collection check box does not wrap input inside the label' do
|
|
263
279
|
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
|
|
264
280
|
|
|
265
|
-
assert_no_select 'form
|
|
281
|
+
assert_no_select 'form label input'
|
|
266
282
|
end
|
|
267
283
|
|
|
268
284
|
# SIMPLE FIELDS
|
|
@@ -273,4 +289,20 @@ class BuilderTest < ActionView::TestCase
|
|
|
273
289
|
end
|
|
274
290
|
end
|
|
275
291
|
end
|
|
292
|
+
|
|
293
|
+
test 'fields for yields an instance of CustomBuilder if main builder is a CustomBuilder' do
|
|
294
|
+
with_custom_form_for(:user) do |f|
|
|
295
|
+
f.simple_fields_for(:company) do |company|
|
|
296
|
+
assert company.instance_of?(CustomFormBuilder)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
test 'fields for yields an instance of FormBuilder if it was set in options' do
|
|
302
|
+
with_custom_form_for(:user) do |f|
|
|
303
|
+
f.simple_fields_for(:company, :builder => SimpleForm::FormBuilder) do |company|
|
|
304
|
+
assert company.instance_of?(SimpleForm::FormBuilder)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
276
308
|
end
|