formtastic 2.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/test.yml +61 -0
  4. data/.gitignore +4 -2
  5. data/CHANGELOG.md +52 -0
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +105 -0
  8. data/MIT-LICENSE +1 -1
  9. data/{README.textile → README.md} +204 -219
  10. data/RELEASE_PROCESS +3 -1
  11. data/Rakefile +27 -29
  12. data/app/assets/stylesheets/formtastic.css +3 -2
  13. data/bin/appraisal +8 -0
  14. data/formtastic.gemspec +11 -14
  15. data/gemfiles/rails_5.2/Gemfile +5 -0
  16. data/gemfiles/rails_6.0/Gemfile +5 -0
  17. data/gemfiles/rails_6.1/Gemfile +5 -0
  18. data/gemfiles/rails_edge/Gemfile +13 -0
  19. data/lib/formtastic/action_class_finder.rb +18 -0
  20. data/lib/formtastic/actions/button_action.rb +55 -60
  21. data/lib/formtastic/actions/input_action.rb +59 -57
  22. data/lib/formtastic/actions/link_action.rb +68 -67
  23. data/lib/formtastic/actions.rb +6 -3
  24. data/lib/formtastic/deprecation.rb +5 -0
  25. data/lib/formtastic/engine.rb +3 -1
  26. data/lib/formtastic/form_builder.rb +35 -16
  27. data/lib/formtastic/helpers/action_helper.rb +34 -28
  28. data/lib/formtastic/helpers/enum.rb +13 -0
  29. data/lib/formtastic/helpers/errors_helper.rb +2 -2
  30. data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -12
  31. data/lib/formtastic/helpers/form_helper.rb +19 -16
  32. data/lib/formtastic/helpers/input_helper.rb +69 -97
  33. data/lib/formtastic/helpers/inputs_helper.rb +35 -25
  34. data/lib/formtastic/helpers/reflection.rb +4 -4
  35. data/lib/formtastic/helpers.rb +1 -2
  36. data/lib/formtastic/html_attributes.rb +12 -1
  37. data/lib/formtastic/i18n.rb +1 -1
  38. data/lib/formtastic/input_class_finder.rb +18 -0
  39. data/lib/formtastic/inputs/base/choices.rb +2 -2
  40. data/lib/formtastic/inputs/base/collections.rb +46 -14
  41. data/lib/formtastic/inputs/base/database.rb +7 -2
  42. data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
  43. data/lib/formtastic/inputs/base/errors.rb +7 -7
  44. data/lib/formtastic/inputs/base/hints.rb +2 -2
  45. data/lib/formtastic/inputs/base/html.rb +10 -9
  46. data/lib/formtastic/inputs/base/labelling.rb +5 -8
  47. data/lib/formtastic/inputs/base/naming.rb +4 -4
  48. data/lib/formtastic/inputs/base/numeric.rb +1 -1
  49. data/lib/formtastic/inputs/base/options.rb +3 -4
  50. data/lib/formtastic/inputs/base/stringish.rb +10 -2
  51. data/lib/formtastic/inputs/base/timeish.rb +34 -22
  52. data/lib/formtastic/inputs/base/validations.rb +41 -13
  53. data/lib/formtastic/inputs/base/wrapping.rb +29 -26
  54. data/lib/formtastic/inputs/base.rb +22 -15
  55. data/lib/formtastic/inputs/boolean_input.rb +26 -12
  56. data/lib/formtastic/inputs/check_boxes_input.rb +39 -31
  57. data/lib/formtastic/inputs/color_input.rb +41 -0
  58. data/lib/formtastic/inputs/country_input.rb +24 -5
  59. data/lib/formtastic/inputs/datalist_input.rb +41 -0
  60. data/lib/formtastic/inputs/date_picker_input.rb +93 -0
  61. data/lib/formtastic/inputs/{date_input.rb → date_select_input.rb} +1 -1
  62. data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
  63. data/lib/formtastic/inputs/{datetime_input.rb → datetime_select_input.rb} +1 -1
  64. data/lib/formtastic/inputs/file_input.rb +2 -2
  65. data/lib/formtastic/inputs/hidden_input.rb +2 -6
  66. data/lib/formtastic/inputs/radio_input.rb +28 -22
  67. data/lib/formtastic/inputs/select_input.rb +36 -39
  68. data/lib/formtastic/inputs/time_picker_input.rb +99 -0
  69. data/lib/formtastic/inputs/{time_input.rb → time_select_input.rb} +6 -2
  70. data/lib/formtastic/inputs/time_zone_input.rb +16 -6
  71. data/lib/formtastic/inputs.rb +32 -21
  72. data/lib/formtastic/localized_string.rb +1 -1
  73. data/lib/formtastic/localizer.rb +24 -24
  74. data/lib/formtastic/namespaced_class_finder.rb +99 -0
  75. data/lib/formtastic/version.rb +1 -1
  76. data/lib/formtastic.rb +20 -10
  77. data/lib/generators/formtastic/form/form_generator.rb +10 -4
  78. data/lib/generators/formtastic/input/input_generator.rb +46 -0
  79. data/lib/generators/formtastic/install/install_generator.rb +5 -19
  80. data/lib/generators/templates/_form.html.slim +2 -2
  81. data/lib/generators/templates/formtastic.rb +46 -25
  82. data/lib/generators/templates/input.rb +19 -0
  83. data/sample/basic_inputs.html +23 -3
  84. data/script/integration-template.rb +74 -0
  85. data/script/integration.sh +19 -0
  86. data/spec/action_class_finder_spec.rb +12 -0
  87. data/spec/actions/button_action_spec.rb +8 -8
  88. data/spec/actions/generic_action_spec.rb +92 -56
  89. data/spec/actions/input_action_spec.rb +7 -7
  90. data/spec/actions/link_action_spec.rb +10 -10
  91. data/spec/builder/custom_builder_spec.rb +36 -20
  92. data/spec/builder/error_proc_spec.rb +4 -4
  93. data/spec/builder/semantic_fields_for_spec.rb +28 -29
  94. data/spec/fast_spec_helper.rb +12 -0
  95. data/spec/generators/formtastic/form/form_generator_spec.rb +45 -32
  96. data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
  97. data/spec/generators/formtastic/install/install_generator_spec.rb +9 -9
  98. data/spec/helpers/action_helper_spec.rb +75 -103
  99. data/spec/helpers/actions_helper_spec.rb +17 -17
  100. data/spec/helpers/form_helper_spec.rb +84 -33
  101. data/spec/helpers/input_helper_spec.rb +333 -285
  102. data/spec/helpers/inputs_helper_spec.rb +167 -121
  103. data/spec/helpers/reflection_helper_spec.rb +3 -3
  104. data/spec/helpers/semantic_errors_helper_spec.rb +23 -23
  105. data/spec/i18n_spec.rb +26 -26
  106. data/spec/input_class_finder_spec.rb +10 -0
  107. data/spec/inputs/base/collections_spec.rb +76 -0
  108. data/spec/inputs/base/validations_spec.rb +480 -0
  109. data/spec/inputs/boolean_input_spec.rb +100 -65
  110. data/spec/inputs/check_boxes_input_spec.rb +200 -101
  111. data/spec/inputs/color_input_spec.rb +85 -0
  112. data/spec/inputs/country_input_spec.rb +20 -20
  113. data/spec/inputs/custom_input_spec.rb +3 -4
  114. data/spec/inputs/datalist_input_spec.rb +61 -0
  115. data/spec/inputs/date_picker_input_spec.rb +449 -0
  116. data/spec/inputs/date_select_input_spec.rb +249 -0
  117. data/spec/inputs/datetime_picker_input_spec.rb +490 -0
  118. data/spec/inputs/datetime_select_input_spec.rb +209 -0
  119. data/spec/inputs/email_input_spec.rb +5 -5
  120. data/spec/inputs/file_input_spec.rb +6 -6
  121. data/spec/inputs/hidden_input_spec.rb +22 -35
  122. data/spec/inputs/include_blank_spec.rb +11 -11
  123. data/spec/inputs/label_spec.rb +62 -25
  124. data/spec/inputs/number_input_spec.rb +112 -112
  125. data/spec/inputs/password_input_spec.rb +5 -5
  126. data/spec/inputs/phone_input_spec.rb +5 -5
  127. data/spec/inputs/placeholder_spec.rb +6 -6
  128. data/spec/inputs/radio_input_spec.rb +99 -55
  129. data/spec/inputs/range_input_spec.rb +66 -66
  130. data/spec/inputs/readonly_spec.rb +50 -0
  131. data/spec/inputs/search_input_spec.rb +5 -5
  132. data/spec/inputs/select_input_spec.rb +170 -170
  133. data/spec/inputs/string_input_spec.rb +68 -16
  134. data/spec/inputs/text_input_spec.rb +16 -16
  135. data/spec/inputs/time_picker_input_spec.rb +455 -0
  136. data/spec/inputs/time_select_input_spec.rb +261 -0
  137. data/spec/inputs/time_zone_input_spec.rb +54 -28
  138. data/spec/inputs/url_input_spec.rb +5 -5
  139. data/spec/inputs/with_options_spec.rb +7 -7
  140. data/spec/localizer_spec.rb +39 -17
  141. data/spec/namespaced_class_finder_spec.rb +79 -0
  142. data/spec/schema.rb +21 -0
  143. data/spec/spec_helper.rb +254 -221
  144. data/spec/support/custom_macros.rb +128 -95
  145. data/spec/support/shared_examples.rb +12 -0
  146. data/spec/support/specialized_class_finder_shared_example.rb +27 -0
  147. data/spec/support/test_environment.rb +26 -10
  148. metadata +177 -238
  149. data/.travis.yml +0 -8
  150. data/Appraisals +0 -11
  151. data/CHANGELOG +0 -371
  152. data/gemfiles/rails-3.0.gemfile +0 -7
  153. data/gemfiles/rails-3.1.gemfile +0 -7
  154. data/gemfiles/rails-3.2.gemfile +0 -7
  155. data/lib/formtastic/helpers/buttons_helper.rb +0 -310
  156. data/lib/formtastic/inputs/base/grouped_collections.rb +0 -77
  157. data/lib/formtastic/util.rb +0 -25
  158. data/lib/tasks/verify_rcov.rb +0 -44
  159. data/spec/helpers/buttons_helper_spec.rb +0 -166
  160. data/spec/helpers/commit_button_helper_spec.rb +0 -530
  161. data/spec/inputs/date_input_spec.rb +0 -227
  162. data/spec/inputs/datetime_input_spec.rb +0 -185
  163. data/spec/inputs/time_input_spec.rb +0 -267
  164. data/spec/support/deferred_garbage_collection.rb +0 -21
