simple_form 1.5.2 → 2.0.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.
Files changed (108) hide show
  1. data/CHANGELOG.md +234 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +816 -0
  4. data/lib/generators/simple_form/install_generator.rb +15 -1
  5. data/lib/generators/simple_form/templates/README +12 -0
  6. data/lib/generators/simple_form/templates/_form.html.erb +2 -2
  7. data/lib/generators/simple_form/templates/_form.html.haml +2 -2
  8. data/lib/generators/simple_form/templates/_form.html.slim +4 -4
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +176 -0
  10. data/lib/simple_form/action_view_extensions/builder.rb +206 -59
  11. data/lib/simple_form/action_view_extensions/form_helper.rb +30 -23
  12. data/lib/simple_form/components/errors.rb +6 -24
  13. data/lib/simple_form/components/hints.rb +7 -21
  14. data/lib/simple_form/components/html5.rb +26 -0
  15. data/lib/simple_form/components/labels.rb +22 -14
  16. data/lib/simple_form/components/maxlength.rb +41 -0
  17. data/lib/simple_form/components/min_max.rb +49 -0
  18. data/lib/simple_form/components/pattern.rb +34 -0
  19. data/lib/simple_form/components/placeholders.rb +5 -17
  20. data/lib/simple_form/components/readonly.rb +22 -0
  21. data/lib/simple_form/components.rb +11 -1
  22. data/lib/simple_form/core_ext/hash.rb +16 -0
  23. data/lib/simple_form/error_notification.rb +9 -3
  24. data/lib/simple_form/form_builder.rb +105 -28
  25. data/lib/simple_form/helpers/autofocus.rb +11 -0
  26. data/lib/simple_form/helpers/disabled.rb +15 -0
  27. data/lib/simple_form/helpers/readonly.rb +15 -0
  28. data/lib/simple_form/helpers/required.rb +10 -11
  29. data/lib/simple_form/helpers/validators.rb +4 -4
  30. data/lib/simple_form/helpers.rb +7 -4
  31. data/lib/simple_form/inputs/base.rb +53 -81
  32. data/lib/simple_form/inputs/boolean_input.rb +46 -4
  33. data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
  34. data/lib/simple_form/inputs/collection_input.rb +27 -13
  35. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +67 -0
  36. data/lib/simple_form/inputs/collection_select_input.rb +14 -0
  37. data/lib/simple_form/inputs/date_time_input.rb +10 -6
  38. data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
  39. data/lib/simple_form/inputs/hidden_input.rb +3 -6
  40. data/lib/simple_form/inputs/numeric_input.rb +3 -51
  41. data/lib/simple_form/inputs/password_input.rb +1 -2
  42. data/lib/simple_form/inputs/priority_input.rb +2 -2
  43. data/lib/simple_form/inputs/range_input.rb +1 -3
  44. data/lib/simple_form/inputs/string_input.rb +6 -8
  45. data/lib/simple_form/inputs/text_input.rb +1 -2
  46. data/lib/simple_form/inputs.rb +17 -13
  47. data/lib/simple_form/version.rb +1 -1
  48. data/lib/simple_form/wrappers/builder.rb +103 -0
  49. data/lib/simple_form/wrappers/many.rb +69 -0
  50. data/lib/simple_form/wrappers/root.rb +34 -0
  51. data/lib/simple_form/wrappers/single.rb +18 -0
  52. data/lib/simple_form/wrappers.rb +8 -0
  53. data/lib/simple_form.rb +118 -48
  54. data/test/action_view_extensions/builder_test.rb +285 -102
  55. data/test/action_view_extensions/form_helper_test.rb +32 -10
  56. data/test/components/label_test.rb +44 -5
  57. data/test/form_builder/association_test.rb +177 -0
  58. data/test/form_builder/button_test.rb +47 -0
  59. data/test/{error_notification_test.rb → form_builder/error_notification_test.rb} +18 -1
  60. data/test/form_builder/error_test.rb +121 -0
  61. data/test/form_builder/general_test.rb +356 -0
  62. data/test/form_builder/hint_test.rb +123 -0
  63. data/test/form_builder/input_field_test.rb +63 -0
  64. data/test/form_builder/label_test.rb +65 -0
  65. data/test/form_builder/wrapper_test.rb +149 -0
  66. data/test/generators/simple_form_generator_test.rb +32 -0
  67. data/test/inputs/boolean_input_test.rb +101 -0
  68. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  69. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  70. data/test/inputs/collection_select_input_test.rb +241 -0
  71. data/test/inputs/datetime_input_test.rb +99 -0
  72. data/test/inputs/disabled_test.rb +38 -0
  73. data/test/inputs/discovery_test.rb +61 -0
  74. data/test/inputs/file_input_test.rb +16 -0
  75. data/test/inputs/general_test.rb +69 -0
  76. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  77. data/test/inputs/hidden_input_test.rb +30 -0
  78. data/test/inputs/numeric_input_test.rb +167 -0
  79. data/test/inputs/priority_input_test.rb +43 -0
  80. data/test/inputs/readonly_test.rb +61 -0
  81. data/test/inputs/required_test.rb +113 -0
  82. data/test/inputs/string_input_test.rb +140 -0
  83. data/test/inputs/text_input_test.rb +24 -0
  84. data/test/support/misc_helpers.rb +53 -12
  85. data/test/support/mock_controller.rb +2 -2
  86. data/test/support/models.rb +20 -5
  87. data/test/test_helper.rb +11 -12
  88. metadata +124 -96
  89. data/.gitignore +0 -3
  90. data/.gitmodules +0 -3
  91. data/.travis.yml +0 -15
  92. data/CHANGELOG.rdoc +0 -159
  93. data/Gemfile +0 -9
  94. data/README.rdoc +0 -466
  95. data/Rakefile +0 -27
  96. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +0 -93
  97. data/lib/simple_form/components/wrapper.rb +0 -38
  98. data/lib/simple_form/helpers/has_errors.rb +0 -15
  99. data/lib/simple_form/helpers/maxlength.rb +0 -24
  100. data/lib/simple_form/helpers/pattern.rb +0 -28
  101. data/simple_form.gemspec +0 -25
  102. data/test/components/error_test.rb +0 -56
  103. data/test/components/hint_test.rb +0 -74
  104. data/test/components/wrapper_test.rb +0 -63
  105. data/test/custom_components.rb +0 -7
  106. data/test/form_builder_test.rb +0 -930
  107. data/test/inputs_test.rb +0 -995
  108. /data/test/{discovery_inputs.rb → support/discovery_inputs.rb} +0 -0
