simple_form 3.0.1 → 3.1.0.rc2

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -27
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +179 -73
  5. data/lib/generators/simple_form/install_generator.rb +2 -2
  6. data/lib/generators/simple_form/templates/README +3 -4
  7. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +25 -5
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +108 -24
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +1 -1
  10. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
  11. data/lib/simple_form/action_view_extensions/form_helper.rb +1 -1
  12. data/lib/simple_form/components/errors.rb +30 -2
  13. data/lib/simple_form/components/hints.rb +2 -2
  14. data/lib/simple_form/components/html5.rb +1 -1
  15. data/lib/simple_form/components/label_input.rb +20 -2
  16. data/lib/simple_form/components/labels.rb +10 -6
  17. data/lib/simple_form/components/maxlength.rb +1 -1
  18. data/lib/simple_form/components/min_max.rb +1 -1
  19. data/lib/simple_form/components/pattern.rb +1 -1
  20. data/lib/simple_form/components/placeholders.rb +2 -2
  21. data/lib/simple_form/components/readonly.rb +1 -1
  22. data/lib/simple_form/form_builder.rb +121 -72
  23. data/lib/simple_form/helpers.rb +5 -5
  24. data/lib/simple_form/inputs/base.rb +33 -11
  25. data/lib/simple_form/inputs/block_input.rb +1 -1
  26. data/lib/simple_form/inputs/boolean_input.rb +27 -14
  27. data/lib/simple_form/inputs/collection_input.rb +32 -9
  28. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +6 -11
  29. data/lib/simple_form/inputs/collection_select_input.rb +4 -2
  30. data/lib/simple_form/inputs/date_time_input.rb +12 -2
  31. data/lib/simple_form/inputs/file_input.rb +4 -2
  32. data/lib/simple_form/inputs/grouped_collection_select_input.rb +15 -3
  33. data/lib/simple_form/inputs/hidden_input.rb +4 -2
  34. data/lib/simple_form/inputs/numeric_input.rb +5 -4
  35. data/lib/simple_form/inputs/password_input.rb +4 -2
  36. data/lib/simple_form/inputs/priority_input.rb +4 -2
  37. data/lib/simple_form/inputs/range_input.rb +1 -1
  38. data/lib/simple_form/inputs/string_input.rb +4 -2
  39. data/lib/simple_form/inputs/text_input.rb +4 -2
  40. data/lib/simple_form/railtie.rb +7 -0
  41. data/lib/simple_form/tags.rb +8 -0
  42. data/lib/simple_form/version.rb +1 -1
  43. data/lib/simple_form/wrappers/builder.rb +6 -6
  44. data/lib/simple_form/wrappers/leaf.rb +28 -0
  45. data/lib/simple_form/wrappers/many.rb +6 -6
  46. data/lib/simple_form/wrappers/root.rb +1 -1
  47. data/lib/simple_form/wrappers/single.rb +5 -3
  48. data/lib/simple_form/wrappers.rb +1 -0
  49. data/lib/simple_form.rb +46 -6
  50. data/test/action_view_extensions/builder_test.rb +5 -5
  51. data/test/action_view_extensions/form_helper_test.rb +13 -13
  52. data/test/components/label_test.rb +36 -36
  53. data/test/form_builder/association_test.rb +21 -4
  54. data/test/form_builder/button_test.rb +5 -5
  55. data/test/form_builder/error_notification_test.rb +1 -1
  56. data/test/form_builder/error_test.rb +78 -17
  57. data/test/form_builder/general_test.rb +65 -60
  58. data/test/form_builder/hint_test.rb +15 -15
  59. data/test/form_builder/input_field_test.rb +37 -13
  60. data/test/form_builder/label_test.rb +44 -12
  61. data/test/form_builder/wrapper_test.rb +120 -19
  62. data/test/generators/simple_form_generator_test.rb +2 -2
  63. data/test/inputs/boolean_input_test.rb +54 -6
  64. data/test/inputs/collection_check_boxes_input_test.rb +63 -17
  65. data/test/inputs/collection_radio_buttons_input_test.rb +112 -36
  66. data/test/inputs/collection_select_input_test.rb +141 -36
  67. data/test/inputs/datetime_input_test.rb +116 -49
  68. data/test/inputs/disabled_test.rb +15 -15
  69. data/test/inputs/discovery_test.rb +56 -6
  70. data/test/inputs/file_input_test.rb +2 -2
  71. data/test/inputs/general_test.rb +20 -20
  72. data/test/inputs/grouped_collection_select_input_test.rb +38 -2
  73. data/test/inputs/hidden_input_test.rb +4 -4
  74. data/test/inputs/numeric_input_test.rb +24 -24
  75. data/test/inputs/priority_input_test.rb +7 -7
  76. data/test/inputs/readonly_test.rb +19 -19
  77. data/test/inputs/required_test.rb +13 -13
  78. data/test/inputs/string_input_test.rb +41 -21
  79. data/test/inputs/text_input_test.rb +4 -4
  80. data/test/simple_form_test.rb +8 -0
  81. data/test/support/discovery_inputs.rb +32 -2
  82. data/test/support/misc_helpers.rb +71 -5
  83. data/test/support/models.rb +50 -24
  84. data/test/test_helper.rb +2 -0
  85. metadata +23 -34
