simple_form 1.5.2 → 2.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of simple_form might be problematic. Click here for more details.

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