formtastic 2.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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