simple_form 1.5.2 → 2.0.0.rc

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 (105) hide show
  1. data/CHANGELOG.md +224 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +817 -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/config/initializers/simple_form.rb.tt +173 -0
  7. data/lib/simple_form.rb +109 -43
  8. data/lib/simple_form/action_view_extensions/builder.rb +158 -53
  9. data/lib/simple_form/action_view_extensions/form_helper.rb +29 -22
  10. data/lib/simple_form/components.rb +11 -1
  11. data/lib/simple_form/components/errors.rb +6 -24
  12. data/lib/simple_form/components/hints.rb +7 -21
  13. data/lib/simple_form/components/html5.rb +26 -0
  14. data/lib/simple_form/components/labels.rb +15 -13
  15. data/lib/simple_form/components/maxlength.rb +41 -0
  16. data/lib/simple_form/components/min_max.rb +49 -0
  17. data/lib/simple_form/components/pattern.rb +34 -0
  18. data/lib/simple_form/components/placeholders.rb +5 -17
  19. data/lib/simple_form/components/readonly.rb +22 -0
  20. data/lib/simple_form/core_ext/hash.rb +16 -0
  21. data/lib/simple_form/error_notification.rb +8 -1
  22. data/lib/simple_form/form_builder.rb +86 -22
  23. data/lib/simple_form/helpers.rb +7 -4
  24. data/lib/simple_form/helpers/autofocus.rb +11 -0
  25. data/lib/simple_form/helpers/disabled.rb +15 -0
  26. data/lib/simple_form/helpers/readonly.rb +15 -0
  27. data/lib/simple_form/helpers/required.rb +7 -10
  28. data/lib/simple_form/helpers/validators.rb +4 -4
  29. data/lib/simple_form/inputs.rb +17 -13
  30. data/lib/simple_form/inputs/base.rb +50 -81
  31. data/lib/simple_form/inputs/boolean_input.rb +43 -4
  32. data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
  33. data/lib/simple_form/inputs/collection_input.rb +27 -13
  34. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +69 -0
  35. data/lib/simple_form/inputs/collection_select_input.rb +14 -0
  36. data/lib/simple_form/inputs/date_time_input.rb +2 -2
  37. data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
  38. data/lib/simple_form/inputs/hidden_input.rb +3 -6
  39. data/lib/simple_form/inputs/numeric_input.rb +3 -51
  40. data/lib/simple_form/inputs/password_input.rb +1 -2
  41. data/lib/simple_form/inputs/priority_input.rb +2 -2
  42. data/lib/simple_form/inputs/range_input.rb +1 -3
  43. data/lib/simple_form/inputs/string_input.rb +6 -8
  44. data/lib/simple_form/inputs/text_input.rb +1 -2
  45. data/lib/simple_form/version.rb +1 -1
  46. data/lib/simple_form/wrappers.rb +8 -0
  47. data/lib/simple_form/wrappers/builder.rb +75 -0
  48. data/lib/simple_form/wrappers/many.rb +68 -0
  49. data/lib/simple_form/wrappers/root.rb +34 -0
  50. data/lib/simple_form/wrappers/single.rb +18 -0
  51. data/test/action_view_extensions/builder_test.rb +195 -100
  52. data/test/action_view_extensions/form_helper_test.rb +24 -2
  53. data/test/components/label_test.rb +20 -5
  54. data/test/form_builder/association_test.rb +167 -0
  55. data/test/form_builder/button_test.rb +28 -0
  56. data/test/{error_notification_test.rb → form_builder/error_notification_test.rb} +2 -1
  57. data/test/form_builder/error_test.rb +101 -0
  58. data/test/form_builder/general_test.rb +348 -0
  59. data/test/form_builder/hint_test.rb +115 -0
  60. data/test/form_builder/input_field_test.rb +51 -0
  61. data/test/form_builder/label_test.rb +51 -0
  62. data/test/form_builder/wrapper_test.rb +140 -0
  63. data/test/generators/simple_form_generator_test.rb +32 -0
  64. data/test/inputs/boolean_input_test.rb +91 -0
  65. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  66. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  67. data/test/inputs/collection_select_input_test.rb +241 -0
  68. data/test/inputs/datetime_input_test.rb +85 -0
  69. data/test/inputs/disabled_test.rb +38 -0
  70. data/test/inputs/discovery_test.rb +61 -0
  71. data/test/inputs/file_input_test.rb +16 -0
  72. data/test/inputs/general_test.rb +69 -0
  73. data/test/inputs/grouped_collection_select_input_test.rb +109 -0
  74. data/test/inputs/hidden_input_test.rb +30 -0
  75. data/test/inputs/numeric_input_test.rb +167 -0
  76. data/test/inputs/priority_input_test.rb +43 -0
  77. data/test/inputs/readonly_test.rb +61 -0
  78. data/test/inputs/required_test.rb +113 -0
  79. data/test/inputs/string_input_test.rb +140 -0
  80. data/test/inputs/text_input_test.rb +24 -0
  81. data/test/{discovery_inputs.rb → support/discovery_inputs.rb} +0 -0
  82. data/test/support/misc_helpers.rb +48 -6
  83. data/test/support/mock_controller.rb +2 -2
  84. data/test/support/models.rb +20 -5
  85. data/test/test_helper.rb +5 -8
  86. metadata +123 -98
  87. data/.gitignore +0 -3
  88. data/.gitmodules +0 -3
  89. data/.travis.yml +0 -15
  90. data/CHANGELOG.rdoc +0 -159
  91. data/Gemfile +0 -9
  92. data/README.rdoc +0 -466
  93. data/Rakefile +0 -27
  94. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +0 -93
  95. data/lib/simple_form/components/wrapper.rb +0 -38
  96. data/lib/simple_form/helpers/has_errors.rb +0 -15
  97. data/lib/simple_form/helpers/maxlength.rb +0 -24
  98. data/lib/simple_form/helpers/pattern.rb +0 -28
  99. data/simple_form.gemspec +0 -25
  100. data/test/components/error_test.rb +0 -56
  101. data/test/components/hint_test.rb +0 -74
  102. data/test/components/wrapper_test.rb +0 -63
  103. data/test/custom_components.rb +0 -7
  104. data/test/form_builder_test.rb +0 -930
  105. data/test/inputs_test.rb +0 -995