@@ -1,6 +1,14 @@
1
+ require 'simple_form/core_ext/hash'
2
+
1
3
  module SimpleForm
2
4
  class FormBuilder < ActionView::Helpers::FormBuilder
3
- attr_reader :template, :object_name, :object
5
+ attr_reader :template, :object_name, :object, :wrapper
6
+
7
+ # When action is create or update, we still should use new and edit
8
+ ACTIONS = {
9
+ :create => :new,
10
+ :update => :edit
11
+ }
4
12
 
5
13
  extend MapType
6
14
  include SimpleForm::Inputs
@@ -11,7 +19,10 @@ module SimpleForm
11
19
  map_type :password, :to => SimpleForm::Inputs::PasswordInput
12
20
  map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
13
21
  map_type :range, :to => SimpleForm::Inputs::RangeInput
14
- map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
22
+ map_type :check_boxes, :to => SimpleForm::Inputs::CollectionCheckBoxesInput
23
+ map_type :radio_buttons, :to => SimpleForm::Inputs::CollectionRadioButtonsInput
24
+ map_type :select, :to => SimpleForm::Inputs::CollectionSelectInput
25
+ map_type :grouped_select, :to => SimpleForm::Inputs::GroupedCollectionSelectInput
15
26
  map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
16
27
  map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