@@ -8,8 +8,8 @@ module SimpleForm
8
8
 
9
9
  # When action is create or update, we still should use new and edit
10
10
  ACTIONS = {
11
- create: :new,
12
- update: :edit
11
+ 'create' => 'new',
12
+ 'update' => 'edit'
13
13
  }
14
14
 
15
15
  ATTRIBUTE_COMPONENTS = [:html5, :min_max, :maxlength, :placeholder, :pattern, :readonly]
@@ -17,19 +17,19 @@ module SimpleForm
17
17
  extend MapType
18
18
  include SimpleForm::Inputs
19
19
 
20
- map_type :text, to: SimpleForm::Inputs::TextInput
21
- map_type :file, to: SimpleForm::Inputs::FileInput
22
- map_type :string, :email, :search, :tel, :url, to: SimpleForm::Inputs::StringInput
23
- map_type :password, to: SimpleForm::Inputs::PasswordInput
24
- map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
25
- map_type :range, to: SimpleForm::Inputs::RangeInput
26
- map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
27
- map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
28
- map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
29
- map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
30
- map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
31
- map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
32
- map_type :boolean, to: SimpleForm::Inputs::BooleanInput
20
+ map_type :text, to: SimpleForm::Inputs::TextInput
21
+ map_type :file, to: SimpleForm::Inputs::FileInput
22
+ map_type :string, :email, :search, :tel, :url, :uuid, to: SimpleForm::Inputs::StringInput
23
+ map_type :password, to: SimpleForm::Inputs::PasswordInput
24
+ map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
25
+ map_type :range, to: SimpleForm::Inputs::RangeInput
26
+ map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
27
+ map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
28
+ map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
29
+ map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
30
+ map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
31
+ map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
32
+ map_type :boolean, to: SimpleForm::Inputs::BooleanInput
33
33
 
34
34
  def self.discovery_cache
35
35
  @discovery_cache ||= {}
@@ -106,18 +106,13 @@ module SimpleForm
106
106
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
107
107
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
108
108
  #
109
- def input(attribute_name, options={}, &block)
109
+ def input(attribute_name, options = {}, &block)
110
110
  options = @defaults.deep_dup.deep_merge(options) if @defaults
111
- input = find_input(attribute_name, options, &block)
112
111
 
113
- chosen =
114
- if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
115
- name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
116
- else
117
- wrapper
118
- end
112
+ input = find_input(attribute_name, options, &block)
113
+ wrapper = find_wrapper(input.input_type, options)
119
114
 
120
- chosen.render input
115
+ wrapper.render input
121
116
  end
122
117
  alias :attribute :input
123
118
 
@@ -135,12 +130,17 @@ module SimpleForm
135
130
  # <input class="string required" id="user_name" maxlength="100"
136
131
  # name="user[name]" type="text" value="Carlos" />
137
132
  #
138
- def input_field(attribute_name, options={})
133
+ def input_field(attribute_name, options = {})
139
134
  options = options.dup
140
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
135
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
141
136
  options = @defaults.deep_dup.deep_merge(options) if @defaults
142
137
 
143
- SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
138
+ input = find_input(attribute_name, options)
139
+ wrapper = find_wrapper(input.input_type, options)
140
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS) + [:input]
141
+ components = components.map { |component| SimpleForm::Wrappers::Leaf.new(component) }
142
+
143
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
144
144
  end
145
145
 
146
146
  # Helper for dealing with association selects/radios, generating the
