simple_form 3.0.1 → 3.1.1

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -25
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +251 -93
  5. data/lib/generators/simple_form/install_generator.rb +3 -3
  6. data/lib/generators/simple_form/templates/README +3 -4
  7. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +29 -9
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +128 -24
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +86 -5
  10. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
  11. data/lib/simple_form/action_view_extensions/builder.rb +1 -0
  12. data/lib/simple_form/action_view_extensions/form_helper.rb +6 -2
  13. data/lib/simple_form/components/errors.rb +28 -2
  14. data/lib/simple_form/components/hints.rb +2 -2
  15. data/lib/simple_form/components/html5.rb +5 -5
  16. data/lib/simple_form/components/label_input.rb +20 -2
  17. data/lib/simple_form/components/labels.rb +11 -7
  18. data/lib/simple_form/components/maxlength.rb +1 -1
  19. data/lib/simple_form/components/min_max.rb +1 -1
  20. data/lib/simple_form/components/pattern.rb +1 -1
  21. data/lib/simple_form/components/placeholders.rb +2 -2
  22. data/lib/simple_form/components/readonly.rb +1 -1
  23. data/lib/simple_form/form_builder.rb +127 -76
  24. data/lib/simple_form/helpers.rb +5 -5
  25. data/lib/simple_form/inputs/base.rb +33 -11
  26. data/lib/simple_form/inputs/block_input.rb +1 -1
  27. data/lib/simple_form/inputs/boolean_input.rb +28 -15
  28. data/lib/simple_form/inputs/collection_input.rb +30 -9
  29. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +7 -12
  30. data/lib/simple_form/inputs/collection_select_input.rb +4 -2
  31. data/lib/simple_form/inputs/date_time_input.rb +23 -9
  32. data/lib/simple_form/inputs/file_input.rb +4 -2
  33. data/lib/simple_form/inputs/grouped_collection_select_input.rb +15 -3
  34. data/lib/simple_form/inputs/hidden_input.rb +4 -2
  35. data/lib/simple_form/inputs/numeric_input.rb +5 -4
  36. data/lib/simple_form/inputs/password_input.rb +4 -2
  37. data/lib/simple_form/inputs/priority_input.rb +4 -2
  38. data/lib/simple_form/inputs/range_input.rb +1 -1
  39. data/lib/simple_form/inputs/string_input.rb +4 -2
  40. data/lib/simple_form/inputs/text_input.rb +4 -2
  41. data/lib/simple_form/railtie.rb +7 -0
  42. data/lib/simple_form/tags.rb +6 -0
  43. data/lib/simple_form/version.rb +1 -1
  44. data/lib/simple_form/wrappers/builder.rb +6 -6
  45. data/lib/simple_form/wrappers/leaf.rb +28 -0
  46. data/lib/simple_form/wrappers/many.rb +6 -6
  47. data/lib/simple_form/wrappers/root.rb +1 -1
  48. data/lib/simple_form/wrappers/single.rb +5 -3
  49. data/lib/simple_form/wrappers.rb +1 -0
  50. data/lib/simple_form.rb +59 -8
  51. data/test/action_view_extensions/builder_test.rb +36 -36
  52. data/test/action_view_extensions/form_helper_test.rb +33 -14
  53. data/test/components/label_test.rb +37 -37
  54. data/test/form_builder/association_test.rb +52 -35
  55. data/test/form_builder/button_test.rb +10 -10
  56. data/test/form_builder/error_notification_test.rb +1 -1
  57. data/test/form_builder/error_test.rb +144 -24
  58. data/test/form_builder/general_test.rb +107 -71
  59. data/test/form_builder/hint_test.rb +18 -18
  60. data/test/form_builder/input_field_test.rb +86 -56
  61. data/test/form_builder/label_test.rb +55 -13
  62. data/test/form_builder/wrapper_test.rb +136 -20
  63. data/test/generators/simple_form_generator_test.rb +4 -4
  64. data/test/inputs/boolean_input_test.rb +62 -6
  65. data/test/inputs/collection_check_boxes_input_test.rb +85 -17
  66. data/test/inputs/collection_radio_buttons_input_test.rb +134 -36
  67. data/test/inputs/collection_select_input_test.rb +146 -41
  68. data/test/inputs/datetime_input_test.rb +120 -48
  69. data/test/inputs/disabled_test.rb +15 -15
  70. data/test/inputs/discovery_test.rb +56 -6
  71. data/test/inputs/file_input_test.rb +2 -2
  72. data/test/inputs/general_test.rb +20 -20
  73. data/test/inputs/grouped_collection_select_input_test.rb +44 -8
  74. data/test/inputs/hidden_input_test.rb +4 -4
  75. data/test/inputs/numeric_input_test.rb +43 -43
  76. data/test/inputs/priority_input_test.rb +13 -13
  77. data/test/inputs/readonly_test.rb +19 -19
  78. data/test/inputs/required_test.rb +25 -13
  79. data/test/inputs/string_input_test.rb +50 -30
  80. data/test/inputs/text_input_test.rb +14 -7
  81. data/test/simple_form_test.rb +8 -0
  82. data/test/support/discovery_inputs.rb +32 -2
  83. data/test/support/misc_helpers.rb +84 -5
  84. data/test/support/models.rb +60 -24
  85. data/test/test_helper.rb +11 -1
  86. metadata +22 -33