@@ -3,7 +3,15 @@ module SimpleForm
3
3
  class InstallGenerator < Rails::Generators::Base
4
4
  desc "Copy SimpleForm default files"
5
5
  source_root File.expand_path('../templates', __FILE__)
6
- class_option :template_engine
6
+ class_option :template_engine, :desc => 'Template engine to be invoked (erb, haml or slim).'
7
+ class_option :bootstrap, :type => :boolean, :desc => 'Add the Twitter Bootstrap wrappers to the SimpleForm initializer.'
8
+
9
+ def info_bootstrap
10
+ return if options.bootstrap?
11
+ puts "Simple Form 2 supports Twitter bootstrap. In case you want to " \
12
+ "generate bootstrap configuration, please re-run this " \
13
+ "generator passing --bootstrap as option."
14
+ end
7
15
 
8
16
  def copy_config
9
17
  directory 'config'
@@ -13,6 +21,12 @@ module SimpleForm
13
21
  engine = options[:template_engine]
14
22
  copy_file "_form.html.#{engine}", "lib/templates/#{engine}/scaffold/_form.html.#{engine}"
15
23
  end
24
+
25
+ def show_readme
26
+ if behavior == :invoke && options.bootstrap?
27
+ readme "README"
28
+ end
29
+ end
16
30
  end
17
31
  end
18
32
  end