17
28
  map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
@@ -20,6 +31,12 @@ module SimpleForm
20
31
  @discovery_cache ||= {}
21
32
  end
22
33
 
34
+ def initialize(*) #:nodoc:
35
+ super
36
+ @defaults = options[:defaults]
37
+ @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
38
+ end
39
+
23
40
  # Basic input helper, combines all components in the stack to generate
24
41
  # input html based on options the user define and some guesses through
25
42
  # database column information. By default a call to input will generate
@@ -71,8 +88,8 @@ module SimpleForm
71
88
  #
72
89
  # == Collection
73
90
  #
74
- # When playing with collections (:radio and :select inputs), you have three extra
75
- # options:
91
+ # When playing with collections (:radio_buttons, :check_boxes and :select
92
+ # inputs), you have three extra options:
76
93
  #
77
94
  # :collection => use to determine the collection to generate the radio or select
78
95
  #
@@ -86,14 +103,16 @@ module SimpleForm
86
103
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
87
104
  #
88
105
  def input(attribute_name, options={}, &block)
89
- column = find_attribute_column(attribute_name)
90
- input_type = default_input_type(attribute_name, column, options)
106
+ options = @defaults.deep_dup.deep_merge(options) if @defaults
91
107
 
92
- if block_given?
93
- SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
94
- else
95
- find_mapping(input_type).new(self, attribute_name, column, input_type, options).render
96
- end
108
+ chosen =
109
+ if name = options[:wrapper]
110
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
111
+ else
112
+ wrapper
113
+ end
114
+
115
+ chosen.render find_input(attribute_name, options, &block)
97
116
  end
98
117
  alias :attribute :input
99
118
 
@@ -112,12 +131,9 @@ module SimpleForm
112
131
  # name="user[name]" size="100" type="text" value="Carlos" />
113
132
  #
114
133
  def input_field(attribute_name, options={})
115
- column = find_attribute_column(attribute_name)
116
- input_type = default_input_type(attribute_name, column, options)
117
-
134
+ options = options.dup
118
135
  options[:input_html] = options.except(:as, :collection, :label_method, :value_method)
119
-
120
- find_mapping(input_type).new(self, attribute_name, column, input_type, options).input
136
+ SimpleForm::Wrappers::Root.new([:input], :wrapper => false).render find_input(attribute_name, options)
121
137
  end
122
138
 
123
139
  # Helper for dealing with association selects/radios, generating the
@@ -147,6 +163,8 @@ module SimpleForm
147
163
  # From the options above, only :collection can also be supplied.
148
164
  #
149
165
  def association(association, options={}, &block)
166
+ options = options.dup
167
+
150
168
  return simple_fields_for(*[association,
151
169
  options.delete(:collection), options].compact, &block) if block_given?
152
170
 
@@ -160,7 +178,7 @@ module SimpleForm
160
178
 
161
179
  attribute = case reflection.macro
162
180
  when :belongs_to
163
- reflection.options[:foreign_key] || :"#{reflection.name}_id"
181
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
164
182
  when :has_one
165
183
  raise ":has_one associations are not supported by f.association"
166
184
  else
@@ -188,11 +206,15 @@ module SimpleForm
188
206
  # f.button :submit
189
207
  # end
190
208
  #
191
- # It just acts as a proxy to method name given.
209
+ # It just acts as a proxy to method name given. We also alias original Rails
210
+ # button implementation (3.2 forward (to delegate to the original when
211
+ # calling `f.button :button`.
192
212
  #
213
+ # TODO: remove if condition when supporting only Rails 3.2 forward.
214
+ alias_method :button_button, :button if method_defined?(:button)
193
215
  def button(type, *args, &block)
194
- options = args.extract_options!
195
- options[:class] = "button #{options[:class]}".strip
216
+ options = args.extract_options!.dup
217
+ options[:class] = [SimpleForm.button_class, options[:class]].compact
196
218
  args << options
