simple_form 3.0.1 → 3.1.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -27
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +204 -81
  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 +28 -7
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +115 -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 +1 -1
  16. data/lib/simple_form/components/label_input.rb +20 -2
  17. data/lib/simple_form/components/labels.rb +10 -6
  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 +123 -73
  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 +6 -11
  30. data/lib/simple_form/inputs/collection_select_input.rb +4 -2
  31. data/lib/simple_form/inputs/date_time_input.rb +12 -2
  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 +89 -64
  59. data/test/form_builder/hint_test.rb +18 -18
  60. data/test/form_builder/input_field_test.rb +80 -16
  61. data/test/form_builder/label_test.rb +45 -13
  62. data/test/form_builder/wrapper_test.rb +135 -19
  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 +117 -50
  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 +13 -13
  79. data/test/inputs/string_input_test.rb +50 -30
  80. data/test/inputs/text_input_test.rb +7 -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 +77 -5
  84. data/test/support/models.rb +60 -24
  85. data/test/test_helper.rb +7 -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,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,18 @@ 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 = {})
134
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
135
+
139
136
  options = options.dup
140
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
137
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *components)
141
138
  options = @defaults.deep_dup.deep_merge(options) if @defaults
142
139
 
143
- SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
140
+ input = find_input(attribute_name, options)
141
+ wrapper = find_wrapper(input.input_type, options)
142
+ components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
143
+
144
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
144
145
  end
145
146
 
146
147
  # Helper for dealing with association selects/radios, generating the
@@ -171,7 +172,7 @@ module SimpleForm
171
172
  #
172
173
  # 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
174
  #
174
- def association(association, options={}, &block)
175
+ def association(association, options = {}, &block)
175
176
  options = options.dup
176
177
 