@@ -0,0 +1,12 @@
1
+ ===============================================================================
2
+
3
+ Be sure to have a copy of the Bootstrap stylesheet available on your
4
+ application, you can get it on http://twitter.github.com/bootstrap.
5
+
6
+ Inside your views, use the 'simple_form_for' with one of the Bootstrap form
7
+ classes, '.form-horizontal', '.form-inline', '.form-search' or
8
+ '.form-vertical', as the following:
9
+
10
+ = simple_form_for(@user, :html => {:class => 'form-horizontal' }) do |form|
11
+
12
+ ===============================================================================
@@ -0,0 +1,173 @@
1
+ # Use this setup block to configure all options available in SimpleForm.
2
+ SimpleForm.setup do |config|
3
+ # Wrappers are used by the form builder to generate a
4
+ # complete input. You can remove any component from the
5
+ # wrapper, change the order or even add your own to the
6
+ # stack. The options given below are used to wrap the
7
+ # whole input.
8
+ config.wrappers :default, :class => :input,
9
+ :hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
10
+ ## Extensions enabled by default
11
+ # Any of these extensions can be disabled for a
12
+ # given input by passing: `f.input EXTENSION_NAME => false`.
13
+ # You can make any of these extensions optional by
14
+ # renaming `b.use` to `b.optional`.
15
+
16
+ # Determines whether to use HTML5 (:email, :url, ...)
17
+ # and required attributes
18
+ b.use :html5
19
+
20
+ # Calculates placeholders automatically from I18n
21
+ # You can also pass a string as f.input :placeholder => "Placeholder"
22
+ b.use :placeholder
23
+
24
+ ## Optional extensions
25
+ # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
26
+ # to the input. If so, they will retrieve the values from the model
27
+ # if any exists. If you want to enable the lookup for any of those
28
+ # extensions by default, you can change `b.optional` to `b.use`.
29
+
30
+ # Calculates maxlength from length validations for string inputs
31
+ b.optional :maxlength
32
+
33
+ # Calculates pattern from format validations for string inputs
34
+ b.optional :pattern
35
+
36
+ # Calculates min and max from length validations for numeric inputs
37
+ b.optional :min_max
38
+
39
+ # Calculates readonly automatically from readonly attributes
40
+ b.optional :readonly
41
+
42
+ ## Inputs
43
+ b.use :label_input
44
+ b.use :hint, :tag => :span, :class => :hint
45
+ b.use :error, :tag => :span, :class => :error
46
+ end
47
+ <% if options.bootstrap? %>
48
+ config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
49
+ b.use :placeholder
50
+ b.use :label, :class => 'control-label'
51
+ b.use :tag => 'div', :class => 'controls' do |ba|
52
+ ba.use :input
53
+ ba.use :error, :tag => 'span', :class => 'help-inline'
54
+ ba.use :hint, :tag => 'p', :class => 'help-block'
55
+ end
56
+ end
57
+
58
+ config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
59
+ b.use :placeholder
60
+ b.use :label, :class => 'control-label'
61
+ b.use :hint, :tag => 'span', :class => 'help-block'
62
+ b.use :tag => 'div', :class => 'controls' do |input|
63
+ input.use :tag => 'div', :class => 'input-prepend' do |prepend|
64
+ prepend.use :input
65
+ end
66
+ input.use :error, :tag => 'span', :class => 'help-inline'
67
+ end
68
+ end
69
+
70
+ config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
71
+ b.use :placeholder
72
+ b.use :label, :class => 'control-label'
73
+ b.use :hint, :tag => 'span', :class => 'help-block'
74
+ b.use :tag => 'div', :class => 'controls' do |input|
75
+ input.use :tag => 'div', :class => 'input-append' do |append|
76
+ append.use :input
77
+ end
78
+ input.use :error, :tag => 'span', :class => 'help-inline'
79
+ end
80
+ end
81
+
82
+ # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
83
+ # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
84
+ # to learn about the different styles for forms and inputs,
85
+ # buttons and other elements.
86
+ config.default_wrapper = :bootstrap
87
+ <% else %>
88
+ # The default wrapper to be used by the FormBuilder.
89
+ config.default_wrapper = :default
90
+ <% end %>
91
+ # Define the way to render check boxes / radio buttons with labels.
92
+ # Defaults to :nested for bootstrap config.
93
+ # :inline => input + label
94
+ # :nested => label > input
95
+ config.boolean_style = :nested
96
+
97
+ # Default class for buttons
98
+ config.button_class = 'btn'
99
+
100
+ # Method used to tidy up errors.
101
+ # config.error_method = :first
102
+
103
+ # Default tag used for error notification helper.
104
+ # config.error_notification_tag = :p
105
+
106
+ # CSS class to add for error notification helper.
107
+ # config.error_notification_class = :error_notification
108
+
109
+ # ID to add for error notification helper.
110
+ # config.error_notification_id = nil
111
+
112
+ # Series of attempts to detect a default label method for collection.
113
+ # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
114
+
115
+ # Series of attempts to detect a default value method for collection.
116
+ # config.collection_value_methods = [ :id, :to_s ]
117
+
118
+ # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
119
+ # config.collection_wrapper_tag = nil
120
+
121
+ # You can define the class to use on all collection wrappers. Defaulting to none.
122
+ # config.collection_wrapper_class = nil
123
+
124
+ # You can wrap each item in a collection of radio/check boxes with a tag,
125
+ # defaulting to :span. Please note that when using :boolean_style = :nested,
126
+ # SimpleForm will force this option to be a label.
127
+ # config.item_wrapper_tag = :span
128
+
129
+ # You can define a class to use in all item wrappers. Defaulting to none.
130
+ # config.item_wrapper_class = nil
131
+
132
+ # How the label text should be generated altogether with the required text.
133
+ # config.label_text = lambda { |label, required| "#{required} #{label}" }
134
+
135
+ # You can define the class to use on all labels. Default is nil.
136
+ # config.label_class = nil
137
+
138
+ # You can define the class to use on all forms. Default is simple_form.
139
+ # config.form_class = :simple_form
140
+
141
+ # Whether attributes are required by default (or not). Default is true.
142
+ # config.required_by_default = true
143
+
144
+ # Tell browsers whether to use default HTML5 validations (novalidate option).
145
+ # Default is enabled.
146
+ config.browser_validations = false
147
+
148
+ # Collection of methods to detect if a file type was given.
149
+ # config.file_methods = [ :mounted_as, :file?, :public_filename ]
150
+
151
+ # Custom mappings for input types. This should be a hash containing a regexp
152
+ # to match as key, and the input type that will be used when the field name
153
+ # matches the regexp as value.
154
+ # config.input_mappings = { /count/ => :integer }
155
+
156
+ # Default priority for time_zone inputs.
157
+ # config.time_zone_priority = nil
158
+
159
+ # Default priority for country inputs.
160
+ # config.country_priority = nil
161
+
162
+ # Default size for text inputs.
163
+ # config.default_input_size = 50
164
+
165
+ # When false, do not use translations for labels.
166
+ # config.translate_labels = true
167
+
168
+ # Automatically discover new inputs in Rails' autoload path.
169
+ # config.inputs_discovery = true
170
+
171
+ # Cache simple form inputs discovery
172
+ # config.cache_discovery = !Rails.env.development?
173
+ end
data/lib/simple_form.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'action_view'
2
2
  require 'simple_form/action_view_extensions/form_helper'