@@ -171,7 +171,7 @@ module SimpleForm
171
171
  #
172
172
  # Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
173
173
  #
174
- def association(association, options={}, &block)
174
+ def association(association, options = {}, &block)
175
175
  options = options.dup
176
176
 
177
177
  return simple_fields_for(*[association,
@@ -183,31 +183,9 @@ module SimpleForm
183
183
  raise "Association #{association.inspect} not found" unless reflection
184
184
 
185
185
  options[:as] ||= :select
186
- options[:collection] ||= options.fetch(:collection) {
187
- conditions = reflection.options[:conditions]
188
- conditions = conditions.call if conditions.respond_to?(:call)
189
- reflection.klass.where(conditions).order(reflection.options[:order])
190
- }
191
-
192
- attribute = case reflection.macro
193
- when :belongs_to
194
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
195
- when :has_one
196
- raise ArgumentError, ":has_one associations are not supported by f.association"
197
- else
198
- if options[:as] == :select
199
- html_options = options[:input_html] ||= {}
200
- html_options[:multiple] = true unless html_options.key?(:multiple)
201
- end
202
-
203
- # Force the association to be preloaded for performance.
204
- if options[:preload] != false && object.respond_to?(association)
205
- target = object.send(association)
206
- target.to_a if target.respond_to?(:to_a)
207
- end
208
-
209
- :"#{reflection.name.to_s.singularize}_ids"
210
- end
186
+ options[:collection] ||= fetch_association_collection(reflection, options)
187
+
188
+ attribute = build_association_attribute(reflection, association, options)
211
189
 
212
190
  input(attribute, options.merge(reflection: reflection))
213
191
  end
@@ -242,7 +220,7 @@ module SimpleForm
242
220
  # f.error :name
243
221
  # f.error :name, id: "cool_error"
244
222
  #
245
- def error(attribute_name, options={})
223
+ def error(attribute_name, options = {})
246
224
  options = options.dup
247
225
 
248
226
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -259,7 +237,7 @@ module SimpleForm
259
237
  #
260
238
  # f.full_error :token #=> <span class="error">Token is invalid</span>
261
239
  #
262
- def full_error(attribute_name, options={})
240
+ def full_error(attribute_name, options = {})
263
241
  options = options.dup
264
242
 
265
243
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -281,7 +259,7 @@ module SimpleForm
281
259
  # f.hint :name, id: "cool_hint"
282
260
  # f.hint "Don't forget to accept this"
283
261
  #
284
- def hint(attribute_name, options={})
262
+ def hint(attribute_name, options = {})
285
263
  options = options.dup
286
264
 
287
265
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -332,7 +310,7 @@ module SimpleForm
332
310
  # f.error_notification message: 'Something went wrong'
333
311
  # f.error_notification id: 'user_error_message', class: 'form_error'
334
312
  #
335
- def error_notification(options={})
313
+ def error_notification(options = {})
336
314
  SimpleForm::ErrorNotification.new(self, options).render
337
315
  end
338
316
 
@@ -464,15 +442,56 @@ module SimpleForm
464
442
  @lookup_action ||= begin
465
443
  action = template.controller && template.controller.action_name
466
444
  return unless action
467
- action = action.to_sym
445
+ action = action.to_s
468
446
  ACTIONS[action] || action
469
447
  end
470
448
  end
471
449
 
472
450
  private
473
451
 
452
+ def fetch_association_collection(reflection, options)
453
+ options.fetch(:collection) do
454
+ relation = reflection.klass.all
455
+
456
+ if reflection.respond_to?(:scope) && reflection.scope
457
+ relation = reflection.klass.instance_exec(&reflection.scope)
458
+ else
459
+ order = reflection.options[:order]
460
+ conditions = reflection.options[:conditions]
461
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
462
+
463
+ relation = relation.where(conditions)
464
+ relation = relation.order(order) if relation.respond_to?(:order)
465
+ end
466
+
467
+ relation
468
+ end
469
+ end
470
+
471
+ def build_association_attribute(reflection, association, options)
472
+ case reflection.macro
473
+ when :belongs_to
474
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
475
+ when :has_one
476
+ raise ArgumentError, ":has_one associations are not supported by f.association"
477
+ else
478
+ if options[:as] == :select
479
+ html_options = options[:input_html] ||= {}
480
+ html_options[:multiple] = true unless html_options.key?(:multiple)
481
+ end
482
+
483
+ # Force the association to be preloaded for performance.
484
+ if options[:preload] != false && object.respond_to?(association)
485
+ target = object.send(association)
486
+ target.to_a if target.respond_to?(:to_a)
487
+ end
488
+
489
+ :"#{reflection.name.to_s.singularize}_ids"
490
+ end
491
+ end
492
+
474
493
  # Find an input based on the attribute name.
475
- def find_input(attribute_name, options={}, &block) #:nodoc:
494
+ def find_input(attribute_name, options = {}, &block)
476
495
  column = find_attribute_column(attribute_name)
477
496
  input_type = default_input_type(attribute_name, column, options)
478
497
 
@@ -486,7 +505,7 @@ module SimpleForm
486
505
  # Attempt to guess the better input type given the defined options. By
487
506
  # default alwayls fallback to the user :as option, or to a :select when a
488
507
  # collection is given.
489
- def default_input_type(attribute_name, column, options) #:nodoc:
508
+ def default_input_type(attribute_name, column, options)
490
509
  return options[:as].to_sym if options[:as]
491
510
  return :select if options[:collection]
492
511
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
@@ -511,24 +530,24 @@ module SimpleForm
511
530
  end
512
531
  end
513
532
 
514
- def find_custom_type(attribute_name) #:nodoc:
533
+ def find_custom_type(attribute_name)
515
534
  SimpleForm.input_mappings.find { |match, type|
516
535
  attribute_name =~ match
517
536
  }.try(:last) if SimpleForm.input_mappings
518
537
  end
519
538
 
520
- def file_method?(attribute_name) #:nodoc:
539
+ def file_method?(attribute_name)
521
540
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
522
541
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
523
542
  end
524
543
 
525
- def find_attribute_column(attribute_name) #:nodoc:
544
+ def find_attribute_column(attribute_name)
526
545
  if @object.respond_to?(:column_for_attribute)
527
546
  @object.column_for_attribute(attribute_name)
528
547
  end
529
548
  end
530
549
 
531
- def find_association_reflection(association) #:nodoc:
550
+ def find_association_reflection(association)
532
551
  if @object.class.respond_to?(:reflect_on_association)
533
552
  @object.class.reflect_on_association(association)
534
553
  end
@@ -541,24 +560,42 @@ module SimpleForm
541
560
  # b) Or use the found mapping
542
561
  # 2) If not, fallbacks to #{input_type}Input
543
562
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
544
- def find_mapping(input_type) #:nodoc:
563
+ def find_mapping(input_type)
545
564
  discovery_cache[input_type] ||=
546
565
  if mapping = self.class.mappings[input_type]
547
566
  mapping_override(mapping) || mapping
548
567
  else
549
568
  camelized = "#{input_type.to_s.camelize}Input"
550
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
569
+ attempt_mapping_with_custom_namespace(camelized) ||
570
+ attempt_mapping(camelized, Object) ||
571
+ attempt_mapping(camelized, self.class) ||
551
572
  raise("No input found for #{input_type}")
552
573
  end
553
574
  end
554
575
 
555
- def find_wrapper_mapping(input_type) #:nodoc:
556
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
576
+ # Attempts to find a wrapper mapping. It follows the following rules:
577
+ #
578
+ # 1) It tries to find a wrapper for the current form
579
+ # 2) If not, it tries to find a config
580
+ def find_wrapper_mapping(input_type)
581
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
582
+ options[:wrapper_mappings][input_type]
583
+ else
584
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
585
+ end
586
+ end
587
+
588
+ def find_wrapper(input_type, options)
589
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
590
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
591
+ else
592
+ wrapper
593
+ end
557
594
  end
558
595
 
559
596
  # If cache_discovery is enabled, use the class level cache that persists
560
597
  # between requests, otherwise use the instance one.
561
- def discovery_cache #:nodoc:
598
+ def discovery_cache
562
599
  if SimpleForm.cache_discovery
563
600
  self.class.discovery_cache
564
601
  else
@@ -566,14 +603,16 @@ module SimpleForm
566
603
  end
567
604
  end
568
605
 
569
- def mapping_override(klass) #:nodoc:
606
+ def mapping_override(klass)
570
607
  name = klass.name
571
608
  if name =~ /^SimpleForm::Inputs/
572
- attempt_mapping name.split("::").last, Object
609
+ input_name = name.split("::").last
610
+ attempt_mapping_with_custom_namespace(input_name) ||
611
+ attempt_mapping(input_name, Object)
573
612
  end
574
613
  end
575
614
 
576
- def attempt_mapping(mapping, at) #:nodoc:
615
+ def attempt_mapping(mapping, at)
577
616
  return if SimpleForm.inputs_discovery == false && at == Object
578
617
 
579
618
  begin
@@ -582,5 +621,15 @@ module SimpleForm
582
621
  raise if e.message !~ /#{mapping}$/
583
622
  end
584
623
  end
624
+
625
+ def attempt_mapping_with_custom_namespace(input_name)
626
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
627
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
628
+ return mapping
629
+ end
630
+ end
631
+
632
+ nil
633
+ end
585
634
  end
586
635
  end
@@ -3,10 +3,10 @@ module SimpleForm
3
3
  # For instance, disabled cannot be turned on automatically, it requires the
4
4
  # user to explicitly pass the option disabled: true so it may work.
5
5
  module Helpers
6
- autoload :Autofocus, 'simple_form/helpers/autofocus'
7
- autoload :Disabled, 'simple_form/helpers/disabled'
8
- autoload :Readonly, 'simple_form/helpers/readonly'
9
- autoload :Required, 'simple_form/helpers/required'
10
- autoload :Validators, 'simple_form/helpers/validators'
6
+ autoload :Autofocus, 'simple_form/helpers/autofocus'
7
+ autoload :Disabled, 'simple_form/helpers/disabled'
8
+ autoload :Readonly, 'simple_form/helpers/readonly'
9
+ autoload :Required, 'simple_form/helpers/required'
10
+ autoload :Validators, 'simple_form/helpers/validators'
11
11
  end
12
12
  end
@@ -1,10 +1,12 @@
1
1
  require 'simple_form/i18n_cache'
2
2
  require 'active_support/core_ext/string/output_safety'
3
+ require 'action_view/helpers'
3
4
 
4
5
  module SimpleForm
5
6
  module Inputs
6
7
  class Base
7
8
  include ERB::Util
9
+ include ActionView::Helpers::TranslationHelper
8
10
 
9
11
  extend I18nCache
10
12
 
@@ -53,14 +55,14 @@ module SimpleForm
53
55
  def initialize(builder, attribute_name, column, input_type, options = {})
54
56
  super
55
57
 
56
- options = options.dup
57
- @builder = builder
58
- @attribute_name = attribute_name
59
- @column = column
60
- @input_type = input_type
61
- @reflection = options.delete(:reflection)
62
- @options = options.reverse_merge!(self.class.default_options)
63
- @required = calculate_required
58
+ options = options.dup
59
+ @builder = builder
60
+ @attribute_name = attribute_name
61
+ @column = column
62
+ @input_type = input_type
63
+ @reflection = options.delete(:reflection)
64
+ @options = options.reverse_merge!(self.class.default_options)
65
+ @required = calculate_required
64
66
 
65
67
  # Notice that html_options_for receives a reference to input_html_classes.
66
68
  # This means that classes added dynamically to input_html_classes will
@@ -79,7 +81,7 @@ module SimpleForm
79
81
  end
80
82
  end
81
83
 
82
- def input
84
+ def input(wrapper_options = nil)
83
85
  raise NotImplementedError
84
86
  end
85
87
 
@@ -167,7 +169,7 @@ module SimpleForm
167
169
  # email: 'E-mail.'
168
170
  #
169
171
  # Take a look at our locale example file.
170
- def translate(namespace, default='')
172
+ def translate_from_namespace(namespace, default = '')
171
173
  model_names = lookup_model_names.dup
172
174
  lookups = []
173
175
 
@@ -176,13 +178,33 @@ module SimpleForm
176
178
  model_names.shift
177
179
 
178
180
  lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
181
+ lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}_html"
179
182
  lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
183
+ lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}_html"
180
184
  end