@@ -2,15 +2,14 @@ module Formtastic
2
2
  module Inputs
3
3
  module Base
4
4
  module Options
5
-
5
+
6
6
  def input_options
7
7
  options.except(*formtastic_options)
8
8
  end
9
-
9
+
10
10
  def formtastic_options
11
- [:priority_countries, :priority_zones, :member_label, :member_value, :collection, :required, :label, :as, :hint, :input_html, :label_html, :value_as_class, :find_options, :class]
11
+ [:priority_countries, :priority_zones, :member_label, :member_value, :collection, :required, :label, :as, :hint, :input_html, :value_as_class, :class]
12
12
  end
13
-
14
13
  end
15
14
  end
16
15
  end
@@ -14,11 +14,19 @@ module Formtastic
14
14
  # Overrides standard `input_html_options` to provide a `maxlength` and `size` attribute.
15
15
  def input_html_options
16
16
  {
17
- :maxlength => options[:input_html].try(:[], :maxlength) || limit,
18
- :size => builder.default_text_field_size
17
+ :maxlength => maxlength,
18
+ :size => size
19
19
  }.merge(super)
20
20
  end
21
21
 
22
+ def size
23
+ builder.default_text_field_size
24
+ end
25
+
26
+ def maxlength
27
+ options[:input_html].try(:[], :maxlength) || limit
28
+ end
29
+
22
30
  def wrapper_html_options
