simple_form 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data/CHANGELOG.md +234 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +816 -0
  4. data/lib/generators/simple_form/install_generator.rb +15 -1
  5. data/lib/generators/simple_form/templates/README +12 -0
  6. data/lib/generators/simple_form/templates/_form.html.erb +2 -2
  7. data/lib/generators/simple_form/templates/_form.html.haml +2 -2
  8. data/lib/generators/simple_form/templates/_form.html.slim +4 -4
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +176 -0
  10. data/lib/simple_form/action_view_extensions/builder.rb +206 -59
  11. data/lib/simple_form/action_view_extensions/form_helper.rb +30 -23
  12. data/lib/simple_form/components/errors.rb +6 -24
  13. data/lib/simple_form/components/hints.rb +7 -21
  14. data/lib/simple_form/components/html5.rb +26 -0
  15. data/lib/simple_form/components/labels.rb +22 -14
  16. data/lib/simple_form/components/maxlength.rb +41 -0
  17. data/lib/simple_form/components/min_max.rb +49 -0
  18. data/lib/simple_form/components/pattern.rb +34 -0
  19. data/lib/simple_form/components/placeholders.rb +5 -17
  20. data/lib/simple_form/components/readonly.rb +22 -0
  21. data/lib/simple_form/components.rb +11 -1
  22. data/lib/simple_form/core_ext/hash.rb +16 -0
  23. data/lib/simple_form/error_notification.rb +9 -3
  24. data/lib/simple_form/form_builder.rb +105 -28
  25. data/lib/simple_form/helpers/autofocus.rb +11 -0
  26. data/lib/simple_form/helpers/disabled.rb +15 -0
  27. data/lib/simple_form/helpers/readonly.rb +15 -0
  28. data/lib/simple_form/helpers/required.rb +10 -11
  29. data/lib/simple_form/helpers/validators.rb +4 -4
  30. data/lib/simple_form/helpers.rb +7 -4
  31. data/lib/simple_form/inputs/base.rb +53 -81
  32. data/lib/simple_form/inputs/boolean_input.rb +46 -4
  33. data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
  34. data/lib/simple_form/inputs/collection_input.rb +27 -13
  35. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +67 -0
  36. data/lib/simple_form/inputs/collection_select_input.rb +14 -0
  37. data/lib/simple_form/inputs/date_time_input.rb +10 -6
  38. data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
  39. data/lib/simple_form/inputs/hidden_input.rb +3 -6
  40. data/lib/simple_form/inputs/numeric_input.rb +3 -51
  41. data/lib/simple_form/inputs/password_input.rb +1 -2
  42. data/lib/simple_form/inputs/priority_input.rb +2 -2
  43. data/lib/simple_form/inputs/range_input.rb +1 -3
  44. data/lib/simple_form/inputs/string_input.rb +6 -8
  45. data/lib/simple_form/inputs/text_input.rb +1 -2
  46. data/lib/simple_form/inputs.rb +17 -13
  47. data/lib/simple_form/version.rb +1 -1
  48. data/lib/simple_form/wrappers/builder.rb +103 -0
  49. data/lib/simple_form/wrappers/many.rb +69 -0
  50. data/lib/simple_form/wrappers/root.rb +34 -0
  51. data/lib/simple_form/wrappers/single.rb +18 -0
  52. data/lib/simple_form/wrappers.rb +8 -0
  53. data/lib/simple_form.rb +118 -48
  54. data/test/action_view_extensions/builder_test.rb +285 -102
  55. data/test/action_view_extensions/form_helper_test.rb +32 -10
  56. data/test/components/label_test.rb +44 -5
  57. data/test/form_builder/association_test.rb +177 -0
  58. data/test/form_builder/button_test.rb +47 -0
  59. data/test/{error_notification_test.rb → form_builder/error_notification_test.rb} +18 -1
  60. data/test/form_builder/error_test.rb +121 -0
  61. data/test/form_builder/general_test.rb +356 -0
  62. data/test/form_builder/hint_test.rb +123 -0
  63. data/test/form_builder/input_field_test.rb +63 -0
  64. data/test/form_builder/label_test.rb +65 -0
  65. data/test/form_builder/wrapper_test.rb +149 -0
  66. data/test/generators/simple_form_generator_test.rb +32 -0
  67. data/test/inputs/boolean_input_test.rb +101 -0
  68. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  69. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  70. data/test/inputs/collection_select_input_test.rb +241 -0
  71. data/test/inputs/datetime_input_test.rb +99 -0
  72. data/test/inputs/disabled_test.rb +38 -0
  73. data/test/inputs/discovery_test.rb +61 -0
  74. data/test/inputs/file_input_test.rb +16 -0
  75. data/test/inputs/general_test.rb +69 -0
  76. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  77. data/test/inputs/hidden_input_test.rb +30 -0
  78. data/test/inputs/numeric_input_test.rb +167 -0
  79. data/test/inputs/priority_input_test.rb +43 -0
  80. data/test/inputs/readonly_test.rb +61 -0
  81. data/test/inputs/required_test.rb +113 -0
  82. data/test/inputs/string_input_test.rb +140 -0
  83. data/test/inputs/text_input_test.rb +24 -0
  84. data/test/support/misc_helpers.rb +53 -12
  85. data/test/support/mock_controller.rb +2 -2
  86. data/test/support/models.rb +20 -5
  87. data/test/test_helper.rb +11 -12
  88. metadata +124 -96
  89. data/.gitignore +0 -3
  90. data/.gitmodules +0 -3
  91. data/.travis.yml +0 -15
  92. data/CHANGELOG.rdoc +0 -159
  93. data/Gemfile +0 -9
  94. data/README.rdoc +0 -466
  95. data/Rakefile +0 -27
  96. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +0 -93
  97. data/lib/simple_form/components/wrapper.rb +0 -38
  98. data/lib/simple_form/helpers/has_errors.rb +0 -15
  99. data/lib/simple_form/helpers/maxlength.rb +0 -24
  100. data/lib/simple_form/helpers/pattern.rb +0 -28
  101. data/simple_form.gemspec +0 -25
  102. data/test/components/error_test.rb +0 -56
  103. data/test/components/hint_test.rb +0 -74
  104. data/test/components/wrapper_test.rb +0 -63
  105. data/test/custom_components.rb +0 -7
  106. data/test/form_builder_test.rb +0 -930
  107. data/test/inputs_test.rb +0 -995
  108. /data/test/{discovery_inputs.rb → support/discovery_inputs.rb} +0 -0