181
185
  lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}"
186
+ lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}_html"
182
187
  lookups << :"defaults.#{reflection_or_attribute_name}"
188
+ lookups << :"defaults.#{reflection_or_attribute_name}_html"
183
189
  lookups << default
184
190
 
185
- I18n.t(lookups.shift, scope: :"simple_form.#{namespace}", default: lookups).presence
191
+ t(lookups.shift, scope: :"#{i18n_scope}.#{namespace}", default: lookups).presence
192
+ end
193
+
194
+ def merge_wrapper_options(options, wrapper_options)
195
+ if wrapper_options
196
+ options.merge(wrapper_options) do |_, oldval, newval|
197
+ if Array === oldval
198
+ oldval + Array(newval)
199
+ end
200
+ end
201
+ else
202
+ options
203
+ end
204
+ end
205
+
206
+ def i18n_scope
207
+ SimpleForm.i18n_scope
186
208
  end
187
209
  end
188
210
  end
@@ -6,7 +6,7 @@ module SimpleForm
6
6
  @block = block
7
7
  end
8
8
 
9
- def input
9
+ def input(wrapper_options = nil)
10
10
  template.capture(&@block)
11
11
  end
12
12
  end
@@ -1,31 +1,36 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class BooleanInput < Base
4
- def input
4
+ def input(wrapper_options = nil)
5
+ merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
6
+
5
7
  if nested_boolean_style?