23
31
  new_class = [super[:class], "stringish"].compact.join(" ")
24
32
  super.merge(:class => new_class)
@@ -1,7 +1,7 @@
1
1
  module Formtastic
2
2
  module Inputs
3
3
  module Base
4
- # Timeish inputs (`:date`, `:datetime`, `:time`) are similar to the Rails date and time
4
+ # Timeish inputs (`:date_select`, `:datetime_select`, `:time_select`) are similar to the Rails date and time
5
5
  # helpers (`date_select`, `datetime_select`, `time_select`), rendering a series of `<select>`
6
6
  # tags for each fragment (year, month, day, hour, minute, seconds). The fragments are then
7
7
  # re-combined to a date by ActiveRecord through multi-parameter assignment.
@@ -21,12 +21,12 @@ module Formtastic
21
21
  # * floating the `<li>` fragments against each other as a single line
22
22
  # * hiding the `<label>` of each fragment with `display:none`
23
23
  #
24
- # @example `:date` input with full form context and sample HTMl output
24
+ # @example `:date_select` input with full form context and sample HTMl output
25
25
  #
26
26
  # <%= semantic_form_for(@post) do |f| %>
27
27
  # <%= f.inputs do %>
28
28
  # ...
29
- # <%= f.input :publish_at, :as => :date %>
29
+ # <%= f.input :publish_at, :as => :date_select %>
30
30
  # <% end %>
31
31
  # <% end %>
32
32
  #
@@ -56,31 +56,37 @@ module Formtastic
56
56
  # </form>
57
57
  #
58
58
  #
59
- # @example `:time` input
60
- # <%= f.input :publish_at, :as => :time %>
59
+ # @example `:time_select` input
60
+ # <%= f.input :publish_at, :as => :time_select %>
61
61
  #
62
- # @example `:datetime` input
63
- # <%= f.input :publish_at, :as => :datetime %>
62
+ # @example `:datetime_select` input
63
+ # <%= f.input :publish_at, :as => :datetime_select %>
64
64
  #
65
65
  # @example Change the labels for each fragment
66
- # <%= f.input :publish_at, :as => :date, :labels => { :year => "Y", :month => "M", :day => "D" } %>
66
+ # <%= f.input :publish_at, :as => :date_select, :labels => { :year => "Y", :month => "M", :day => "D" } %>
67
+ #
68
+ # @example Suppress the labels for all fragments
69
+ # <%= f.input :publish_at, :as => :date_select, :labels => false %>
67
70
  #
68
71
  # @example Skip a fragment (defaults to 1, skips all following fragments)