177
178
  return simple_fields_for(*[association,
@@ -183,31 +184,9 @@ module SimpleForm
183
184
  raise "Association #{association.inspect} not found" unless reflection
184
185
 
185
186
  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
187
+ options[:collection] ||= fetch_association_collection(reflection, options)
188
+
189
+ attribute = build_association_attribute(reflection, association, options)
211
190
 
212
191
  input(attribute, options.merge(reflection: reflection))
213
192
  end
@@ -242,7 +221,7 @@ module SimpleForm
242
221
  # f.error :name
243
222
  # f.error :name, id: "cool_error"
244
223
  #
245
- def error(attribute_name, options={})
224
+ def error(attribute_name, options = {})
246
225
  options = options.dup
247
226
 
248
227
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -259,7 +238,7 @@ module SimpleForm
259
238
  #
260
239
  # f.full_error :token #=> <span class="error">Token is invalid</span>
261
240
  #
262
- def full_error(attribute_name, options={})
241
+ def full_error(attribute_name, options = {})
263
242
  options = options.dup
264
243
 
265
244
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -281,7 +260,7 @@ module SimpleForm
281
260
  # f.hint :name, id: "cool_hint"
282
261
  # f.hint "Don't forget to accept this"
283
262
  #
284
- def hint(attribute_name, options={})
263
+ def hint(attribute_name, options = {})
285
264
  options = options.dup
286
265
 
287
266
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -332,7 +311,7 @@ module SimpleForm
332
311
  # f.error_notification message: 'Something went wrong'
333
312
  # f.error_notification id: 'user_error_message', class: 'form_error'
334
313
  #
335
- def error_notification(options={})
314
+ def error_notification(options = {})
336
315
  SimpleForm::ErrorNotification.new(self, options).render
337
316
  end
338
317
 
@@ -464,15 +443,56 @@ module SimpleForm
464
443
  @lookup_action ||= begin
465
444
  action = template.controller && template.controller.action_name
466
445
  return unless action
467
- action = action.to_sym
446
+ action = action.to_s
468
447
  ACTIONS[action] || action
469
448
  end
470
449
  end
471
450
 
472
451
  private
473
452
 
453
+ def fetch_association_collection(reflection, options)
454
+ options.fetch(:collection) do
455
+ relation = reflection.klass.all
456
+
457
+ if reflection.respond_to?(:scope) && reflection.scope
458
+ relation = reflection.klass.instance_exec(&reflection.scope)
459
+ else
460
+ order = reflection.options[:order]
461
+ conditions = reflection.options[:conditions]
462
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
463
+
464
+ relation = relation.where(conditions)
465
+ relation = relation.order(order) if relation.respond_to?(:order)
466
+ end
467
+
468
+ relation
469
+ end
470
+ end
471
+
472
+ def build_association_attribute(reflection, association, options)
473
+ case reflection.macro
474
+ when :belongs_to
475
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
476
+ when :has_one
477
+ raise ArgumentError, ":has_one associations are not supported by f.association"
478
+ else
479
+ if options[:as] == :select
480
+ html_options = options[:input_html] ||= {}
481
+ html_options[:multiple] = true unless html_options.key?(:multiple)
482
+ end
483
+
484
+ # Force the association to be preloaded for performance.
485
+ if options[:preload] != false && object.respond_to?(association)
486
+ target = object.send(association)
487
+ target.to_a if target.respond_to?(:to_a)
488
+ end
489
+
490
+ :"#{reflection.name.to_s.singularize}_ids"
491
+ end
492
+ end
493
+
474
494
  # Find an input based on the attribute name.
475
- def find_input(attribute_name, options={}, &block) #:nodoc:
495
+ def find_input(attribute_name, options = {}, &block)
476
496
  column = find_attribute_column(attribute_name)
477
497
  input_type = default_input_type(attribute_name, column, options)
478
498
 
@@ -486,7 +506,7 @@ module SimpleForm
486
506
  # Attempt to guess the better input type given the defined options. By
487
507
  # default alwayls fallback to the user :as option, or to a :select when a
488
508
  # collection is given.
489
- def default_input_type(attribute_name, column, options) #:nodoc:
509
+ def default_input_type(attribute_name, column, options)
490
510
  return options[:as].to_sym if options[:as]
491
511
  return :select if options[:collection]
492
512
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
@@ -511,24 +531,24 @@ module SimpleForm
511
531
  end
512
532
  end
513
533
 
514
- def find_custom_type(attribute_name) #:nodoc:
534
+ def find_custom_type(attribute_name)
515
535
  SimpleForm.input_mappings.find { |match, type|
516
536
  attribute_name =~ match
517
537
  }.try(:last) if SimpleForm.input_mappings
518
538
  end
519
539
 
520
- def file_method?(attribute_name) #:nodoc:
540
+ def file_method?(attribute_name)
521
541
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
522
542
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
523
543
  end
524
544
 
525
- def find_attribute_column(attribute_name) #:nodoc:
526
- if @object.respond_to?(:column_for_attribute)
545
+ def find_attribute_column(attribute_name)
546
+ if @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
527
547
  @object.column_for_attribute(attribute_name)
528
548
  end
529
549
  end
530
550
 
531
- def find_association_reflection(association) #:nodoc:
551
+ def find_association_reflection(association)
532
552
  if @object.class.respond_to?(:reflect_on_association)
533
553
  @object.class.reflect_on_association(association)
534
554
  end
@@ -541,24 +561,42 @@ module SimpleForm
541
561
  # b) Or use the found mapping
542
562
  # 2) If not, fallbacks to #{input_type}Input
543
563
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
544
- def find_mapping(input_type) #:nodoc:
564
+ def find_mapping(input_type)
545
565
  discovery_cache[input_type] ||=
546
566
  if mapping = self.class.mappings[input_type]
547
567
  mapping_override(mapping) || mapping
548
568
  else
549
569
  camelized = "#{input_type.to_s.camelize}Input"
550
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
570
+ attempt_mapping_with_custom_namespace(camelized) ||
571
+ attempt_mapping(camelized, Object) ||
572
+ attempt_mapping(camelized, self.class) ||
551
573
  raise("No input found for #{input_type}")
552
574
  end
553
575
  end
554
576
 
555
- def find_wrapper_mapping(input_type) #:nodoc:
556
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
577
+ # Attempts to find a wrapper mapping. It follows the following rules:
578
+ #
579
+ # 1) It tries to find a wrapper for the current form
580
+ # 2) If not, it tries to find a config
581
+ def find_wrapper_mapping(input_type)
582
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
583
+ options[:wrapper_mappings][input_type]
584
+ else
585
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
586
+ end
587
+ end
588
+
589
+ def find_wrapper(input_type, options)
590
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
591
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
592
+ else
593
+ wrapper
594
+ end
557
595
  end
558
596
 
559
597
  # If cache_discovery is enabled, use the class level cache that persists
560
598
  # between requests, otherwise use the instance one.
561
- def discovery_cache #:nodoc:
599
+ def discovery_cache
562
600
  if SimpleForm.cache_discovery
563
601
  self.class.discovery_cache
564
602
  else
@@ -566,14 +604,16 @@ module SimpleForm
566
604
  end
567
605
  end
568
606
 
569
- def mapping_override(klass) #:nodoc:
607
+ def mapping_override(klass)
570
608
  name = klass.name
571
609
  if name =~ /^SimpleForm::Inputs/
572
- attempt_mapping name.split("::").last, Object
610
+ input_name = name.split("::").last
611
+ attempt_mapping_with_custom_namespace(input_name) ||
612
+ attempt_mapping(input_name, Object)
573
613
  end
574
614
  end
575
615
 
576
- def attempt_mapping(mapping, at) #:nodoc:
616
+ def attempt_mapping(mapping, at)
577
617
  return if SimpleForm.inputs_discovery == false && at == Object
578
618
 
579
619
  begin
@@ -582,5 +622,15 @@ module SimpleForm
582
622
  raise if e.message !~ /#{mapping}$/
583
623
  end
584
624
  end
625
+
626
+ def attempt_mapping_with_custom_namespace(input_name)
627
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
628
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
629
+ return mapping
630
+ end
631
+ end
632
+
633
+ nil
634
+ end
585
635
  end
586
636
  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
-
@@ -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