@@ -13,9 +13,8 @@ module SimpleForm
13
13
  end
14
14
 
15
15
  def input
16
- label_method, value_method = detect_collection_methods
17
- @builder.send(:"collection_#{input_type}", attribute_name, collection,
18
- value_method, label_method, input_options, input_html_options)
16
+ raise NotImplementedError,
17
+ "input should be implemented by classes inheriting from CollectionInput"
19
18
  end
20
19
 
21
20
  def input_options
@@ -27,18 +26,23 @@ module SimpleForm
27
26
  private
28
27
 
29
28
  def collection
30
- @collection ||= (options.delete(:collection) || self.class.boolean_collection).to_a
29
+ @collection ||= begin
30
+ collection = options.delete(:collection) || self.class.boolean_collection
31
+ collection.respond_to?(:call) ? collection.call : collection.to_a
32
+ end
31
33
  end
32
34
 
33
- # Select components does not allow the required html tag.
34
35
  def has_required?
35
- super && input_type != :select
36
+ super && (input_options[:include_blank] || multiple?)
36
37
  end
37
38
 
38
39
  # Check if :include_blank must be included by default.
39
40
  def skip_include_blank?
40
- (options.keys & [:prompt, :include_blank, :default, :selected]).any? ||
41
- options[:input_html].try(:[], :multiple)
41
+ (options.keys & [:prompt, :include_blank, :default, :selected]).any? || multiple?
42
+ end
43
+
44
+ def multiple?
45
+ !!options[:input_html].try(:[], :multiple)
42
46
  end
43
47
 
44
48
  # Detect the right method to find the label and value for a collection.
@@ -58,10 +62,10 @@ module SimpleForm
58
62
  [label, value]
59
63
  end
60
64
 