69
- # <%= f.input :publish_at, :as => :datetime, :discard_minute => true %>
70
- # <%= f.input :publish_at, :as => :datetime, :discard_hour => true %>
71
- # <%= f.input :publish_at, :as => :datetime, :discard_day => true %>
72
- # <%= f.input :publish_at, :as => :datetime, :discard_month => true %>
73
- # <%= f.input :publish_at, :as => :datetime, :discard_year => true %>
72
+ # <%= f.input :publish_at, :as => :datetime_select, :discard_minute => true %>
73
+ # <%= f.input :publish_at, :as => :datetime_select, :discard_hour => true %>
74
+ # <%= f.input :publish_at, :as => :datetime_select, :discard_day => true %>
75
+ # <%= f.input :publish_at, :as => :datetime_select, :discard_month => true %>
76
+ # <%= f.input :publish_at, :as => :datetime_select, :discard_year => true %>
74
77
  #
75
78
  # @example Change the order
76
- # <%= f.input :publish_at, :as => :date, :order => [:month, :day, :year] %>
79
+ # <%= f.input :publish_at, :as => :date_select, :order => [:month, :day, :year] %>
77
80
  #
78
81
  # @example Include seconds with times (excluded by default)
79
- # <%= f.input :publish_at, :as => :time, :include_seconds => true %>
82
+ # <%= f.input :publish_at, :as => :time_select, :include_seconds => true %>
80
83
  #
81
84
  # @example Specify if there should be a blank option at the start of each select or not. Note that, unlike select inputs, :include_blank does not accept a string value.
82
- # <%= f.input :publish_at, :as => :time, :include_blank => true %>
83
- # <%= f.input :publish_at, :as => :time, :include_blank => false %>
85
+ # <%= f.input :publish_at, :as => :time_select, :include_blank => true %>
86
+ # <%= f.input :publish_at, :as => :time_select, :include_blank => false %>
87
+ #
88
+ # @example Provide a value for the field via selected
89
+ # <%= f.input :publish_at, :as => :datetime_select, :selected => DateTime.new(2018, 10, 4, 12, 00)
84
90
  #
85
91
  # @todo Document i18n
86
92
  # @todo Check what other Rails options are supported (`start_year`, `end_year`, `use_month_numbers`, `use_short_month`, `add_month_numbers`, `prompt`), write tests for them, and otherwise support them
@@ -130,8 +136,10 @@ module Formtastic
130
136
  end
131
137
 
132
138
  def fragment_label(fragment)
133
- labels_from_options = options[:labels] || {}
134
- if labels_from_options.key?(fragment)
139
+ labels_from_options = options.key?(:labels) ? options[:labels] : {}
140
+ if !labels_from_options
141
+ ''
142
+ elsif labels_from_options.key?(fragment)
135
143
  labels_from_options[fragment]
136
144
  else
137
145
  ::I18n.t(fragment.to_s, :default => fragment.to_s.humanize, :scope => [:datetime, :prompts])
@@ -152,6 +160,7 @@ module Formtastic
152
160
  end
153
161
 
154
162
  def value
163
+ return input_options[:selected] if options.key?(:selected)
155
164
  object.send(method) if object && object.respond_to?(method)
156
165
  end
157
166
 
@@ -183,8 +192,11 @@ module Formtastic
183
192
 
184
193
  def i18n_date_fragments
185
194
  order = ::I18n.t(:order, :scope => [:date])
186
- order = nil unless order.is_a?(Array)
187
- order
195
+ if order.is_a?(Array)
196
+ order.map &:to_sym
197
+ else
198
+ nil
199
+ end
188
200
  end
189
201
 
190
202
  def fragments_wrapping(&block)
@@ -230,4 +242,4 @@ module Formtastic
230
242
  end
231
243
  end
232
244
  end
233
- end
245
+ end
@@ -35,8 +35,10 @@ module Formtastic
35
35
  return true unless validator.options.key?(:if) || validator.options.key?(:unless)
36
36
  conditional = validator.options.key?(:if) ? validator.options[:if] : validator.options[:unless]
37
37
 
38
- result = if conditional.respond_to?(:call)
38
+ result = if conditional.respond_to?(:call) && conditional.arity > 0
39
39
  conditional.call(object)
40
+ elsif conditional.respond_to?(:call) && conditional.arity == 0
41
+ object.instance_exec(&conditional)
40
42
  elsif conditional.is_a?(::Symbol) && object.respond_to?(conditional)
41
43
  object.send(conditional)
42
44
  else
@@ -71,13 +73,11 @@ module Formtastic
71
73
  raise IndeterminableMinimumAttributeError if validation.options[:greater_than] && column? && [:float, :decimal].include?(column.type)
72
74
 
73
75
  if validation.options[:greater_than_or_equal_to]
74
- return (validation.options[:greater_than_or_equal_to].call(object)) if validation.options[:greater_than_or_equal_to].kind_of?(Proc)
75
- return (validation.options[:greater_than_or_equal_to])
76
+ return option_value(validation.options[:greater_than_or_equal_to], object)
76
77
  end
77
78
 