@@ -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,20 @@ 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
+ map_type :hidden, to: SimpleForm::Inputs::HiddenInput
33
34
 
34
35
  def self.discovery_cache
35
36
  @discovery_cache ||= {}
@@ -106,18 +107,13 @@ module SimpleForm
106
107
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
107
108
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
108
109
  #
109
- def input(attribute_name, options={}, &block)
110
+ def input(attribute_name, options = {}, &block)
110
111
  options = @defaults.deep_dup.deep_merge(options) if @defaults
111
- input = find_input(attribute_name, options, &block)
112
112
 
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
113
+ input = find_input(attribute_name, options, &block)
114
+ wrapper = find_wrapper(input.input_type, options)
119
115
 
120
- chosen.render input
116
+ wrapper.render input
121
117
  end
122
118
  alias :attribute :input
123
119
 
@@ -135,12 +131,18 @@ module SimpleForm
135
131
  # <input class="string required" id="user_name" maxlength="100"
136
132
  # name="user[name]" type="text" value="Carlos" />
137
133
  #
138
- def input_field(attribute_name, options={})
134
+ def input_field(attribute_name, options = {})
135
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
136
+
139
137
  options = options.dup
140
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
138
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *components)
141
139
  options = @defaults.deep_dup.deep_merge(options) if @defaults
142
140
 
143
- SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
141
+ input = find_input(attribute_name, options)
142
+ wrapper = find_wrapper(input.input_type, options)
143
+ components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
144
+
145
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
144
146
  end
145
147
 
146
148
  # Helper for dealing with association selects/radios, generating the
@@ -171,7 +173,7 @@ module SimpleForm
171
173
  #
172
174
  # 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
175
  #
174
- def association(association, options={}, &block)
176
+ def association(association, options = {}, &block)
175
177
  options = options.dup
176
178
 
