simple_form 3.0.4 → 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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -43
  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 +27 -5
  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 -74
  23. data/lib/simple_form/helpers.rb +5 -5
  24. data/lib/simple_form/inputs/base.rb +34 -12
  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 +68 -49
  57. data/test/form_builder/general_test.rb +67 -62
  58. data/test/form_builder/hint_test.rb +15 -15
  59. data/test/form_builder/input_field_test.rb +33 -49
  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 +66 -6
  83. data/test/support/models.rb +50 -24
  84. metadata +7 -7
@@ -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,14 +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={})
139
- components = (wrapper.components & ATTRIBUTE_COMPONENTS)
140
-
133
+ def input_field(attribute_name, options = {})
141
134
  options = options.dup
142
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *components)
135
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
143
136
  options = @defaults.deep_dup.deep_merge(options) if @defaults
144
137
 
145
- SimpleForm::Wrappers::Root.new(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
146
144
  end
147
145
 
148
146
  # Helper for dealing with association selects/radios, generating the
@@ -173,7 +171,7 @@ module SimpleForm
173
171
  #
174
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.
175
173
  #
176
- def association(association, options={}, &block)
174
+ def association(association, options = {}, &block)
177
175
  options = options.dup
178
176
 
179
177
  return simple_fields_for(*[association,
@@ -185,31 +183,9 @@ module SimpleForm
185
183
  raise "Association #{association.inspect} not found" unless reflection
186
184
 
187
185
  options[:as] ||= :select
188
- options[:collection] ||= options.fetch(:collection) {
189
- conditions = reflection.options[:conditions]
190
- conditions = conditions.call if conditions.respond_to?(:call)
191
- reflection.klass.where(conditions).order(reflection.options[:order])
192
- }
193
-
194
- attribute = case reflection.macro
195
- when :belongs_to
196
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
197
- when :has_one
198
- raise ArgumentError, ":has_one associations are not supported by f.association"
199
- else
200
- if options[:as] == :select
201
- html_options = options[:input_html] ||= {}
202
- html_options[:multiple] = true unless html_options.key?(:multiple)
203
- end
204
-
205
- # Force the association to be preloaded for performance.
206
- if options[:preload] != false && object.respond_to?(association)
207
- target = object.send(association)
208
- target.to_a if target.respond_to?(:to_a)
209
- end
210
-
211
- :"#{reflection.name.to_s.singularize}_ids"
212
- end
186
+ options[:collection] ||= fetch_association_collection(reflection, options)
187
+
188
+ attribute = build_association_attribute(reflection, association, options)
213
189
 
214
190
  input(attribute, options.merge(reflection: reflection))
215
191
  end
@@ -244,7 +220,7 @@ module SimpleForm
244
220
  # f.error :name
245
221
  # f.error :name, id: "cool_error"
246
222
  #
247
- def error(attribute_name, options={})
223
+ def error(attribute_name, options = {})
248
224
  options = options.dup
249
225
 
250
226
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -261,7 +237,7 @@ module SimpleForm
261
237
  #
262
238
  # f.full_error :token #=> <span class="error">Token is invalid</span>
263
239
  #
264
- def full_error(attribute_name, options={})
240
+ def full_error(attribute_name, options = {})
265
241
  options = options.dup
266
242
 
267
243
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -283,7 +259,7 @@ module SimpleForm
283
259
  # f.hint :name, id: "cool_hint"
284
260
  # f.hint "Don't forget to accept this"
285
261
  #
286
- def hint(attribute_name, options={})
262
+ def hint(attribute_name, options = {})
287
263
  options = options.dup
288
264
 
289
265
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -334,7 +310,7 @@ module SimpleForm
334
310
  # f.error_notification message: 'Something went wrong'
335
311
  # f.error_notification id: 'user_error_message', class: 'form_error'
336
312
  #
337
- def error_notification(options={})
313
+ def error_notification(options = {})
338
314
  SimpleForm::ErrorNotification.new(self, options).render
339
315
  end
340
316
 
@@ -466,15 +442,56 @@ module SimpleForm
466
442
  @lookup_action ||= begin
467
443
  action = template.controller && template.controller.action_name
468
444
  return unless action
469
- action = action.to_sym
445
+ action = action.to_s
470
446
  ACTIONS[action] || action
471
447
  end
472
448
  end
473
449
 
474
450
  private
475
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
+
476
493
  # Find an input based on the attribute name.
477
- def find_input(attribute_name, options={}, &block) #:nodoc:
494
+ def find_input(attribute_name, options = {}, &block)
478
495
  column = find_attribute_column(attribute_name)
479
496
  input_type = default_input_type(attribute_name, column, options)
480
497
 
@@ -488,7 +505,7 @@ module SimpleForm
488
505
  # Attempt to guess the better input type given the defined options. By
489
506
  # default alwayls fallback to the user :as option, or to a :select when a
490
507
  # collection is given.
491
- def default_input_type(attribute_name, column, options) #:nodoc:
508
+ def default_input_type(attribute_name, column, options)
492
509
  return options[:as].to_sym if options[:as]
493
510
  return :select if options[:collection]
494
511
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
@@ -513,24 +530,24 @@ module SimpleForm
513
530
  end
514
531
  end
515
532
 
516
- def find_custom_type(attribute_name) #:nodoc:
533
+ def find_custom_type(attribute_name)
517
534
  SimpleForm.input_mappings.find { |match, type|
518
535
  attribute_name =~ match
519
536
  }.try(:last) if SimpleForm.input_mappings
520
537
  end
521
538
 
522
- def file_method?(attribute_name) #:nodoc:
539
+ def file_method?(attribute_name)
523
540
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
524
541
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
525
542
  end
526
543
 
527
- def find_attribute_column(attribute_name) #:nodoc:
544
+ def find_attribute_column(attribute_name)
528
545
  if @object.respond_to?(:column_for_attribute)
529
546
  @object.column_for_attribute(attribute_name)
530
547
  end
531
548
  end
532
549
 
533
- def find_association_reflection(association) #:nodoc:
550
+ def find_association_reflection(association)
534
551
  if @object.class.respond_to?(:reflect_on_association)
535
552
  @object.class.reflect_on_association(association)
536
553
  end
@@ -543,24 +560,42 @@ module SimpleForm
543
560
  # b) Or use the found mapping
544
561
  # 2) If not, fallbacks to #{input_type}Input
545
562
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
546
- def find_mapping(input_type) #:nodoc:
563
+ def find_mapping(input_type)
547
564
  discovery_cache[input_type] ||=
548
565
  if mapping = self.class.mappings[input_type]
549
566
  mapping_override(mapping) || mapping
550
567
  else
551
568
  camelized = "#{input_type.to_s.camelize}Input"
552
- 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) ||
553
572
  raise("No input found for #{input_type}")
554
573
  end
555
574
  end
556
575
 
557
- def find_wrapper_mapping(input_type) #:nodoc:
558
- 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
559
594
  end
560
595
 
561
596
  # If cache_discovery is enabled, use the class level cache that persists
562
597
  # between requests, otherwise use the instance one.
563
- def discovery_cache #:nodoc:
598
+ def discovery_cache
564
599
  if SimpleForm.cache_discovery
565
600
  self.class.discovery_cache
566
601
  else
@@ -568,14 +603,16 @@ module SimpleForm
568
603
  end
569
604
  end
570
605
 
571
- def mapping_override(klass) #:nodoc:
606
+ def mapping_override(klass)
572
607
  name = klass.name
573
608
  if name =~ /^SimpleForm::Inputs/
574
- 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)
575
612
  end
576
613
  end
577
614
 
578
- def attempt_mapping(mapping, at) #:nodoc:
615
+ def attempt_mapping(mapping, at)
579
616
  return if SimpleForm.inputs_discovery == false && at == Object
580
617
 
581
618
  begin
@@ -584,5 +621,15 @@ module SimpleForm
584
621
  raise if e.message !~ /#{mapping}$/
585
622
  end
586
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
587
634
  end
588
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
 
@@ -45,7 +47,7 @@ module SimpleForm
45
47
  end
46
48
 
47
49
  # Always enabled.
48
- enable :hint, :error
50
+ enable :hint
49
51
 
50
52
  # Usually disabled, needs to be enabled explicitly passing true as option.
51
53
  disable :maxlength, :placeholder, :pattern, :min_max
@@ -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
-