61
- def detect_common_display_methods
62
- collection_classes = detect_collection_classes
65
+ def detect_common_display_methods(collection_classes = detect_collection_classes)
66
+ collection_translated = translate_collection if collection_classes == [Symbol]
63
67
 
64
- if collection_classes.include?(Array)
68
+ if collection_translated || collection_classes.include?(Array)
65
69
  { :label => :first, :value => :last }
66
70
  elsif collection_includes_basic_objects?(collection_classes)
67
71
  { :label => :to_s, :value => :to_s }
@@ -73,8 +77,8 @@ module SimpleForm
73
77
  end
74
78
  end
75
79
 
76
- def detect_collection_classes
77
- collection.map { |e| e.class }.uniq
80
+ def detect_collection_classes(some_collection = collection)
81
+ some_collection.map { |e| e.class }.uniq
78
82
  end
79
83
 
80
84
  def collection_includes_basic_objects?(collection_classes)
@@ -82,6 +86,16 @@ module SimpleForm
82
86
  String, Integer, Fixnum, Bignum, Float, NilClass, Symbol, TrueClass, FalseClass
83
87
  ]).any?
84
88
  end
89
+
90
+ def translate_collection
91
+ if translated_collection = translate(:options)
92
+ @collection = collection.map do |key|
93
+ [translated_collection[key] || key, key]
94
+ end
95
+ true
96
+ end
97
+ end
85
98
  end
86
99
  end
87
100
  end