3
3
  require 'simple_form/action_view_extensions/builder'
4
+ require 'active_support/core_ext/hash/slice'
5
+ require 'active_support/core_ext/hash/except'
6
+ require 'active_support/core_ext/hash/reverse_merge'
4
7
 
5
8
  module SimpleForm
6
9
  autoload :Components, 'simple_form/components'
@@ -10,22 +13,9 @@ module SimpleForm
10
13
  autoload :I18nCache, 'simple_form/i18n_cache'
11
14
  autoload :Inputs, 'simple_form/inputs'
12
15
  autoload :MapType, 'simple_form/map_type'
16
+ autoload :Wrappers, 'simple_form/wrappers'
13
17
 
14
- # Default tag used on hints.
15
- mattr_accessor :hint_tag
16
- @@hint_tag = :span
17
-
18
- # CSS class to add to all hint tags.
19
- mattr_accessor :hint_class
20
- @@hint_class = :hint
21
-
22
- # Default tag used on errors.
23
- mattr_accessor :error_tag
24
- @@error_tag = :span
25
-
26
- # CSS class to add to all error tags.
27
- mattr_accessor :error_class
28
- @@error_class = :error
18
+ ## CONFIGURATION OPTIONS
29
19
 
30
20
  # Method used to tidy up errors.
31
21
  mattr_accessor :error_method
@@ -43,10 +33,6 @@ module SimpleForm
43
33
  mattr_accessor :error_notification_id
44
34
  @@error_notification_id = nil
45
35
 
46
- # Components used by the form builder.
47
- mattr_accessor :components
48
- @@components = [ :placeholder, :label_input, :hint, :error ]
49
-
50
36
  # Series of attemps to detect a default label method for collection.
51
37
  mattr_accessor :collection_label_methods
52
38
  @@collection_label_methods = [ :to_label, :name, :title, :to_s ]
@@ -59,21 +45,19 @@ module SimpleForm
59
45
  mattr_accessor :collection_wrapper_tag
60
46
  @@collection_wrapper_tag = nil
61
47
 
62
- # You can wrap each item in a collection of radio/check boxes with a tag, defaulting to none.
48
+ # You can define the class to use on all collection wrappers, defaulting to none.
49
+ mattr_accessor :collection_wrapper_class
50
+ @@collection_wrapper_class = nil
51
+
52
+ # You can wrap each item in a collection of radio/check boxes with a tag,
53
+ # defaulting to none. Please note that when using :boolean_style = :nested,
54
+ # SimpleForm will force this option to be a :label.
63
55
  mattr_accessor :item_wrapper_tag
64
56
  @@item_wrapper_tag = :span
65
57
 
66
- # You can wrap all inputs in a pre-defined tag. Default is a div.
67
- mattr_accessor :wrapper_tag
68
- @@wrapper_tag = :div
69
-
70
- # You can define the class to use on all wrappers. Default is input.
71
- mattr_accessor :wrapper_class
72
- @@wrapper_class = :input
73
-
74
- # You can define the class to add to the wrapper when the field has errors. Default is field_with_errors.
75
- mattr_accessor :wrapper_error_class
76
- @@wrapper_error_class = :field_with_errors
58
+ # You can define the class to use on all item wrappers, defaulting to none.
59
+ mattr_accessor :item_wrapper_class
60
+ @@item_wrapper_class = nil
77
61
 
78
62
  # How the label text should be generated altogether with the required text.
79
63
  mattr_accessor :label_text
@@ -83,6 +67,12 @@ module SimpleForm
83
67
  mattr_accessor :label_class
84
68
  @@label_class = nil
85
69
 