177
179
  return simple_fields_for(*[association,
@@ -183,31 +185,9 @@ module SimpleForm
183
185
  raise "Association #{association.inspect} not found" unless reflection
184
186
 
185
187
  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
188
+ options[:collection] ||= fetch_association_collection(reflection, options)
189
+
190
+ attribute = build_association_attribute(reflection, association, options)
211
191
 
212
192
  input(attribute, options.merge(reflection: reflection))
213
193
  end
@@ -227,8 +207,8 @@ module SimpleForm
227
207
  options = args.extract_options!.dup
228
208
  options[:class] = [SimpleForm.button_class, options[:class]].compact
229
209
  args << options
230
- if respond_to?("#{type}_button")
231
- send("#{type}_button", *args, &block)
210
+ if respond_to?(:"#{type}_button")
211
+ send(:"#{type}_button", *args, &block)
232
212
  else
233
213
  send(type, *args, &block)
234
214
  end
@@ -242,7 +222,7 @@ module SimpleForm
242
222
  # f.error :name
243
223
  # f.error :name, id: "cool_error"
244
224
  #
245
- def error(attribute_name, options={})
225
+ def error(attribute_name, options = {})
246
226
  options = options.dup
247
227
 
248
228
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -259,7 +239,7 @@ module SimpleForm
259
239
  #
260
240
  # f.full_error :token #=> <span class="error">Token is invalid</span>
261
241
  #
262
- def full_error(attribute_name, options={})
242
+ def full_error(attribute_name, options = {})
263
243
  options = options.dup
264
244
 
265
245
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -281,7 +261,7 @@ module SimpleForm
281
261
  # f.hint :name, id: "cool_hint"
282
262
  # f.hint "Don't forget to accept this"
283
263
  #
284
- def hint(attribute_name, options={})
264
+ def hint(attribute_name, options = {})
285
265
  options = options.dup
286
266
 
287
267
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -332,7 +312,7 @@ module SimpleForm
332
312
  # f.error_notification message: 'Something went wrong'
333
313
  # f.error_notification id: 'user_error_message', class: 'form_error'
334
314
  #
335
- def error_notification(options={})
315
+ def error_notification(options = {})
336
316
  SimpleForm::ErrorNotification.new(self, options).render
337
317
  end
338
318
 
@@ -464,15 +444,56 @@ module SimpleForm
464
444
  @lookup_action ||= begin
465
445
  action = template.controller && template.controller.action_name
466
446
  return unless action
467
- action = action.to_sym
447
+ action = action.to_s
468
448
  ACTIONS[action] || action
469
449
  end
470
450
  end
471
451
 
472
452
  private
473
453
 
454
+ def fetch_association_collection(reflection, options)
455
+ options.fetch(:collection) do
456
+ relation = reflection.klass.all
457
+
458
+ if reflection.respond_to?(:scope) && reflection.scope
459
+ relation = reflection.klass.instance_exec(&reflection.scope)
460
+ else
461
+ order = reflection.options[:order]
462
+ conditions = reflection.options[:conditions]
463
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
464
+
465
+ relation = relation.where(conditions)
466
+ relation = relation.order(order) if relation.respond_to?(:order)
467
+ end
468
+
469
+ relation
470
+ end
471
+ end
472
+
473
+ def build_association_attribute(reflection, association, options)
474
+ case reflection.macro
475
+ when :belongs_to
476
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
477
+ when :has_one
478
+ raise ArgumentError, ":has_one associations are not supported by f.association"
479
+ else
480
+ if options[:as] == :select
481
+ html_options = options[:input_html] ||= {}
482
+ html_options[:multiple] = true unless html_options.key?(:multiple)
483
+ end
484
+
485
+ # Force the association to be preloaded for performance.
486
+ if options[:preload] != false && object.respond_to?(association)
487
+ target = object.send(association)
488
+ target.to_a if target.respond_to?(:to_a)
489
+ end
490
+
491
+ :"#{reflection.name.to_s.singularize}_ids"
492
+ end
493
+ end
494
+
474
495
  # Find an input based on the attribute name.
475
- def find_input(attribute_name, options={}, &block) #:nodoc:
496
+ def find_input(attribute_name, options = {}, &block)
476
497
  column = find_attribute_column(attribute_name)
477
498
  input_type = default_input_type(attribute_name, column, options)
478
499
 
@@ -486,10 +507,10 @@ module SimpleForm
486
507
  # Attempt to guess the better input type given the defined options. By
487
508
  # default alwayls fallback to the user :as option, or to a :select when a
488
509
  # collection is given.
489
- def default_input_type(attribute_name, column, options) #:nodoc:
510
+ def default_input_type(attribute_name, column, options)
490
511
  return options[:as].to_sym if options[:as]
491
- return :select if options[:collection]
492
512
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
513
+ return :select if options[:collection]
493
514
 
494
515
  input_type = column.try(:type)
495
516
  case input_type
@@ -511,24 +532,24 @@ module SimpleForm
511
532
  end
512
533
  end
513
534
 
514
- def find_custom_type(attribute_name) #:nodoc:
535
+ def find_custom_type(attribute_name)
515
536
  SimpleForm.input_mappings.find { |match, type|
516
537
  attribute_name =~ match
517
538
  }.try(:last) if SimpleForm.input_mappings
518
539
  end
519
540
 
520
- def file_method?(attribute_name) #:nodoc:
541
+ def file_method?(attribute_name)
521
542
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
522
543
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
523
544
  end
524
545
 
525
- def find_attribute_column(attribute_name) #:nodoc:
526
- if @object.respond_to?(:column_for_attribute)
546
+ def find_attribute_column(attribute_name)
547
+ if @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
527
548
  @object.column_for_attribute(attribute_name)
528
549
  end
529
550
  end
530
551
 
531
- def find_association_reflection(association) #:nodoc:
552
+ def find_association_reflection(association)
532
553
  if @object.class.respond_to?(:reflect_on_association)
533
554
  @object.class.reflect_on_association(association)
534
555
  end
@@ -541,24 +562,42 @@ module SimpleForm
541
562
  # b) Or use the found mapping
542
563
  # 2) If not, fallbacks to #{input_type}Input
543
564
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
544
- def find_mapping(input_type) #:nodoc:
565
+ def find_mapping(input_type)
545
566
  discovery_cache[input_type] ||=
546
567
  if mapping = self.class.mappings[input_type]
547
568
  mapping_override(mapping) || mapping
548
569
  else
549
570
  camelized = "#{input_type.to_s.camelize}Input"
550
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
571
+ attempt_mapping_with_custom_namespace(camelized) ||
572
+ attempt_mapping(camelized, Object) ||
573
+ attempt_mapping(camelized, self.class) ||
551
574
  raise("No input found for #{input_type}")
552
575
  end
553
576
  end
554
577
 
555
- def find_wrapper_mapping(input_type) #:nodoc:
556
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
578
+ # Attempts to find a wrapper mapping. It follows the following rules:
579
+ #
580
+ # 1) It tries to find a wrapper for the current form
581
+ # 2) If not, it tries to find a config
582
+ def find_wrapper_mapping(input_type)
583
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
584
+ options[:wrapper_mappings][input_type]
585
+ else
586
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
587
+ end
588
+ end
589
+
590
+ def find_wrapper(input_type, options)
591
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
592
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
593
+ else
594
+ wrapper
595
+ end
557
596
  end
558
597
 
559
598
  # If cache_discovery is enabled, use the class level cache that persists
560
599
  # between requests, otherwise use the instance one.
561
- def discovery_cache #:nodoc:
600
+ def discovery_cache
562
601
  if SimpleForm.cache_discovery
563
602
  self.class.discovery_cache
564
603
  else
@@ -566,14 +605,16 @@ module SimpleForm
566
605
  end
567
606
  end
568
607
 
569
- def mapping_override(klass) #:nodoc:
608
+ def mapping_override(klass)
570
609
  name = klass.name
571
610
  if name =~ /^SimpleForm::Inputs/
572
- attempt_mapping name.split("::").last, Object
611
+ input_name = name.split("::").last
612
+ attempt_mapping_with_custom_namespace(input_name) ||
613
+ attempt_mapping(input_name, Object)
573
614
  end
574
615
  end
575
616
 
576
- def attempt_mapping(mapping, at) #:nodoc:
617
+ def attempt_mapping(mapping, at)
577
618
  return if SimpleForm.inputs_discovery == false && at == Object
578
619
 
579
620
  begin
@@ -582,5 +623,15 @@ module SimpleForm
582
623
  raise if e.message !~ /#{mapping}$/
583
624
  end
584
625
  end
626
+
627
+ def attempt_mapping_with_custom_namespace(input_name)
628
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
629
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
630
+ return mapping
631
+ end
632
+ end
633
+
634
+ nil
635
+ end
585
636
  end
586
637
  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)
39
- @builder.check_box(attribute_name, input_html_options, checked_value, unchecked_value)
43
+ def build_check_box(unchecked_value, options)
44
+ @builder.check_box(attribute_name, 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,18 @@ 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
+ translate_option options, :prompt
25
+ translate_option options, :include_blank
26
+
23
27
  options
24
28
  end
25
29
 
@@ -33,7 +37,7 @@ module SimpleForm
33
37
  end
34
38
 
35
39
  def has_required?
36
- super && (input_options[:include_blank] || multiple?)
40
+ super && (input_options[:include_blank] || input_options[:prompt] || multiple?)
37
41
  end
38
42
 
39
43
  # Check if :include_blank must be included by default.
@@ -70,13 +74,17 @@ module SimpleForm
70
74
  elsif collection_includes_basic_objects?(collection_classes)
71
75
  { label: :to_s, value: :to_s }
72
76
  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) } }