197
219
  if respond_to?("#{type}_button")
198
220
  send("#{type}_button", *args, &block)
@@ -210,10 +232,13 @@ module SimpleForm
210
232
  # f.error :name, :id => "cool_error"
211
233
  #
212
234
  def error(attribute_name, options={})
235
+ options = options.dup
236
+
213
237
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
214
238
  column = find_attribute_column(attribute_name)
215
239
  input_type = default_input_type(attribute_name, column, options)
216
- SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
240
+ wrapper.find(:error).
241
+ render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
217
242
  end
218
243
 
219
244
  # Return the error but also considering its name. This is used
@@ -224,6 +249,8 @@ module SimpleForm
224
249
  # f.full_error :token #=> <span class="error">Token is invalid</span>
225
250
  #
226
251
  def full_error(attribute_name, options={})
252
+ options = options.dup
253
+
227
254
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
228
255
  object.class.human_attribute_name(attribute_name.to_s)
229
256
  else
@@ -244,7 +271,9 @@ module SimpleForm
244
271
  # f.hint "Don't forget to accept this"
245
272
  #
246
273
  def hint(attribute_name, options={})
247
- options[:hint_html] = options.except(:hint_tag)
274
+ options = options.dup
275
+
276
+ options[:hint_html] = options.except(:hint_tag, :hint)
248
277
  if attribute_name.is_a?(String)
249
278
  options[:hint] = attribute_name
250
279
  attribute_name, column, input_type = nil, nil, nil
@@ -252,7 +281,9 @@ module SimpleForm
252
281
  column = find_attribute_column(attribute_name)
253
282
  input_type = default_input_type(attribute_name, column, options)
254
283
  end
255
- SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
284
+
285
+ wrapper.find(:hint).
286
+ render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
256
287
  end
257
288
 
258
289
  # Creates a default label tag for the given attribute. You can give a label
@@ -270,10 +301,10 @@ module SimpleForm
270
301
  #
271
302
  def label(attribute_name, *args)
272
303
  return super if args.first.is_a?(String) || block_given?
273
- options = args.extract_options!
274
- options[:label_html] = options.dup
275
- options[:label] = options.delete(:label)
276
- options[:required] = options.delete(:required)
304
+
305
+ options = args.extract_options!.dup
306
+ options[:label_html] = options.except(:label, :required)
307
+
277
308
  column = find_attribute_column(attribute_name)
278
309
  input_type = default_input_type(attribute_name, column, options)
279
310
  SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
@@ -294,7 +325,53 @@ module SimpleForm
294
325
  SimpleForm::ErrorNotification.new(self, options).render
295
326
  end
296
327
 
297
- private
328
+ # Extract the model names from the object_name mess, ignoring numeric and
329
+ # explicit child indexes.
330
+ #
331
+ # Example:
332
+ #
333
+ # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
334
+ # ["route", "blocks", "blocks_learning_object", "foo"]
335
+ #
336
+ def lookup_model_names
337
+ @lookup_model_names ||= begin
338
+ child_index = options[:child_index]
339
+ names = object_name.to_s.scan(/([a-zA-Z_]+)/).flatten
340
+ names.delete(child_index) if child_index
341
+ names.each { |name| name.gsub!('_attributes', '') }
342
+ names.freeze
343
+ end
344
+ end
345
+
346
+ # The action to be used in lookup.
347
+ def lookup_action
348
+ @lookup_action ||= begin
349
+ action = template.controller.action_name
350
+ return unless action
351
+ action = action.to_sym
352
+ ACTIONS[action] || action
353
+ end
354
+ end
355
+
356
+ private
357
+
358
+ # Find an input based on the attribute name.
359
+ def find_input(attribute_name, options={}, &block) #:nodoc:
360
+ column = find_attribute_column(attribute_name)
361
+ input_type = default_input_type(attribute_name, column, options)
362
+
363
+ if input_type == :radio
364
+ SimpleForm.deprecation_warn "Using `:as => :radio` as input type is " \
365
+ "deprecated, please change it to `:as => :radio_buttons`."
366
+ input_type = :radio_buttons
367
+ end
368
+
369
+ if block_given?
370
+ SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
371
+ else
372
+ find_mapping(input_type).new(self, attribute_name, column, input_type, options)
373
+ end
374
+ end
298
375
 