70
+ # Define the way to render check boxes / radio buttons with labels.
71
+ # :inline => input + label (default)
72
+ # :nested => label > input
73
+ mattr_accessor :boolean_style
74
+ @@boolean_style = :inline
75
+
86
76
  # You can define the class to use on all forms. Default is simple_form.
87
77
  mattr_accessor :form_class
88
78
  @@form_class = :simple_form
@@ -95,13 +85,6 @@ module SimpleForm
95
85
  mattr_accessor :browser_validations
96
86
  @@browser_validations = true
97
87
 
98
- # Determines whether HTML5 types (:email, :url, :search, :tel) and attributes
99
- # (e.g. required) are used or not. True by default.
100
- # Having this on in non-HTML5 compliant sites can cause odd behavior in
101
- # HTML5-aware browsers such as Chrome.
102
- mattr_accessor :html5
103
- @@html5 = true
104
-
105
88
  # Collection of methods to detect if a file type was given.
106
89
  mattr_accessor :file_methods
107
90
  @@file_methods = [ :mounted_as, :file?, :public_filename ]
@@ -124,10 +107,10 @@ module SimpleForm
124
107
  mattr_accessor :default_input_size
125
108
  @@default_input_size = 50
126
109
 
127
- # When off, do not use translations in hint, labels and placeholders.
128
- # It is a small performance improvement if you are not using such features.
129
- mattr_accessor :translate
130
- @@translate = true
110
+ # When off, do not use translations in labels. Disabling translation in
111
+ # hints and placeholders can be done manually in the wrapper API.
112
+ mattr_accessor :translate_labels
113
+ @@translate_labels = true
131
114
 
132
115
  # Automatically discover new inputs in Rails' autoload path.
133
116
  mattr_accessor :inputs_discovery
@@ -135,11 +118,94 @@ module SimpleForm
135
118
 
136
119
  # Cache simple form inputs discovery
137
120
  mattr_accessor :cache_discovery
138
- @@cache_discovery = !Rails.env.development?
121
+ @@cache_discovery = defined?(Rails) && !Rails.env.development?
122
+
123
+ # Adds a class to each generated button, mostly for compatiblity
124
+ mattr_accessor :button_class
125
+ @@button_class = 'button'
126
+
127
+ ## WRAPPER CONFIGURATION
128
+ # The default wrapper to be used by the FormBuilder.
129
+ mattr_accessor :default_wrapper
130
+ @@default_wrapper = :default
131
+ @@wrappers = {}
132
+
133
+ # Retrieves a given wrapper
134
+ def self.wrapper(name)
135
+ @@wrappers[name.to_sym] or raise WrapperNotFound, "Couldn't find wrapper with name #{name}"
136
+ end
137
+
138
+ # Raised when fails to find a given wrapper name
139
+ class WrapperNotFound < StandardError
140
+ end
141
+
142
+ # Define a new wrapper using SimpleForm::Wrappers::Builder
143
+ # and store it in the given name.
144
+ def self.wrappers(*args, &block)
145
+ if block_given?
146
+ options = args.extract_options!
147
+ name = args.first || :default
148
+ @@wrappers[name.to_sym] = build(options, &block)
149
+ else
150
+ @@wrappers
151
+ end
152
+ end
153
+
154
+ # Builds a new wrapper using SimpleForm::Wrappers::Builder.
155
+ def self.build(options={})
156
+ options[:tag] = :div if options[:tag].nil?
157
+ builder = SimpleForm::Wrappers::Builder.new(options)
158
+ yield builder
159
+ SimpleForm::Wrappers::Root.new(builder.to_a, options)
160
+ end
161
+
162
+ wrappers :class => :input, :hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
163
+ b.use :html5
164
+
165
+ b.use :min_max
166
+ b.use :maxlength
167
+ b.use :placeholder
168
+ b.optional :pattern
169
+ b.optional :readonly
170
+
171
+ b.use :label_input
172
+ b.use :hint, :tag => :span, :class => :hint
173
+ b.use :error, :tag => :span, :class => :error
174
+ end
175
+
176
+ ## SETUP
177
+
178
+ DEPRECATED = %w(hint_tag hint_class error_tag error_class wrapper_tag wrapper_class wrapper_error_class components html5)
179
+ @@deprecated = []
180
+
181
+ DEPRECATED.each do |method|
182
+ class_eval "def self.#{method}=(*); @@deprecated << :#{method}=; end"
183
+ class_eval "def self.#{method}; deprecation_warn 'SimpleForm.#{method} is deprecated and has no effect'; end"
184
+ end
185
+
186
+ def self.translate=(value)
187
+ deprecation_warn "SimpleForm.translate= is disabled in favor of translate_labels="
188
+ self.translate_labels = value
189
+ end
190
+
191
+ def self.translate
192
+ deprecation_warn "SimpleForm.translate is disabled in favor of translate_labels"
193
+ self.translate_labels
194
+ end
195
+
196
+ def self.deprecation_warn(message)
197
+ ActiveSupport::Deprecation.warn "[SIMPLE_FORM] #{message}", caller
198
+ end
139
199
 