6
8
  build_hidden_field_for_checkbox +
7
- template.label_tag(nil, class: "checkbox") {
8
- build_check_box_without_hidden_field + inline_label
9
+ template.label_tag(nil, class: SimpleForm.boolean_label_class) {
10
+ build_check_box_without_hidden_field(merged_input_options) +
11
+ inline_label
9
12
  }
10
13
  else
11
- build_check_box
14
+ build_check_box(unchecked_value, merged_input_options)
12
15
  end
13
16
  end
14
17
 
15
- def label_input
16
- if options[:label] == false
17
- input
18
+ def label_input(wrapper_options = nil)
19
+ if options[:label] == false || inline_label?
20
+ input(wrapper_options)
18
21
  elsif nested_boolean_style?
19
22
  html_options = label_html_options.dup
20
23
  html_options[:class] ||= []
21
- html_options[:class].push(:checkbox)
24
+ html_options[:class].push(SimpleForm.boolean_label_class) if SimpleForm.boolean_label_class
25
+
26
+ merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
22
27
 
23
28
  build_hidden_field_for_checkbox +
24
29
  @builder.label(label_target, html_options) {
25
- build_check_box_without_hidden_field + label_text
30
+ build_check_box_without_hidden_field(merged_input_options) + label_text
26
31
  }
27
32
  else
28
- input + label
33
+ input(wrapper_options) + label(wrapper_options)
29
34
  end
30
35
  end
31
36
 
@@ -35,14 +40,14 @@ module SimpleForm
35
40
  # reuse the method for nested boolean style, but with no unchecked value,
36
41
  # which won't generate the hidden checkbox. This is the default functionality
37
42
  # in Rails > 3.2.1, and is backported in SimpleForm AV helpers.
38
- def build_check_box(unchecked_value = unchecked_value)
43
+ def build_check_box(unchecked_value, options)
39
44
  @builder.check_box(attribute_name, input_html_options, checked_value, unchecked_value)
40
45
  end
41
46
 
42
47
  # Build a checkbox without generating the hidden field. See
43
48
  # #build_hidden_field_for_checkbox for more info.
44
- def build_check_box_without_hidden_field
45
- build_check_box(nil)
49
+ def build_check_box_without_hidden_field(options)
50
+ build_check_box(nil, options)
46
51
  end
47
52
 
48
53
  # Create a hidden field for the current checkbox, so we can simulate Rails
@@ -56,9 +61,17 @@ module SimpleForm
56
61
  @builder.hidden_field(attribute_name, options)
57
62
  end
58
63
 
64
+ def inline_label?
65
+ nested_boolean_style? && options[:inline_label]
66
+ end
67
+
59
68
  def inline_label
60
69
  inline_option = options[:inline_label]
61
- inline_option == true ? label_text : inline_option
70
+
71
+ if inline_option
72
+ label = inline_option == true ? label_text : html_escape(inline_option)
73
+ " #{label}".html_safe
74
+ end
62
75
  end
63
76
 
64
77
  # Booleans are not required by default because in most of the cases
@@ -12,14 +12,20 @@ module SimpleForm
12
12
  end
13
13
  end
14
14
 
15
- def input
15
+ def input(wrapper_options = nil)
16
16
  raise NotImplementedError,
17
17
  "input should be implemented by classes inheriting from CollectionInput"
18
18
  end
19
19
 
20
20
  def input_options
21
21
  options = super
22
+
22
23
  options[:include_blank] = true unless skip_include_blank?
24
+
25
+ [:prompt, :include_blank].each do |key|
26
+ translate_option options, key
27
+ end
28
+
23
29
  options
24
30
  end
25
31
 
@@ -33,7 +39,7 @@ module SimpleForm
33
39
  end
34
40
 
35
41
  def has_required?
36
- super && (input_options[:include_blank] || multiple?)
42
+ super && (input_options[:include_blank] || input_options[:prompt] || multiple?)
37
43
  end
38
44
 
39
45
  # Check if :include_blank must be included by default.
@@ -70,13 +76,17 @@ module SimpleForm
70
76
  elsif collection_includes_basic_objects?(collection_classes)
71
77
  { label: :to_s, value: :to_s }
72
78
  else
73
- sample = collection.first || collection.last
74
-
75
- { label: SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
76
- value: SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
79
+ detect_method_from_class(collection_classes)
77
80
  end
78
81
  end
79
82
 
83
+ def detect_method_from_class(collection_classes)
84
+ sample = collection.first || collection.last
85
+
86
+ { label: SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
87
+ value: SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
88
+ end
89
+
80
90
  def detect_collection_classes(some_collection = collection)
81
91
  some_collection.map { |e| e.class }.uniq
82
92
  end
@@ -88,14 +98,27 @@ module SimpleForm
88
98
  end
89
99
 
90
100
  def translate_collection
91
- if translated_collection = translate(:options)
101
+ if translated_collection = translate_from_namespace(:options)
92
102
  @collection = collection.map do |key|
93
- [translated_collection[key] || key, key]
103
+ html_key = "#{key}_html".to_sym
104
+
105
+ if translated_collection[html_key]
106
+ [translated_collection[html_key].html_safe || key, key.to_s]
107
+ else
108
+ [translated_collection[key] || key, key.to_s]
109
+ end
94
110
  end
95
111
  true
96
112
  end
97
113
  end
114
+
115
+ def translate_option(options, key)
116
+ if options[key] == :translate
117
+ namespace = key.to_s.pluralize
118
+
119
+ options[key] = translate_from_namespace(namespace, true)
120
+ end
121
+ end
98
122
  end
99
123
  end
100
124
  end
101
-
@@ -1,19 +1,21 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class CollectionRadioButtonsInput < CollectionInput
4
- def input
4
+ def input(wrapper_options = nil)
5
5
  label_method, value_method = detect_collection_methods
6
6
 
7
+ merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
8
+
7
9
  @builder.send("collection_#{input_type}",
8
10
  attribute_name, collection, value_method, label_method,
9
- input_options, input_html_options, &collection_block_for_nested_boolean_style
11
+ input_options, merged_input_options,
12
+ &collection_block_for_nested_boolean_style
10
13
  )
11
14
  end
12
15
 
13
16
  def input_options
14
17
  options = super
15
18
  apply_default_collection_options!(options)
16
- apply_nested_boolean_collection_options!(options) if nested_boolean_style?
17
19
  options
18
20
  end
19
21
 
@@ -23,7 +25,7 @@ module SimpleForm
23
25
  options[:item_wrapper_tag] ||= options.fetch(:item_wrapper_tag, SimpleForm.item_wrapper_tag)
24
26
  options[:item_wrapper_class] = [
25
27
  item_wrapper_class, options[:item_wrapper_class], SimpleForm.item_wrapper_class
26
- ].compact.presence
28
+ ].compact.presence if SimpleForm.include_default_input_wrapper_class
27
29
 
28
30
  options[:collection_wrapper_tag] ||= options.fetch(:collection_wrapper_tag, SimpleForm.collection_wrapper_tag)
29
31
  options[:collection_wrapper_class] = [
@@ -31,13 +33,6 @@ module SimpleForm
31
33
  ].compact.presence
32
34
  end
33
35
 
34
- # Force item wrapper to be a label when using nested boolean, to support
35
- # configuring classes through :item_wrapper_class, and to maintain
36
- # compatibility with :inline style and default :item_wrapper_tag.
37
- def apply_nested_boolean_collection_options!(options)
38
- options[:item_wrapper_tag] = :label
39
- end
40
-
41
36
  def collection_block_for_nested_boolean_style
42
37
  return unless nested_boolean_style?
43
38