299
376
  # Attempt to guess the better input type given the defined options. By
300
377
  # default alwayls fallback to the user :as option, or to a :select when a
@@ -0,0 +1,11 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Autofocus
4
+ private
5
+
6
+ def has_autofocus?
7
+ options[:autofocus] == true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Disabled
4
+ private
5
+
6
+ def has_disabled?
7
+ options[:disabled] == true
8
+ end
9
+
10
+ def disabled_class
11
+ :disabled if has_disabled?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Readonly
4
+ private
5
+
6
+ def readonly_class
7
+ :readonly if has_readonly?
8
+ end
9
+
10
+ def has_readonly?
11
+ options[:readonly] == true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,7 +3,7 @@ module SimpleForm
3
3
  module Required
4
4
  private
5
5
 
6
- def attribute_required?
6
+ def required_field?
7
7
  @required
8
8
  end
9
9
 
@@ -11,26 +11,25 @@ module SimpleForm
11
11
  if !options[:required].nil?
12
12
  options[:required]
13
13
  elsif has_validators?
14
- (attribute_validators + reflection_validators).any? do |v|
15
- v.kind == :presence && valid_validator?(v)
16
- end
14
+ required_by_validators?
17
15
  else
18
- attribute_required_by_default?
16
+ required_by_default?
19
17
  end
20
18
  end
21
19
 
