simple_form 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of simple_form might be problematic. Click here for more details.

@@ -39,8 +39,8 @@ module SimpleForm
39
39
  render_collection(
40
40
  attribute, collection, value_method, text_method, options, html_options
41
41
  ) do |value, text, default_html_options|
42
- radio = radio_button(attribute, value, default_html_options)
43
- collection_label(attribute, value, radio, text, :class => "collection_radio")
42
+ radio_button(attribute, value, default_html_options) +
43
+ label(sanitize_attribute_name(attribute, value), text, :class => "collection_radio")
44
44
  end
45
45
  end
46
46
 
@@ -83,8 +83,8 @@ module SimpleForm
83
83
  ) do |value, text, default_html_options|
84
84
  default_html_options[:multiple] = true
85
85
 
86
- check_box = check_box(attribute, default_html_options, value, '')
87
- collection_label(attribute, value, check_box, text, :class => "collection_check_boxes")
86
+ check_box(attribute, default_html_options, value, '') +
87
+ label(sanitize_attribute_name(attribute, value), text, :class => "collection_check_boxes")
88
88
  end
89
89
  end
90
90
 
@@ -99,17 +99,16 @@ module SimpleForm
99
99
  # end
100
100
  def simple_fields_for(*args, &block)
101
101
  options = args.extract_options!
102
- options[:builder] = SimpleForm::FormBuilder
102
+ if self.class < ActionView::Helpers::FormBuilder
103
+ options[:builder] ||= self.class
104
+ else
105
+ options[:builder] ||= SimpleForm::FormBuilder
106
+ end
103
107
  fields_for(*(args << options), &block)
104
108
  end
105
109
 
106
110
  private
107
111
 
108
- # Wraps the given component in a label, for better accessibility with collections.
109
- def collection_label(attribute, value, component_tag, label_text, html_options) #:nodoc:
110
- label(sanitize_attribute_name(attribute, value), component_tag << label_text.to_s, html_options)
111
- end
112
-
113
112
  # Generate default options for collection helpers, such as :checked and
114
113
  # :disabled.
115
114
  def default_html_options_for_collection(item, value, options, html_options) #:nodoc:
@@ -40,7 +40,8 @@ module SimpleForm
40
40
  else dom_class(record_or_name_or_array)
41
41
  end
42
42
  options[:html] ||= {}
43
- options[:html][:class] = "simple_form \#{css_class} \#{options[:html][:class]}".strip
43
+ options[:html][:novalidate] = !SimpleForm.browser_validations
44
+ options[:html][:class] = "\#{SimpleForm.form_class} \#{css_class} \#{options[:html][:class]}".strip
44
45
 
45
46
  with_custom_field_error_proc do
46
47
  #{helper}(record_or_name_or_array, *(args << options), &block)
@@ -12,7 +12,11 @@ module SimpleForm
12
12
  end
13
13
 
14
14
  def error_text
15
- errors.send(error_method)
15
+ if options[:error_prefix]
16
+ options[:error_prefix] + " " + errors.send(error_method)
17
+ else
18
+ errors.send(error_method)
19
+ end
16
20
  end
17
21
 
18
22
  def error_method
@@ -36,7 +36,7 @@ module SimpleForm
36
36
  end
37
37
 
38
38
  def label_html_options
39
- label_options = html_options_for(:label, [input_type, required_class])
39
+ label_options = html_options_for(:label, [input_type, required_class, SimpleForm.label_class])
40
40
  label_options[:for] = options[:input_html][:id] if options.key?(:input_html) && options[:input_html].key?(:id)
41
41
  label_options
42
42
  end
@@ -5,13 +5,17 @@ module SimpleForm
5
5
  extend MapType
6
6
  include SimpleForm::Inputs
7
7
 
8
- map_type :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
9
- map_type :string, :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
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
- klass = self.class.mappings[input_type] || self.class.const_get("#{input_type.to_s.camelize}Input")
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 association are not supported by f.association"
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
 
@@ -181,6 +210,23 @@ module SimpleForm
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.
@@ -254,48 +300,90 @@ module SimpleForm
254
300
 
255
301
  input_type = column.try(:type)
256
302
  case input_type
257
- when :timestamp
258
- :datetime
259
- when :string, nil
260
- match = case attribute_name.to_s
261
- when /password/ then :password
262
- when /time_zone/ then :time_zone
263
- when /country/ then :country
264
- when /email/ then :email
265
- when /phone/ then :tel
266
- when /url/ then :url
267
- end
268
-
269
- 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
270
313
  else
271
- input_type
314
+ file_method?(attribute_name) ? :file : (input_type || :string)
315
+ end
316
+ else
317
+ input_type
272
318
  end
273
319
  end
274
320
 
275
- def find_custom_type(attribute_name)
321
+ def find_custom_type(attribute_name) #:nodoc:
276
322
  SimpleForm.input_mappings.find { |match, type|
277
323
  attribute_name =~ match
278
324
  }.try(:last) if SimpleForm.input_mappings
279
325
  end
280
326
 
281
- # Checks if attribute is a file_method.
282
327
  def file_method?(attribute_name) #:nodoc:
283
328
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
284
- :file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
329
+ file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
285
330
  end
286
331
 
287
- # Finds the database column for the given attribute.
288
332
  def find_attribute_column(attribute_name) #:nodoc:
289
333
  if @object.respond_to?(:column_for_attribute)
290
334
  @object.column_for_attribute(attribute_name)
291
335
  end
292
336
  end
293
337
 
294
- # Find reflection related to association.
295
338
  def find_association_reflection(association) #:nodoc:
296
339
  if @object.class.respond_to?(:reflect_on_association)
297
340
  @object.class.reflect_on_association(association)
298
341
  end
299
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
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
300
388
  end
301
389
  end
@@ -28,9 +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] = true if has_required?
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
32
  o[:disabled] = true if disabled?
33
- o[:autofocus] = true if has_autofocus?
33
+ o[:autofocus] = true if has_autofocus? && SimpleForm.html5
34
34
  end
35
35
  end
36
36
 
@@ -74,7 +74,7 @@ module SimpleForm
74
74
 
75
75
  # Whether this input is valid for HTML 5 required attribute.
76
76
  def has_required?
77
- attribute_required?
77
+ attribute_required? && SimpleForm.html5
78
78
  end
79
79
 
80
80
  def has_autofocus?
@@ -130,6 +130,15 @@ module SimpleForm
130
130
  # Action is the action being rendered, usually :new or :edit.
131
131
  # And attribute is the attribute itself, :name for example.
132
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
+ #
133
142
  # Example:
134
143
  #
135
144
  # simple_form:
@@ -143,14 +152,36 @@ module SimpleForm
143
152
  # Take a look at our locale example file.
144
153
  def translate(namespace, default='')
145
154
  return nil unless SimpleForm.translate
146
- lookups = []
147
- lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
148
- lookups << :"#{object_name}.#{reflection_or_attribute_name}"
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
149
166
  lookups << :"#{reflection_or_attribute_name}"
150
167
  lookups << default
168
+
151
169
  I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
152
170
  end
153
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
+
154
185
  # The action to be used in lookup.
155
186
  def lookup_action
156
187
  action = template.controller.action_name
@@ -158,6 +189,11 @@ module SimpleForm
158
189
  action = action.to_sym
159
190
  ACTIONS[action] || action
160
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
161
197
  end
162
198
  end
163
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
@@ -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 integer?
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