78
79
  if validation.options[:greater_than]
79
- return (validation.options[:greater_than].call(object) + 1) if validation.options[:greater_than].kind_of?(Proc)
80
- return (validation.options[:greater_than] + 1)
80
+ return option_value(validation.options[:greater_than], object) + 1
81
81
  end
82
82
  end
83
83
  end
@@ -92,13 +92,11 @@ module Formtastic
92
92
  raise IndeterminableMaximumAttributeError if validation.options[:less_than] && column? && [:float, :decimal].include?(column.type)
93
93
 
94
94
  if validation.options[:less_than_or_equal_to]
95
- return (validation.options[:less_than_or_equal_to].call(object)) if validation.options[:less_than_or_equal_to].kind_of?(Proc)
96
- return (validation.options[:less_than_or_equal_to])
95
+ return option_value(validation.options[:less_than_or_equal_to], object)
97
96
  end
98
97
 
99
98
  if validation.options[:less_than]
100
- return ((validation.options[:less_than].call(object)) - 1) if validation.options[:less_than].kind_of?(Proc)
101
- return (validation.options[:less_than] - 1)
99
+ return option_value(validation.options[:less_than], object) - 1
102
100
  end
103
101
  end
104
102
  end
@@ -134,9 +132,10 @@ module Formtastic
134
132
  return true if options[:required] == true
135
133
  return false if not_required_through_negated_validation?
136
134
  if validations?
137
- validations.select { |validator|
135
+ validations.any? { |validator|
138
136
  if validator.options.key?(:on)
139
- return false if (validator.options[:on] != :save) && ((object.new_record? && validator.options[:on] != :create) || (!object.new_record? && validator.options[:on] != :update))
137
+ validator_on = Array(validator.options[:on])
138
+ next false if (validator_on.exclude?(:save)) && ((object.new_record? && validator_on.exclude?(:create)) || (!object.new_record? && validator_on.exclude?(:update)))
140
139
  end
141
140
  case validator.kind
142
141
  when :presence
@@ -150,7 +149,7 @@ module Formtastic
150
149
  else
151
150
  false
152
151
  end
153
- }.any?
152
+ }
154
153
  else
155
154
  return responds_to_global_required? && !!builder.all_fields_required_by_default
156
155
  end
@@ -190,8 +189,37 @@ module Formtastic
190
189
  validation_limit || column_limit
191
190
  end
192
191
 
192
+ def readonly?
193
+ readonly_from_options? || readonly_attribute?
194
+ end
195
+
196
+ def readonly_attribute?
197
+ object_class = self.object.class
198
+ object_class.respond_to?(:readonly_attributes) &&
199
+ self.object.persisted? &&
200
+ column.respond_to?(:name) &&
201
+ object_class.readonly_attributes.include?(column.name.to_s)
202
+ end
203
+
204
+ def readonly_from_options?
205
+ options[:input_html] && options[:input_html][:readonly]
206
+ end
207
+
208
+ private
209
+
210
+ # Loosely based on
211
+ # https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/validations/numericality.rb#L54-L59
212
+ def option_value(option, object)
213
+ case option
214
+ when Symbol
215
+ object.send(option)
216
+ when Proc
217
+ option.call(object)
218
+ else
219
+ option
220
+ end
221
+ end
193
222
  end
194
223
  end
195
224
  end
196
225
  end
197
-
@@ -3,44 +3,47 @@ module Formtastic
3
3
  module Base
4
4
  # @todo relies on `dom_id`, `required?`, `optional`, `errors?`, `association_primary_key` & `sanitized_method_name` methods from another module
5
5
  module Wrapping
6
-
6
+
7
7
  # Override this method if you want to change the display order (for example, rendering the
8
8
  # errors before the body of the input).
9
9
  def input_wrapping(&block)