140
200
  # Default way to setup SimpleForm. Run rails generate simple_form:install
141
201
  # to create a fresh initializer with all configuration values.
142
202
  def self.setup
143
203
  yield self
204
+
205
+ unless @@deprecated.empty?
206
+ raise "[SIMPLE FORM] Your simple form initializer file is using the following methods: #{@@deprecated.to_sentence}. " <<
207
+ "Those methods are part of the outdated configuration API. Updating to the new API is easy and fast. " <<
208
+ "Check for more info here: https://github.com/plataformatec/simple_form/wiki/Upgrading-to-Simple-Form-2.0"
209
+ end
144
210
  end
145
211
  end
@@ -14,13 +14,24 @@ module SimpleForm
14
14
  # == Examples
15
15
  #
16
16
  # form_for @user do |f|
17
- # f.collection_radio :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
17
+ # f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
18
18
  # end
19
19
  #
20
20
  # <input id="user_options_true" name="user[options]" type="radio" value="true" />
21
- # <label class="collection_radio" for="user_options_true">Yes</label>
21
+ # <label class="collection_radio_buttons" for="user_options_true">Yes</label>
22
22
  # <input id="user_options_false" name="user[options]" type="radio" value="false" />
23
- # <label class="collection_radio" for="user_options_false">No</label>
23
+ # <label class="collection_radio_buttons" for="user_options_false">No</label>
24
+ #
25
+ # It is also possible to give a block that should generate the radio +
26
+ # label. To wrap the radio with the label, for instance:
27
+ #
28
+ # form_for @user do |f|
29
+ # f.collection_radio_buttons(
30
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
31
+ # ) do |label_for, text, value, html_options|
32
+ # f.label(label_for) { f.radio_button(attribute, value, html_options) + text }
33
+ # end
34
+ # end
24
35
  #
25
36
  # == Options
26
37
  #
@@ -31,17 +42,36 @@ module SimpleForm
31
42
  # * disabled => the value or values that should be disabled. Accepts a single
32
43
  # item or an array of items.
33
44
  #
34
- # * collection_wrapper_tag => the tag to wrap the entire collection.
45
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
46
+ #
47
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
35
48
  #
36
- # * item_wrapper_tag => the tag to wrap each item in the collection.
49
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
37
50
  #