77
+ detect_method_from_class(collection_classes)
77
78
  end
78
79
  end
79
80
 
81
+ def detect_method_from_class(collection_classes)
82
+ sample = collection.first || collection.last
83
+
84
+ { label: SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
85
+ value: SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
86
+ end
87
+
80
88
  def detect_collection_classes(some_collection = collection)
81
89
  some_collection.map { |e| e.class }.uniq
82
90
  end
@@ -88,14 +96,27 @@ module SimpleForm
88
96
  end
89
97
 
90
98
  def translate_collection
91
- if translated_collection = translate(:options)
99
+ if translated_collection = translate_from_namespace(:options)
92
100
  @collection = collection.map do |key|
93
- [translated_collection[key] || key, key]
101
+ html_key = "#{key}_html".to_sym
102
+
103
+ if translated_collection[html_key]
104
+ [translated_collection[html_key].html_safe || key, key.to_s]
105
+ else
106
+ [translated_collection[key] || key, key.to_s]
107
+ end
94
108
  end
95
109
  true
96
110
  end
97
111
  end
112
+
113
+ def translate_option(options, key)
114
+ if options[key] == :translate
115
+ namespace = key.to_s.pluralize
116
+
117
+ options[key] = translate_from_namespace(namespace, true)
118
+ end
119
+ end
98
120
  end
99
121
  end
100
122
  end
101
-