10
- template.content_tag(:li,
11
- [template.capture(&block), error_html, hint_html].join("\n").html_safe,
10
+ template.content_tag(:li,
11
+ [template.capture(&block), error_html, hint_html].join("\n").html_safe,
12
12
  wrapper_html_options
13
13
  )
14
14
  end
15
-
15
+
16
16
  def wrapper_html_options
17
- opts = (options[:wrapper_html] || {}).dup
18
- opts[:class] =
19
- case opts[:class]
20
- when Array
21
- opts[:class].dup
22
- when nil
23
- []
24
- else
25
- [opts[:class].to_s]
26
- end
27
- opts[:class] << as
28
- opts[:class] << "input"
29
- opts[:class] << "error" if errors?
30
- opts[:class] << "optional" if optional?
31
- opts[:class] << "required" if required?
32
- opts[:class] << "autofocus" if autofocus?
33
- opts[:class] = opts[:class].join(' ')
34
-
35
- opts[:id] ||= wrapper_dom_id
36
-
17
+ opts = wrapper_html_options_raw
18
+ opts[:class] = wrapper_classes
19
+ opts[:id] = wrapper_dom_id unless opts.has_key? :id
37
20
  opts
38
21
  end
39
-
22
+
23
+ def wrapper_html_options_raw
24
+ (options[:wrapper_html] || {}).dup
25
+ end
26
+
27
+ def wrapper_classes_raw
28
+ [*wrapper_html_options_raw[:class]]
29
+ end
30
+
31
+ def wrapper_classes
32
+ classes = wrapper_classes_raw
33
+ classes << as
34
+ classes << "input"
35
+ classes << "error" if errors?
36
+ classes << "optional" if optional?
37
+ classes << "required" if required?
38
+ classes << "autofocus" if autofocus?
39
+
40
+ classes.join(' ')
41
+ end
42
+
40
43
  def wrapper_dom_id
41
44
  @wrapper_dom_id ||= "#{dom_id.to_s.gsub((association_primary_key || method).to_s, sanitized_method_name.to_s)}_input"
42
45
  end
43
-
46
+
44
47
  end
45
48
  end
46
49
  end
@@ -1,9 +1,9 @@
1
1
  module Formtastic
2
2
  module Inputs
3
3
  module Base
4
-
4
+
5
5
  attr_accessor :builder, :template, :object, :object_name, :method, :options
6
-
6
+
7
7
  def initialize(builder, template, object, object_name, method, options)
8
8
  @builder = builder
9
9
  @template = template
@@ -11,34 +11,42 @@ module Formtastic
11
11
  @object_name = object_name
12
12
  @method = method
13
13
  @options = options.dup
14
-
15
- removed_option!(:label_method)
16
- removed_option!(:value_method)
17
- removed_option!(:group_label_method)
14
+
15
+ # Deprecate :member_label and :member_value, remove v4.0
16
+ member_deprecation_message = "passing an Array of label/value pairs like [['Justin', 2], ['Kate', 3]] into :collection directly (consider building the array in your model using Model.pluck)"
17
+ warn_deprecated_option!(:member_label, member_deprecation_message)
18
+ warn_deprecated_option!(:member_value, member_deprecation_message)
18
19
  end
19
-
20
+
20
21
  # Usefull for deprecating options.
21
22
  def warn_and_correct_option!(old_option_name, new_option_name)
22
23
  if options.key?(old_option_name)
23
- ::ActiveSupport::Deprecation.warn("The :#{old_option_name} option is deprecated in favour of :#{new_option_name} and will be removed from Formtastic after 2.0")
24
+ ::ActiveSupport::Deprecation.warn("The :#{old_option_name} option is deprecated in favour of :#{new_option_name} and will be removed from Formtastic in the next version", caller(6))
24
25
  options[new_option_name] = options.delete(old_option_name)
25
26
  end
26
27
  end
27
-
28
+
29
+ # Usefull for deprecating options.
30
+ def warn_deprecated_option!(old_option_name, instructions)
31
+ if options.key?(old_option_name)
32
+ ::ActiveSupport::Deprecation.warn("The :#{old_option_name} option is deprecated in favour of `#{instructions}`. :#{old_option_name} will be removed in the next version", caller(6))
33
+ end
34
+ end
35
+
28
36
  # Usefull for raising an error on previously supported option.
29
37
  def removed_option!(old_option_name)
30
38
  raise ArgumentError, ":#{old_option_name} is no longer available" if options.key?(old_option_name)
31
39
  end
32
-
40
+
33
41
  extend ActiveSupport::Autoload
34
-
42
+
43
+ autoload :DatetimePickerish
35
44
  autoload :Associations
36
45
  autoload :Collections
37
46
  autoload :Choices
38
47
  autoload :Database
39
48
  autoload :Errors
40
49
  autoload :Fileish
41
- autoload :GroupedCollections
42
50
  autoload :Hints
43
51
  autoload :Html
44
52
  autoload :Labelling
@@ -50,7 +58,7 @@ module Formtastic
50
58
  autoload :Timeish
51
59
  autoload :Validations
52
60
  autoload :Wrapping
53
-
61
+
54
62
  include Html
55
63
  include Options
56
64
  include Database
@@ -62,8 +70,7 @@ module Formtastic
62
70
  include Associations
63
71
  include Labelling
64
72
  include Wrapping
65
-
73
+
66
74
  end
67
75
  end
68
76
  end
69
-
@@ -50,17 +50,12 @@ module Formtastic
50
50
  label_html_options
51
51
  )
52
52
  end
53
-
53
+
54
54
  def label_html_options
55
- prev = super
56
- prev[:class] = prev[:class] - ['label']
57
-
58
- input_html_options.merge(
59
- prev.merge(
60
- :id => nil,
61
- :for => input_html_options[:id]
62
- )
63
- )
55
+ {
56
+ :for => input_html_options[:id],
57
+ :class => super[:class] - ['label'] # remove 'label' class
58
+ }
64
59
  end
65
60
 
66
61
  def label_text_with_embedded_checkbox
@@ -96,9 +91,28 @@ module Formtastic
96
91
  end
97
92
 
98
93
  def checked?