38
- def collection_radio(attribute, collection, value_method, text_method, options={}, html_options={})
39
- render_collection(
40
- attribute, collection, value_method, text_method, options, html_options
51
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
52
+ #
53
+ # * a block => to generate the label + radio or any other component.
54
+ #
55
+ def collection_radio_buttons(attribute, collection, value_method, text_method, options={}, html_options={})
56
+ rendered_collection = render_collection(
57
+ collection, value_method, text_method, options, html_options
41
58
  ) do |value, text, default_html_options|
42
- radio_button(attribute, value, default_html_options) +
43
- label(sanitize_attribute_name(attribute, value), text, :class => "collection_radio")
59
+ if block_given?
60
+ yield sanitize_attribute_name(attribute, value), text, value, default_html_options
61
+ else
62
+ radio_button(attribute, value, default_html_options) +
63
+ label(sanitize_attribute_name(attribute, value), text, :class => "collection_radio_buttons")
64
+ end
44
65
  end
66
+
67
+ wrap_rendered_collection(rendered_collection, options)
68
+ end
69
+
70
+ # deprecated
71
+ def collection_radio(*args, &block)
72
+ SimpleForm.deprecation_warn "The `collection_radio` helper is deprecated, " \
73
+ "please use `collection_radio_buttons` instead."
74
+ collection_radio_buttons(*args, &block)
45
75
  end
46
76
 
47
77
  # Creates a collection of check boxes for each item in the collection,
@@ -63,29 +93,56 @@ module SimpleForm
63
93
  # <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
64
94
  # <label class="collection_check_boxes" for="user_options_false">No</label>
65
95
  #
96
+ # It is also possible to give a block that should generate the check box +
97
+ # label. To wrap the check box with the label, for instance:
98
+ #
99
+ # form_for @user do |f|
100
+ # f.collection_check_boxes(
101
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
102
+ # ) do |label_for, text, value, html_options|
103
+ # f.label(label_for) { f.check_box(attribute, html_options, value, '') + text }
104
+ # end
105
+ # end
106
+ #
66
107
  # == Options
67
108
  #
68
109
  # Collection check box accepts some extra options:
69
110
  #
70
111
  # * checked => the value or values that should be checked initially. Accepts
71
- # a single item or an array of items.
112
+ # a single item or an array of items. It overrides existing associations.
72
113
  #
73
114
  # * disabled => the value or values that should be disabled. Accepts a single
74
115
  # item or an array of items.
75
116
  #
76
- # * collection_wrapper_tag => the tag to wrap the entire collection.
117
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
77
118
  #
78
- # * item_wrapper_tag => the tag to wrap each item in the collection.
119
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
120
+ #
121
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
122
+ #
123
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
124
+ #
125
+ # * a block => to generate the label + check box or any other component.
79
126
  #
80
127
  def collection_check_boxes(attribute, collection, value_method, text_method, options={}, html_options={})
81
- render_collection(
82
- attribute, collection, value_method, text_method, options, html_options
128
+ rendered_collection = render_collection(
129
+ collection, value_method, text_method, options, html_options
83
130
  ) do |value, text, default_html_options|
84
131
  default_html_options[:multiple] = true
85
132
 
86
- check_box(attribute, default_html_options, value, '') +
87
- label(sanitize_attribute_name(attribute, value), text, :class => "collection_check_boxes")
133
+ if block_given?
134
+ yield sanitize_attribute_name(attribute, value), text, value, default_html_options
135
+ else
136
+ check_box(attribute, default_html_options, value, nil) +
137
+ label(sanitize_attribute_name(attribute, value), text, :class => "collection_check_boxes")
138
+ end
88
139
  end
140
+
141
+ # Append a hidden field to make sure something will be sent back to the
142
+ # server if all checkboxes are unchecked.
143
+ hidden = template.hidden_field_tag("#{object_name}[#{attribute}][]", "", :id => nil)
144
+
145
+ wrap_rendered_collection(rendered_collection + hidden, options)
89
146
  end
90
147
 
91
148
  # Wrapper for using simple form inside a default rails form.
@@ -99,6 +156,8 @@ module SimpleForm
99
156
  # end
100
157
  def simple_fields_for(*args, &block)
101
158
  options = args.extract_options!
159
+ options[:wrapper] ||= self.options[:wrapper]
160
+
102
161
  if self.class < ActionView::Helpers::FormBuilder
103
162
  options[:builder] ||= self.class
104
163
  else
@@ -107,7 +166,7 @@ module SimpleForm
107
166
  fields_for(*(args << options), &block)
108
167
  end
109
168
 
110
- private
169
+ private
111
170
 
112
171
  # Generate default options for collection helpers, such as :checked and
113
172
  # :disabled.
@@ -117,75 +176,121 @@ module SimpleForm
117
176
  [:checked, :selected, :disabled].each do |option|
118
177
  next unless options[option]
119
178
 
179
+
120
180
  accept = if options[option].respond_to?(:call)
121
181
  options[option].call(item)
122
182
  else
123
183
  Array(options[option]).include?(value)
124
184
  end
125
185
 
126
- html_options[option] = true if accept
186
+ if accept
187
+ html_options[option] = true
188
+ elsif option == :checked
189
+ html_options[option] = false
190
+ end
127
191
  end
128
192
 
129
193
  html_options
130
194
  end
131
195
 
132
- def sanitize_attribute_name(attribute, value)
196
+ def sanitize_attribute_name(attribute, value) #:nodoc:
133
197
  "#{attribute}_#{value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase}"
134
198
  end
135
199
 
136
- def render_collection(attribute, collection, value_method, text_method, options={}, html_options={}) #:nodoc:
137
- collection_wrapper_tag = options.has_key?(:collection_wrapper_tag) ? options[:collection_wrapper_tag] : SimpleForm.collection_wrapper_tag
138
- item_wrapper_tag = options.has_key?(:item_wrapper_tag) ? options[:item_wrapper_tag] : SimpleForm.item_wrapper_tag
200
+ def render_collection(collection, value_method, text_method, options={}, html_options={}) #:nodoc:
201
+ item_wrapper_tag = options.fetch(:item_wrapper_tag, :span)
202
+ item_wrapper_class = options[:item_wrapper_class]
139
203
 
140
- rendered_collection = collection.map do |item|
204
+ collection.map do |item|
141
205
  value = value_for_collection(item, value_method)
142
206
  text = value_for_collection(item, text_method)
143
207
  default_html_options = default_html_options_for_collection(item, value, options, html_options)
144
208
 
145
209
  rendered_item = yield value, text, default_html_options
146
210
 
147
- item_wrapper_tag ? @template.content_tag(item_wrapper_tag, rendered_item) : rendered_item
211
+ item_wrapper_tag ? @template.content_tag(item_wrapper_tag, rendered_item, :class => item_wrapper_class) : rendered_item
148
212
  end.join.html_safe
149
-
150
- collection_wrapper_tag ? @template.content_tag(collection_wrapper_tag, rendered_collection) : rendered_collection
151
213
  end
152
214
 
153
215
  def value_for_collection(item, value) #:nodoc:
154
216
  value.respond_to?(:call) ? value.call(item) : item.send(value)
155
217
  end
218
+
219
+ def wrap_rendered_collection(collection, options)
220
+ wrapper_tag = options[:collection_wrapper_tag]
221
+
222
+ if wrapper_tag
223
+ wrapper_class = options[:collection_wrapper_class]
224
+ @template.content_tag(wrapper_tag, collection, :class => wrapper_class)
225
+ else
226
+ collection
227
+ end
228
+ end
156
229
  end
157
230
  end
158
231
  end
159
232
 
160
- class ActionView::Helpers::FormBuilder
161
- include SimpleForm::ActionViewExtensions::Builder
162
-
163
- # Override default Rails collection_select helper to handle lambdas/procs in
164
- # text and value methods, so it works the same way as collection_radio and
165
- # collection_check_boxes in SimpleForm. If none of text/value methods is a
166
- # callable object, then it just delegates back to original collection select.
167
- #
168
- alias :original_collection_select :collection_select
169
- def collection_select(attribute, collection, value_method, text_method, options={}, html_options={})
170
- if value_method.respond_to?(:call) || text_method.respond_to?(:call)
171
- collection = collection.map do |item|
172
- value = value_for_collection(item, value_method)
173
- text = value_for_collection(item, text_method)
174
-
175
- default_html_options = default_html_options_for_collection(item, value, options, html_options)
176
- disabled = value if default_html_options[:disabled]
177
- selected = value if default_html_options[:selected]
178
-
179
- [value, text, selected, disabled]
180
- end
233
+ module ActionView::Helpers
234
+ class FormBuilder
235
+ include SimpleForm::ActionViewExtensions::Builder
236
+
237
+ # Override default Rails collection_select helper to handle lambdas/procs in
238
+ # text and value methods, so it works the same way as collection_radio_buttons
239
+ # and collection_check_boxes in SimpleForm. If none of text/value methods is a
240
+ # callable object, then it just delegates back to original collection select.
241
+ #
242
+ alias :original_collection_select :collection_select
243
+ def collection_select(attribute, collection, value_method, text_method, options={}, html_options={})
244
+ if value_method.respond_to?(:call) || text_method.respond_to?(:call)
245
+ collection = collection.map do |item|
246
+ value = value_for_collection(item, value_method)
247
+ text = value_for_collection(item, text_method)
248
+
249
+ default_html_options = default_html_options_for_collection(item, value, options, html_options)
250
+ disabled = value if default_html_options[:disabled]
251
+ selected = value if default_html_options[:selected]
252
+
253
+ [value, text, selected, disabled]
254
+ end
181
255
 
182
- [:disabled, :selected].each do |option|
183
- option_value = collection.map(&:pop).compact
184
- options[option] = option_value if option_value.present?
256
+ [:disabled, :selected].each do |option|
257
+ option_value = collection.map(&:pop).compact
258
+ options[option] = option_value if option_value.present?
259
+ end
260
+ value_method, text_method = :first, :last
185
261
  end
186
- value_method, text_method = :first, :last
262
+
263
+ original_collection_select(attribute, collection, value_method, text_method, options, html_options)
187
264
  end
265
+ end
188
266
 
189
- original_collection_select(attribute, collection, value_method, text_method, options, html_options)
267
+ # Backport Rails fix to checkbox tag element, which does not generate the
268
+ # hidden input when given nil as unchecked value. This is to make SimpleForm
269
+ # collection check boxes helper to work fine with nested boolean style, when
270
+ # they are wrapped in labels. Without that, clicking in the label would
271
+ # actually change the hidden input, instead of the checkbox.
272
+ # FIXME: remove when support only Rails >= 3.2.2.
273
+ class InstanceTag
274
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
275
+ options = options.stringify_keys
276
+ options["type"] = "checkbox"
277
+ options["value"] = checked_value
278
+ if options.has_key?("checked")
279
+ cv = options.delete "checked"
280
+ checked = cv == true || cv == "checked"
281
+ else
282
+ checked = self.class.check_box_checked?(value(object), checked_value)
283
+ end
284
+ options["checked"] = "checked" if checked
285
+ if options["multiple"]
286
+ add_default_name_and_id_for_value(checked_value, options)
287
+ options.delete("multiple")
288
+ else
289
+ add_default_name_and_id(options)
290
+ end
291
+ hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
292
+ checkbox = tag("input", options)
293
+ (hidden + checkbox).html_safe
294
+ end
190
295
  end
191
296
  end