101
+
@@ -0,0 +1,67 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class CollectionRadioButtonsInput < CollectionInput
4
+ def input
5
+ label_method, value_method = detect_collection_methods
6
+
7
+ @builder.send("collection_#{input_type}",
8
+ attribute_name, collection, value_method, label_method,
9
+ input_options, input_html_options, &collection_block_for_nested_boolean_style
10
+ )
11
+ end
12
+
13
+ def input_options
14
+ options = super
15
+ apply_default_collection_options!(options)
16
+ apply_nested_boolean_collection_options!(options) if nested_boolean_style?
17
+ options
18
+ end
19
+
20
+ protected
21
+
22
+ def apply_default_collection_options!(options)
23
+ unless options.key?(:item_wrapper_tag)
24
+ options[:item_wrapper_tag] = SimpleForm.item_wrapper_tag
25
+ end
26
+ options[:item_wrapper_class] = [
27
+ item_wrapper_class, options[:item_wrapper_class], SimpleForm.item_wrapper_class
28
+ ].compact.presence
29
+
30
+ unless options.key?(:collection_wrapper_tag)
31
+ options[:collection_wrapper_tag] = SimpleForm.collection_wrapper_tag
32
+ end
33
+ options[:collection_wrapper_class] = [
34
+ options[:collection_wrapper_class], SimpleForm.collection_wrapper_class
35
+ ].compact.presence
36
+ end
37
+
38
+ # Force item wrapper to be a label when using nested boolean, to support
39
+ # configuring classes through :item_wrapper_class, and to maintain
40
+ # compatibility with :inline style and default :item_wrapper_tag.
41
+ def apply_nested_boolean_collection_options!(options)
42
+ options[:item_wrapper_tag] = :label
43
+ end
44
+
45
+ def collection_block_for_nested_boolean_style
46
+ return unless nested_boolean_style?
47
+
48
+ proc { |builder| build_nested_boolean_style_item_tag(builder) }
49
+ end
50
+
51
+ def build_nested_boolean_style_item_tag(collection_builder)
52
+ collection_builder.radio_button + collection_builder.text
53
+ end
54
+
55
+ def item_wrapper_class
56
+ "radio"
57
+ end
58
+
59
+ # Do not attempt to generate label[for] attributes by default, unless an
60
+ # explicit html option is given. This avoids generating labels pointing to
61
+ # non existent fields.
62
+ def generate_label_for_attribute?
63
+ false
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class CollectionSelectInput < CollectionInput
4
+ def input
5
+ label_method, value_method = detect_collection_methods
6
+
7
+ @builder.collection_select(
8
+ attribute_name, collection, value_method, label_method,
9
+ input_options, input_html_options
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,19 +5,23 @@ module SimpleForm
5
5
  @builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
6
6
  end
7
7
 
8
- private
9
-
10
8
  def has_required?
11
9
  false
12
10
  end
13
11
 
12
+ private
13
+
14
14
  def label_target
15
- case input_type
15
+ position = case input_type
16
16
  when :date, :datetime
17
- "#{attribute_name}_1i"
18
- when :time
19
- "#{attribute_name}_4i"
17
+ date_order = input_options[:order] || I18n.t('date.order')
18
+ date_order.first
19
+ else
20
+ :hour
20
21
  end
22
+
23
+ position = ActionView::Helpers::DateTimeSelector::POSITION[position]
24
+ "#{attribute_name}_#{position}i"
21
25
  end
22
26
  end
23
27
  end
@@ -0,0 +1,41 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class GroupedCollectionSelectInput < CollectionInput
4
+ def input
5
+ label_method, value_method = detect_collection_methods
6
+ @builder.grouped_collection_select(attribute_name, grouped_collection,
7
+ group_method, group_label_method, value_method, label_method,
8
+ input_options, input_html_options)
9
+ end
10
+
11
+ private
12
+
13
+ def grouped_collection
14
+ @grouped_collection ||= begin
15
+ grouped_collection = options.delete(:collection)
16
+ grouped_collection.respond_to?(:call) ? grouped_collection.call : grouped_collection.to_a
17
+ end
18
+ end
19
+
20
+ # Sample collection
21
+ def collection
22
+ @collection ||= grouped_collection.first.try(:send, group_method) || []
23
+ end
24
+
25
+ def group_method
26
+ @group_method ||= options.delete(:group_method)
27
+ end
28
+
29
+ def group_label_method
30
+ label = options.delete(:group_label_method)
31
+
32
+ unless label
33
+ common_method_for = detect_common_display_methods(detect_collection_classes(grouped_collection))
34
+ label = common_method_for[:label]
35
+ end
36
+
37
+ label
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,17 +1,14 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class HiddenInput < Base
4
- def render
4
+ disable :label, :errors, :hint, :required
5
+
6
+ def input
5
7
  @builder.hidden_field(attribute_name, input_html_options)
6
8
  end
7
- alias :input :render
8
9
 
9
10
  private
10
11
 
11
- def attribute_required?
12
- false
13
- end
14
-
15
12
  def required_class
16
13
  nil
17
14
  end
@@ -1,72 +1,24 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class NumericInput < Base
4
- enable :placeholder
4
+ enable :placeholder, :min_max
5
5
 
6
6
  def input
7
7
  add_size!
8
- if SimpleForm.html5
8
+ input_html_classes.unshift("numeric")
9
+ if html5?
9
10
  input_html_options[:type] ||= "number"
10
11
  input_html_options[:step] ||= integer? ? 1 : "any"
11
- infer_attributes_from_validations!
12
12
  end
13
13
  @builder.text_field(attribute_name, input_html_options)
14
14
  end
15
15
 
16
- def input_html_classes
17
- super.unshift("numeric")
18
- end
19
-
20
16
  private
21
17
 
22
18
  # Rails adds the size attr by default, if the :size key does not exist.
23
19
  def add_size!
24
20
  input_html_options[:size] ||= nil
25
21
  end
26
-
27
- def infer_attributes_from_validations!
28
- return unless has_validators?
29
-
30
- numeric_validator = find_numericality_validator or return
31
- validator_options = numeric_validator.options
32
-
33
- input_html_options[:min] ||= minimum_value(validator_options)
34
- input_html_options[:max] ||= maximum_value(validator_options)
35
- end
36
-
37
- def integer?
38
- input_type == :integer
39
- end
40
-
41
- def minimum_value(validator_options)
42
- if integer? && validator_options.key?(:greater_than)
43
- evaluate_validator_option(validator_options[:greater_than]) + 1
44
- else
45
- evaluate_validator_option(validator_options[:greater_than_or_equal_to])
46
- end
47
- end
48
-
49
- def maximum_value(validator_options)
50
- if integer? && validator_options.key?(:less_than)
51
- evaluate_validator_option(validator_options[:less_than]) - 1
52
- else
53
- evaluate_validator_option(validator_options[:less_than_or_equal_to])
54
- end
55
- end
56
-
57
- def find_numericality_validator
58
- find_validator(ActiveModel::Validations::NumericalityValidator)
59
- end
60
-
61
- def evaluate_validator_option(option)
62
- if option.is_a?(Numeric)
63
- option
64
- elsif option.is_a?(Symbol)
65
- object.send(option)
66
- elsif option.respond_to?(:call)
67
- option.call(object)
68
- end
69
- end
70
22
  end
71
23
  end
72
24
  end
@@ -1,11 +1,10 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class PasswordInput < Base
4
- enable :placeholder
4
+ enable :placeholder, :maxlength
5
5
 
6
6
  def input
7
7
  add_size!
8
- add_maxlength!
9
8
  @builder.password_field(attribute_name, input_html_options)
10
9
  end
11
10
  end
@@ -1,6 +1,6 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
- class PriorityInput < CollectionInput
3
+ class PriorityInput < CollectionSelectInput
4
4
  def input
5
5
  @builder.send(:"#{input_type}_select", attribute_name, input_priority,
6
6
  input_options, input_html_options)
@@ -10,7 +10,7 @@ module SimpleForm
10
10
  options[:priority] || SimpleForm.send(:"#{input_type}_priority")
11
11
  end
12
12
 
13
- protected
13
+ protected
14
14
 
15
15
  def has_required?
16
16
  false
@@ -1,10 +1,8 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class RangeInput < NumericInput
4
- disable :placeholder
5
-
6
4
  def input
7
- if SimpleForm.html5
5
+ if html5?
8
6
  input_html_options[:type] ||= "range"
9
7
  input_html_options[:step] ||= 1
10
8
  end
@@ -1,20 +1,18 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class StringInput < Base
4
- enable :placeholder
4
+ enable :placeholder, :maxlength, :pattern
5
5
 
6
6
  def input
7
- input_html_options[:type] ||= input_type if SimpleForm.html5 && !string?
8
- add_maxlength!
9
- add_pattern!
7
+ unless string?
8
+ input_html_classes.unshift("string")
9
+ input_html_options[:type] ||= input_type if html5?
10
+ end
11
+
10
12
  add_size!
11
13
  @builder.text_field(attribute_name, input_html_options)
12
14
  end
13
15
 
14
- def input_html_classes
15
- string? ? super : super.unshift("string")
16
- end
17
-
18
16
  private
19
17
 
20
18
  def string?
@@ -1,10 +1,9 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class TextInput < Base
4
- enable :placeholder
4
+ enable :placeholder, :maxlength
5
5
 
6
6
  def input
7
- add_maxlength!
8
7
  @builder.text_area(attribute_name, input_html_options)
9
8
  end
10
9
  end
@@ -1,17 +1,21 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
- autoload :Base, 'simple_form/inputs/base'
4
- autoload :BlockInput, 'simple_form/inputs/block_input'
5
- autoload :BooleanInput, 'simple_form/inputs/boolean_input'
6
- autoload :CollectionInput, 'simple_form/inputs/collection_input'
7
- autoload :DateTimeInput, 'simple_form/inputs/date_time_input'
8
- autoload :FileInput, 'simple_form/inputs/file_input'
9
- autoload :HiddenInput, 'simple_form/inputs/hidden_input'
10
- autoload :NumericInput, 'simple_form/inputs/numeric_input'
11
- autoload :PasswordInput, 'simple_form/inputs/password_input'
12
- autoload :PriorityInput, 'simple_form/inputs/priority_input'
13
- autoload :RangeInput, 'simple_form/inputs/range_input'
14
- autoload :StringInput, 'simple_form/inputs/string_input'
15
- autoload :TextInput, 'simple_form/inputs/text_input'
3
+ autoload :Base, 'simple_form/inputs/base'
4
+ autoload :BlockInput, 'simple_form/inputs/block_input'
5
+ autoload :BooleanInput, 'simple_form/inputs/boolean_input'
6
+ autoload :CollectionCheckBoxesInput, 'simple_form/inputs/collection_check_boxes_input'
7
+ autoload :CollectionInput, 'simple_form/inputs/collection_input'
8
+ autoload :CollectionRadioButtonsInput, 'simple_form/inputs/collection_radio_buttons_input'
9
+ autoload :CollectionSelectInput, 'simple_form/inputs/collection_select_input'
10
+ autoload :DateTimeInput, 'simple_form/inputs/date_time_input'
11
+ autoload :FileInput, 'simple_form/inputs/file_input'
12
+ autoload :GroupedCollectionSelectInput, 'simple_form/inputs/grouped_collection_select_input'
13
+ autoload :HiddenInput, 'simple_form/inputs/hidden_input'
14
+ autoload :NumericInput, 'simple_form/inputs/numeric_input'
15
+ autoload :PasswordInput, 'simple_form/inputs/password_input'
16
+ autoload :PriorityInput, 'simple_form/inputs/priority_input'
17
+ autoload :RangeInput, 'simple_form/inputs/range_input'
18
+ autoload :StringInput, 'simple_form/inputs/string_input'
19
+ autoload :TextInput, 'simple_form/inputs/text_input'
16
20
  end
17
21
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleForm
2
- VERSION = "1.5.2".freeze
2
+ VERSION = "2.0.0".freeze
3
3
  end
@@ -0,0 +1,103 @@
1
+ module SimpleForm
2
+ module Wrappers
3
+ # Provides the builder syntax for components. The builder provides
4
+ # three methods `use`, `optional` and `wrapper` and they allow the following invocations:
5
+ #
6
+ # config.wrappers do |b|
7
+ # # Use a single component
8
+ # b.use :html5
9
+ #
10
+ # # Use the component, but do not automatically lookup. It will only be triggered when
11
+ # # :placeholder is explicitly set.
12
+ # b.optional :placeholder
13
+ #
14
+ # # Use a component with specific wrapper options
15
+ # b.use :error, :wrap_with => { :tag => "span", :class => "error" }
16
+ #
17
+ # # Use a set of components by wrapping them in a tag+class.
18
+ # b.wrapper :tag => "div", :class => "another" do |ba|
19
+ # ba.use :label
20
+ # ba.use :input
21
+ # end
22
+ #
23
+ # # Use a set of components by wrapping them in a tag+class.
24
+ # # This wrapper is identified by :label_input, which means it can
25
+ # # be turned off on demand with `f.input :name, :label_input => false`
26
+ # b.wrapper :label_input, :tag => "div", :class => "another" do |ba|
27
+ # ba.use :label
28
+ # ba.use :input
29
+ # end
30
+ # end
31
+ #
32
+ # The builder also accepts default options at the root level. This is usually
33
+ # used if you want a component to be disabled by default:
34
+ #
35
+ # config.wrappers :hint => false do |b|
36
+ # b.use :hint
37
+ # b.use :label_input
38
+ # end
39
+ #
40
+ # In the example above, hint defaults to false, which means it won't automatically
41
+ # do the lookup anymore. It will only be triggered when :hint is explicitly set.
42
+ class Builder
43
+ def initialize(options)
44
+ @options = options
45
+ @components = []
46
+ end
47
+
48
+ def use(name, options=nil, &block)
49
+ if block_given?
50
+ ActiveSupport::Deprecation.warn "Passing a block to use is deprecated. " \
51
+ "Please use wrapper instead of use."
52
+ return wrapper(name, options, &block)
53
+ end
54
+
55
+ if options && options.keys != [:wrap_with]
56
+ ActiveSupport::Deprecation.warn "Passing :tag, :class and others to use is deprecated. " \
57
+ "Please invoke b.use #{name.inspect}, :wrap_with => #{options.inspect} instead."
58
+ options = { :wrap_with => options }
59
+ end
60
+
61
+ if options && wrapper = options[:wrap_with]
62
+ @components << Single.new(name, wrapper)
63
+ else
64
+ @components << name
65
+ end
66
+ end
67
+
68
+ def optional(name, options=nil, &block)
69
+ if block_given?
70
+ ActiveSupport::Deprecation.warn "Passing a block to optional is deprecated. " \
71
+ "Please use wrapper instead of optional."
72
+ return wrapper(name, options, &block)
73
+ end
74
+
75
+ if options && options.keys != [:wrap_with]
76
+ ActiveSupport::Deprecation.warn "Passing :tag, :class and others to optional is deprecated. " \
77
+ "Please invoke b.optional #{name.inspect}, :wrap_with => #{options.inspect} instead."
78
+ options = { :wrap_with => options }
79
+ end
80
+
81
+ @options[name] = false
82
+ use(name, options, &block)
83
+ end
84
+
85
+ def wrapper(name, options=nil)
86
+ if block_given?
87
+ name, options = nil, name if name.is_a?(Hash)
88
+ builder = self.class.new(@options)
89
+ options ||= {}
90
+ options[:tag] = :div if options[:tag].nil?
91
+ yield builder
92
+ @components << Many.new(name, builder.to_a, options)
93
+ else
94
+ raise ArgumentError, "A block is required as argument to wrapper"
95
+ end
96
+ end
97
+
98
+ def to_a
99
+ @components
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,69 @@
1
+ module SimpleForm
2
+ module Wrappers
3
+ # A wrapper is an object that holds several components and render them.
4
+ # A component may either be a symbol or any object that responds to `render`.
5
+ # This API allows inputs/components to be easily wrapped, removing the
6
+ # need to modify the code only to wrap input in an extra tag.
7
+ #
8
+ # `Many` represents a wrapper around several components at the same time.
9
+ # It may optionally receive a namespace, allowing it to be configured
10
+ # on demand on input generation.
11
+ class Many
12
+ attr_reader :namespace, :defaults, :components
13
+ alias :to_sym :namespace
14
+
15
+ def initialize(namespace, components, defaults={})
16
+ @namespace = namespace
17
+ @components = components
18
+ @defaults = defaults
19
+ @defaults[:tag] = :div unless @defaults.key?(:tag)
20
+ @defaults[:class] = Array.wrap(@defaults[:class])
21
+ end
22
+
23
+ def render(input)
24
+ content = "".html_safe
25
+ options = input.options
26
+
27
+ components.each do |component|
28
+ next if options[component] == false
29
+ rendered = component.respond_to?(:render) ? component.render(input) : input.send(component)
30
+ content.safe_concat rendered.to_s if rendered
31
+ end
32
+
33
+ wrap(input, options, content)
34
+ end
35
+
36
+ def find(name)
37
+ return self if namespace == name
38
+
39
+ @components.each do |c|
40
+ if c.is_a?(Symbol)
41
+ return nil if c == namespace
42
+ elsif value = c.find(name)
43
+ return value
44
+ end
45
+ end
46
+
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def wrap(input, options, content)
53
+ return content if options[namespace] == false
54
+
55
+ tag = (namespace && options[:"#{namespace}_tag"]) || @defaults[:tag]
56
+ return content unless tag
57
+
58
+ klass = html_classes(input, options)
59
+ opts = options[:"#{namespace}_html"] || {}
60
+ opts[:class] = (klass << opts[:class]).join(' ').strip unless klass.empty?
61
+ input.template.content_tag(tag, content, opts)
62
+ end
63
+
64
+ def html_classes(input, options)
65
+ @defaults[:class].dup
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ module SimpleForm
2
+ module Wrappers
3
+ # `Root` is the root wrapper for all components. It is special cased to
4
+ # always have a namespace and to add special html classes.
5
+ class Root < Many
6
+ attr_reader :options
7
+
8
+ def initialize(*args)
9
+ super(:wrapper, *args)
10
+ @options = @defaults.except(:tag, :class, :error_class, :hint_class)
11
+ end
12
+
13
+ def render(input)
14
+ input.options.reverse_merge!(@options)
15
+ super
16
+ end
17
+
18
+ # Provide a fallback if name cannot be found.
19
+ def find(name)
20
+ super || SimpleForm::Wrappers::Many.new(name, [name])
21
+ end
22
+
23
+ private
24
+
25
+ def html_classes(input, options)
26
+ css = options[:wrapper_class] ? Array.wrap(options[:wrapper_class]) : @defaults[:class]
27
+ css += SimpleForm.additional_classes_for(:wrapper) { input.html_classes }
28
+ css << (options[:wrapper_error_class] || @defaults[:error_class]) if input.has_errors?
29
+ css << (options[:wrapper_hint_class] || @defaults[:hint_class]) if input.has_hint?
30
+ css
31
+ end
32
+ end
33
+ end
34
+ end