22
- # Whether this input is valid for HTML 5 required attribute.
23
- def has_required?
24
- attribute_required? && SimpleForm.html5 && SimpleForm.browser_validations
20
+ def required_by_validators?
21
+ (attribute_validators + reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
25
22
  end
26
23
 
27
- def attribute_required_by_default?
24
+ def required_by_default?
28
25
  SimpleForm.required_by_default
29
26
  end
30
27
 
28
+ # Do not use has_required? because we want to add the class
29
+ # regardless of the required option.
31
30
  def required_class
32
- attribute_required? ? :required : :optional
31
+ required_field? ? :required : :optional
33
32
  end
34
33
  end
35
34
  end
36
- end
35
+ end
@@ -1,12 +1,12 @@
1
1
  module SimpleForm
2
2
  module Helpers
3
3
  module Validators
4
- private
5
-
6
4
  def has_validators?
7
- attribute_name && object.class.respond_to?(:validators_on)
5
+ @has_validators ||= attribute_name && object.class.respond_to?(:validators_on)
8
6
  end
9
7
 
8
+ private
9
+
10
10
  def attribute_validators
11
11
  object.class.validators_on(attribute_name)
12
12
  end
@@ -37,7 +37,7 @@ module SimpleForm
37
37
  end
38
38
 
39
39
  def find_validator(validator)
40
- attribute_validators.find { |v| validator === v }
40
+ attribute_validators.find { |v| validator === v } if has_validators?
41
41
  end
42
42
  end
43
43
  end
@@ -1,9 +1,12 @@
1
1
  module SimpleForm
2
+ # Helpers are made of several helpers that cannot be turned on automatically.
3
+ # For instance, disabled cannot be turned on automatically, it requires the
4
+ # user to explicitly pass the option :disabled => true so it may work.
2
5
  module Helpers
3
- autoload :HasErrors, 'simple_form/helpers/has_errors'
4
- autoload :Maxlength, 'simple_form/helpers/maxlength'
5
- autoload :Pattern, 'simple_form/helpers/pattern'
6
- autoload :Validators, 'simple_form/helpers/validators'
6
+ autoload :Autofocus, 'simple_form/helpers/autofocus'
7
+ autoload :Disabled, 'simple_form/helpers/disabled'
8
+ autoload :Readonly, 'simple_form/helpers/readonly'
7
9
  autoload :Required, 'simple_form/helpers/required'
10
+ autoload :Validators, 'simple_form/helpers/validators'
8
11
  end
9
12
  end
@@ -3,49 +3,71 @@ module SimpleForm
3
3
  class Base
4
4
  extend I18nCache
5
5
 
6
- # When action is create or update, we still should use new and edit
7
- ACTIONS = {
8
- :create => :new,
9
- :update => :edit
10
- }
11
-
6
+ include SimpleForm::Helpers::Autofocus
7
+ include SimpleForm::Helpers::Disabled
8
+ include SimpleForm::Helpers::Readonly
12
9
  include SimpleForm::Helpers::Required
13
10
  include SimpleForm::Helpers::Validators
14
- include SimpleForm::Helpers::Maxlength
15
- include SimpleForm::Helpers::Pattern
16
11
 
17
12
  include SimpleForm::Components::Errors
18
13
  include SimpleForm::Components::Hints
14
+ include SimpleForm::Components::HTML5
19
15
  include SimpleForm::Components::LabelInput
16
+ include SimpleForm::Components::Maxlength
17
+ include SimpleForm::Components::MinMax
18
+ include SimpleForm::Components::Pattern
20
19
  include SimpleForm::Components::Placeholders
21
- include SimpleForm::Components::Wrapper
20
+ include SimpleForm::Components::Readonly
21
+
22
+ attr_reader :attribute_name, :column, :input_type, :reflection,
23
+ :options, :input_html_options, :input_html_classes, :html_classes
24
+
25
+ delegate :template, :object, :object_name, :lookup_model_names, :lookup_action, :to => :@builder
22
26
 
23
- # Enables certain components support to the given input.
24
- def self.enable(*args)
25
- args.each { |m| alias_method m, :"enabled_#{m}" }
27
+ class_attribute :default_options
28
+ self.default_options = {}
29
+
30
+ def self.enable(*keys)
31
+ options = self.default_options.dup
32
+ keys.each { |key| options.delete(key) }
33
+ self.default_options = options
26
34
  end
27
35
 
28
- def self.disable(*args)
29
- args.each { |m| alias_method m, :"disabled_#{m}" }
36
+ def self.disable(*keys)
37
+ options = self.default_options.dup
38
+ keys.each { |key| options[key] = false }
39
+ self.default_options = options
30
40
  end
31
41
 
32
- attr_reader :attribute_name, :column, :input_type, :reflection,
33
- :options, :input_html_options
42
+ # Always enabled.
43
+ enable :hint
34
44
 
35
- delegate :template, :object, :object_name, :to => :@builder
45
+ # Usually disabled, needs to be enabled explicitly passing true as option.
46
+ disable :maxlength, :placeholder, :pattern, :min_max
36
47
 
37
48
  def initialize(builder, attribute_name, column, input_type, options = {})
49
+ super
50
+
38
51
  @builder = builder
39
52
  @attribute_name = attribute_name
40
53
  @column = column
41
54
  @input_type = input_type
42
55
  @reflection = options.delete(:reflection)
43
- @options = options
56
+ @options = options.reverse_merge!(self.class.default_options)
44
57
  @required = calculate_required
58
+
59
+ # Notice that html_options_for receives a reference to input_html_classes.
60
+ # This means that classes added dynamically to input_html_classes will
61
+ # still propagate to input_html_options.
62
+ @html_classes = SimpleForm.additional_classes_for(:input) {
63
+ [input_type, required_class, readonly_class, disabled_class].compact
64
+ }
65
+
66
+ @input_html_classes = @html_classes.dup
45
67
  @input_html_options = html_options_for(:input, input_html_classes).tap do |o|
46
- o[:required] = true if has_required?
47
- o[:disabled] = true if disabled?
48
- o[:autofocus] = true if has_autofocus? && SimpleForm.html5
68
+ o[:readonly] = true if has_readonly?
69
+ o[:disabled] = true if has_disabled?
70
+ o[:autofocus] = true if has_autofocus?
49
71
  end
50
72
  end
51
73
 
@@ -57,20 +79,6 @@ module SimpleForm
57
79
  options
58
80
  end
59
81
 
60
- def input_html_classes
61
- [input_type, required_class]
62
- end
63
-
64
- def render
65
- content = "".html_safe
66
- components_list.each do |component|
67
- next if options[component] == false
68
- rendered = send(component)
69
- content.safe_concat rendered.to_s if rendered
70
- end
71
- wrap(content)
72
- end
73
-
74
82
  private
75
83
 
76
84
  def add_size!
@@ -81,17 +89,8 @@ module SimpleForm
81
89
  column && column.limit
82
90
  end
83
91
 
84
- def components_list
85
- if components = options[:components]
86
- ActiveSupport::Deprecation.warn "The option :components of f.input is deprecated. Please turn off each component individually instead."
87
- components
88
- else
89
- SimpleForm.components
90
- end
91
- end
92
-
93
- def has_autofocus?
94
- options[:autofocus]
92
+ def nested_boolean_style?
93
+ options.fetch(:boolean_style, SimpleForm.boolean_style) == :nested
95
94
  end
96
95
 
97
96
  # Find reflection name when available, otherwise use attribute
@@ -100,22 +99,19 @@ module SimpleForm
100
99
  end
101
100
 
102
101
  # Retrieve options for the given namespace from the options hash
103
- def html_options_for(namespace, extra)
102
+ def html_options_for(namespace, css_classes)
104
103
  html_options = options[:"#{namespace}_html"] || {}
105
- html_options[:class] = (extra << html_options[:class]).join(' ').strip if extra.present?
104
+ css_classes << html_options[:class] if html_options.key?(:class)
105
+ html_options[:class] = css_classes
106
106
  html_options
107
107
  end
108
108
 
109
- def disabled?
110
- options[:disabled] == true
111
- end
112
-
113
109
  # Lookup translations for the given namespace using I18n, based on object name,
114
110
  # actual action and attribute name. Lookup priority as follows:
115
111
  #
116
112
  # simple_form.{namespace}.{model}.{action}.{attribute}
117
113
  # simple_form.{namespace}.{model}.{attribute}
118
- # simple_form.{namespace}.{attribute}
114
+ # simple_form.{namespace}.defaults.{attribute}
119
115
  #
120
116
  # Namespace is used for :labels and :hints.
121
117
  #
@@ -130,7 +126,7 @@ module SimpleForm
130
126
  # simple_form.{namespace}.{model}.{nested}.{attribute}
131
127
  # simple_form.{namespace}.{nested}.{action}.{attribute}
132
128
  # simple_form.{namespace}.{nested}.{attribute}
133
- # simple_form.{namespace}.{attribute}
129
+ # simple_form.{namespace}.defaults.{attribute}
134
130
  #
135
131
  # Example:
136
132
  #
@@ -144,9 +140,7 @@ module SimpleForm
144
140
  #
145
141
  # Take a look at our locale example file.
146
142
  def translate(namespace, default='')
147
- return nil unless SimpleForm.translate
148
-
149
- model_names = lookup_model_names
143
+ model_names = lookup_model_names.dup
150
144
  lookups = []
151
145
 
152
146
  while !model_names.empty?
@@ -156,34 +150,12 @@ module SimpleForm
156
150
  lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
157
151
  lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
158
152
  end
159
- lookups << :"#{reflection_or_attribute_name}"
153
+ lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}"
154
+ lookups << :"defaults.#{attribute_name}"
160
155
  lookups << default