99
- object && ActionView::Helpers::InstanceTag.check_box_checked?(object.send(method), checked_value)
94
+ object && boolean_checked?(object.send(method), checked_value)
100
95
  end
96
+
97
+ private
101
98
 
99
+ def boolean_checked?(value, checked_value)
100
+ case value
101
+ when TrueClass, FalseClass
102
+ value
103
+ when NilClass
104
+ false
105
+ when Integer
106
+ value == checked_value.to_i
107
+ when String
108
+ value == checked_value
109
+ when Array
110
+ value.include?(checked_value)
111
+ else
112
+ value.to_i != 0
113
+ end
114
+ end
115
+
102
116
  end
103
117
  end
104
- end
118
+ end
@@ -39,12 +39,17 @@ module Formtastic
39
39
  # <%= f.input :categories, :as => :check_boxes, :collection => @categories %>
40
40
  # <%= f.input :categories, :as => :check_boxes, :collection => Category.all %>
41
41
  # <%= f.input :categories, :as => :check_boxes, :collection => Category.some_named_scope %>
42
+ # <%= f.input :categories, :as => :check_boxes, :collection => Category.pluck(:label, :id) %>
42
43
  # <%= f.input :categories, :as => :check_boxes, :collection => [Category.find_by_name("Ruby"), Category.find_by_name("Rails")] %>
43
44
  # <%= f.input :categories, :as => :check_boxes, :collection => ["Ruby", "Rails"] %>
44
45
  # <%= f.input :categories, :as => :check_boxes, :collection => [["Ruby", "ruby"], ["Rails", "rails"]] %>
45
46
  # <%= f.input :categories, :as => :check_boxes, :collection => [["Ruby", "1"], ["Rails", "2"]] %>
46
47
  # <%= f.input :categories, :as => :check_boxes, :collection => [["Ruby", 1], ["Rails", 2]] %>
48
+ # <%= f.input :categories, :as => :check_boxes, :collection => [["Ruby", 1, {'data-attr' => 'attr-value'}]] %>
47
49
  # <%= f.input :categories, :as => :check_boxes, :collection => 1..5 %>
50
+ # <%= f.input :categories, :as => :check_boxes, :collection => [:ruby, :rails] %>
51
+ # <%= f.input :categories, :as => :check_boxes, :collection => [["Ruby", :ruby], ["Rails", :rails]] %>
52
+ # <%= f.input :categories, :as => :check_boxes, :collection => Set.new([:ruby, :rails]) %>
48
53
  #
49
54
  # @example `:hidden_fields` can be used to skip Rails' rendering of a hidden field before every checkbox
50
55
  # <%= f.input :categories, :as => :check_boxes, :hidden_fields => false %>
@@ -52,24 +57,6 @@ module Formtastic
52
57
  # @example `:disabled` can be used to disable any checkboxes with a value found in the given Array
53
58
  # <%= f.input :categories, :as => :check_boxes, :collection => ["a", "b"], :disabled => ["a"] %>
54
59
  #
55
- # @example `:member_label` can be used to call a different method (or a Proc) on each object in the collection for rendering the label text (it'll try the methods like `to_s` in `collection_label_methods` config by default)
56
- # <%= f.input :categories, :as => :check_boxes, :member_label => :name %>
57
- # <%= f.input :categories, :as => :check_boxes, :member_label => :name_with_post_count
58
- # <%= f.input :categories, :as => :check_boxes, :member_label => { |c| "#{c.name} (#{pluralize("post", c.posts.count)})" }
59
- #
60
- # @example `:member_label` can be used with a helper method (both examples have the same result)
61
- # <%= f.input :categories, :as => :check_boxes, :member_label => method(:fancy_label)
62
- # <%= f.input :categories, :as => :check_boxes, :member_label => Proc.new { |category| fancy_label(category) }
63
- #
64
- # @example `:member_value` can be used to call a different method (or a Proc) on each object in the collection for rendering the value for each checkbox (it'll try the methods like `id` in `collection_value_methods` config by default)
65
- # <%= f.input :categories, :as => :check_boxes, :member_value => :code %>
66
- # <%= f.input :categories, :as => :check_boxes, :member_value => :isbn
67
- # <%= f.input :categories, :as => :check_boxes, :member_value => Proc.new { |c| c.name.downcase.underscore }
68
- #
69
- # @example `:member_value` can be used with a helper method (both examples have the same result)
70
- # <%= f.input :categories, :as => :check_boxes, :member_value => method(:some_helper)
71
- # <%= f.input :categories, :as => :check_boxes, :member_value => Proc.new { |category| some_helper(category) }
72
- #
73
60
  # @example `:value_as_class` can be used to add a class to the `<li>` wrapped around each choice using the checkbox value for custom styling of each choice
74
61
  # <%= f.input :categories, :as => :check_boxes, :value_as_class => true %>
75
62
  #
@@ -82,6 +69,11 @@ module Formtastic
82
69
  include Base::Collections
83
70
  include Base::Choices