161
156
 
162
157
  I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
163
158
  end
164
-
165
- # Extract the model names from the object_name mess, ignoring numeric and
166
- # explicit child indexes.
167
- #
168
- # Example:
169
- #
170
- # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
171
- # ["route", "blocks", "blocks_learning_object", "foo"]
172
- #
173
- def lookup_model_names
174
- child_index = @builder.options[:child_index]
175
- names = object_name.to_s.scan(/([a-zA-Z_]+)/).flatten
176
- names.delete(child_index) if child_index
177
- names.each { |name| name.gsub!('_attributes', '') }
178
- end
179
-
180
- # The action to be used in lookup.
181
- def lookup_action
182
- action = template.controller.action_name
183
- return unless action
184
- action = action.to_sym
185
- ACTIONS[action] || action
186
- end
187
159
  end
188
160
  end
189
161
  end
@@ -2,21 +2,63 @@ module SimpleForm
2
2
  module Inputs
3
3
  class BooleanInput < Base
4
4
  def input
5
- @builder.check_box(attribute_name, input_html_options)
5
+ if nested_boolean_style?
6
+ build_hidden_field_for_checkbox +
7
+ template.label_tag(nil, :class => "checkbox") {
8
+ build_check_box_without_hidden_field
9
+ }
10
+ else
11
+ build_check_box
12
+ end
6
13
  end
7
14
 
8
15
  def label_input
9
- input + (options[:label] == false ? "" : label)
16
+ if options[:label] == false
17
+ input
18
+ elsif nested_boolean_style?
19
+ html_options = label_html_options.dup
20
+ html_options[:class].push(:checkbox)
21
+
22
+ build_hidden_field_for_checkbox +
23
+ @builder.label(label_target, html_options) {
24
+ build_check_box_without_hidden_field + label_text
25
+ }
26
+ else
27
+ input + label
28
+ end
10
29
  end
11
30
 
12
31
  private
13
32
 
33
+ # Build a checkbox tag using default unchecked value. This allows us to
34
+ # reuse the method for nested boolean style, but with no unchecked value,
35
+ # which won't generate the hidden checkbox. This is the default functionality
36
+ # in Rails > 3.2.1, and is backported in SimpleForm AV helpers.
37
+ def build_check_box(unchecked_value='0')
38
+ @builder.check_box(attribute_name, input_html_options, '1', unchecked_value)
39
+ end
40
+
41
+ # Build a checkbox without generating the hidden field. See
42
+ # #build_hidden_field_for_checkbox for more info.
43
+ def build_check_box_without_hidden_field
44
+ build_check_box(nil)
45
+ end
46
+
47
+ # Create a hidden field for the current checkbox, so we can simulate Rails
48
+ # functionality with hidden + checkbox, but under a nested context, where
49
+ # we need the hidden field to be *outside* the label (otherwise it
50
+ # generates invalid html - html5 only).
51
+ def build_hidden_field_for_checkbox
52
+ @builder.hidden_field(attribute_name, :value => '0', :id => nil,
53
+ :disabled => input_html_options[:disabled])
54
+ end
55
+
14
56
  # Booleans are not required by default because in most of the cases
15
57
  # it makes no sense marking them as required. The only exception is
16
58
  # Terms of Use usually presented at most sites sign up screen.
17
- def attribute_required_by_default?
59
+ def required_by_default?
18
60
  false
19
61
  end
20
62
  end
21
63
  end
22
- end
64
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class CollectionCheckBoxesInput < CollectionRadioButtonsInput
4
+ protected
5
+
6
+ # Checkbox components do not use the required html tag.
7
+ # More info: https://github.com/plataformatec/simple_form/issues/340#issuecomment-2871956
8
+ def has_required?
9
+ false
10
+ end
11
+
12
+ def build_nested_boolean_style_item_tag(collection_builder)
13
+ collection_builder.check_box + collection_builder.text
14
+ end
15
+
16
+ def item_wrapper_class
17
+ "checkbox"
18
+ end
19
+ end
20
+ end
21
+ end