84
71
 
72
+ def initialize(*args)
73
+ super
74
+ raise Formtastic::UnsupportedEnumCollection if collection_from_enum?
75
+ end
76
+
85
77
  def to_html
86
78
  input_wrapping do
87
79
  choices_wrapping do
@@ -99,18 +91,16 @@ module Formtastic
99
91
  end
100
92
 
101
93
  def choice_html(choice)
102
- template.content_tag(:label,
103
- hidden_fields? ?
104
- check_box_with_hidden_input(choice) :
105
- check_box_without_hidden_input(choice) <<
106
- choice_label(choice),
94
+ template.content_tag(
95
+ :label,
96
+ checkbox_input(choice) + choice_label(choice),
107
97
  label_html_options.merge(:for => choice_input_dom_id(choice), :class => nil)
108
98
  )
109
99
  end
110
100
 
111
101
  def hidden_field_for_all
112
- if hidden_fields?
113
- ""
102
+ if hidden_fields_for_every?
103
+ ''
114
104
  else
115
105
  options = {}
116
106
  options[:class] = [method.to_s.singularize, 'default'].join('_') if value_as_class?
@@ -119,7 +109,7 @@ module Formtastic
119
109
  end
120
110
  end
121
111
 
122
- def hidden_fields?
112
+ def hidden_fields_for_every?
123
113
  options[:hidden_fields]
124
114
  end
125
115
 
@@ -127,7 +117,7 @@ module Formtastic
127
117
  value = choice_value(choice)
128
118
  builder.check_box(
129
119
  association_primary_key || method,
130
- input_html_options.merge(:id => choice_input_dom_id(choice), :name => input_name, :disabled => disabled?(value), :required => false),
120
+ extra_html_options(choice).merge(:id => choice_input_dom_id(choice), :name => input_name, :disabled => disabled?(value), :required => false),
131
121
  value,
132
122
  unchecked_value
133
123
  )
@@ -139,10 +129,14 @@ module Formtastic
139
129
  input_name,
140
130
  value,
141
131
  checked?(value),
142
- input_html_options.merge(:id => choice_input_dom_id(choice), :disabled => disabled?(value), :required => false)
132
+ extra_html_options(choice).merge(:id => choice_input_dom_id(choice), :disabled => disabled?(value), :required => false)
143
133
  )
144
134
  end
145
135
 
136
+ def extra_html_options(choice)
137
+ input_html_options.merge(custom_choice_html_options(choice))
138
+ end
139
+
146
140
  def checked?(value)
147
141
  selected_values.include?(value)
148
142
  end
@@ -175,11 +169,25 @@ module Formtastic
175
169
 
176
170
  protected
177
171
 
172
+ def checkbox_input(choice)
173
+ if hidden_fields_for_every?
174
+ check_box_with_hidden_input(choice)
175
+ else
176
+ check_box_without_hidden_input(choice)
177
+ end
178
+ end
179
+
178
180
  def make_selected_values
179
181
  if object.respond_to?(method)
180
- selected_items = [object.send(method)].compact.flatten
181
-
182
- [*selected_items.map { |o| send_or_call_or_object(value_method, o) }].compact
182
+ selected_items = object.send(method)
183
+ # Construct an array from the return value, regardless of the return type
184
+ selected_items = [*selected_items].compact.flatten
185
+
186
+ selected = []
187
+ selected_items.map do |selected_item|
188
+ selected_item_id = selected_item.id if selected_item.respond_to? :id
189
+ item = send_or_call_or_object(value_method, selected_item) || selected_item_id
190
+ end.compact
183
191
  else
184
192
  []
185
193
  end
@@ -0,0 +1,41 @@
1
+ module Formtastic
2
+ module Inputs
3
+
4
+ # Outputs a simple `<label>` with a HTML5 `<input type="color">` wrapped in the standard
5
+ # `<li>` wrapper. This is the default input choice for attributes with a name matching
6
+ # `/color/`, but can be applied to any text-like input with `:as => :color`.
7
+ #
8
+ # @example Full form context and output
9
+ #
10
+ # <%= semantic_form_for(@user) do |f| %>
11
+ # <%= f.inputs do %>
12
+ # <%= f.input :color, :as => :color %>
13
+ # <% end %>
14
+ # <% end %>
15
+ #
16
+ # <form...>
17
+ # <fieldset>
18
+ # <ol>
19
+ # <li class="color">
20
+ # <label for="user_color">Color</label>
21
+ # <input type="color" id="user_color" name="user[color]">
22
+ # </li>
23
+ # </ol>
24
+ # </fieldset>
25
+ # </form>
26
+ #
27
+ # @see Formtastic::Helpers::InputsHelper#input InputsHelper#input for full documentation of all possible options.
28
+ class ColorInput
29
+ include Base
30
+ include Base::Stringish
31
+ include Base::Placeholder
32
+
33
+ def to_html
34
+ input_wrapping do
35
+ label_html <<
36
+ builder.color_field(method, input_html_options)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end