formtastic 2.1.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.github/workflows/test.yml +61 -0
- data/.gitignore +4 -2
- data/CHANGELOG.md +52 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +105 -0
- data/MIT-LICENSE +1 -1
- data/{README.textile → README.md} +204 -219
- data/RELEASE_PROCESS +3 -1
- data/Rakefile +27 -29
- data/app/assets/stylesheets/formtastic.css +3 -2
- data/bin/appraisal +8 -0
- data/formtastic.gemspec +11 -14
- data/gemfiles/rails_5.2/Gemfile +5 -0
- data/gemfiles/rails_6.0/Gemfile +5 -0
- data/gemfiles/rails_6.1/Gemfile +5 -0
- data/gemfiles/rails_edge/Gemfile +13 -0
- data/lib/formtastic/action_class_finder.rb +18 -0
- data/lib/formtastic/actions/button_action.rb +55 -60
- data/lib/formtastic/actions/input_action.rb +59 -57
- data/lib/formtastic/actions/link_action.rb +68 -67
- data/lib/formtastic/actions.rb +6 -3
- data/lib/formtastic/deprecation.rb +5 -0
- data/lib/formtastic/engine.rb +3 -1
- data/lib/formtastic/form_builder.rb +35 -16
- data/lib/formtastic/helpers/action_helper.rb +34 -28
- data/lib/formtastic/helpers/enum.rb +13 -0
- data/lib/formtastic/helpers/errors_helper.rb +2 -2
- data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -12
- data/lib/formtastic/helpers/form_helper.rb +19 -16
- data/lib/formtastic/helpers/input_helper.rb +69 -97
- data/lib/formtastic/helpers/inputs_helper.rb +35 -25
- data/lib/formtastic/helpers/reflection.rb +4 -4
- data/lib/formtastic/helpers.rb +1 -2
- data/lib/formtastic/html_attributes.rb +12 -1
- data/lib/formtastic/i18n.rb +1 -1
- data/lib/formtastic/input_class_finder.rb +18 -0
- data/lib/formtastic/inputs/base/choices.rb +2 -2
- data/lib/formtastic/inputs/base/collections.rb +46 -14
- data/lib/formtastic/inputs/base/database.rb +7 -2
- data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
- data/lib/formtastic/inputs/base/errors.rb +7 -7
- data/lib/formtastic/inputs/base/hints.rb +2 -2
- data/lib/formtastic/inputs/base/html.rb +10 -9
- data/lib/formtastic/inputs/base/labelling.rb +5 -8
- data/lib/formtastic/inputs/base/naming.rb +4 -4
- data/lib/formtastic/inputs/base/numeric.rb +1 -1
- data/lib/formtastic/inputs/base/options.rb +3 -4
- data/lib/formtastic/inputs/base/stringish.rb +10 -2
- data/lib/formtastic/inputs/base/timeish.rb +34 -22
- data/lib/formtastic/inputs/base/validations.rb +41 -13
- data/lib/formtastic/inputs/base/wrapping.rb +29 -26
- data/lib/formtastic/inputs/base.rb +22 -15
- data/lib/formtastic/inputs/boolean_input.rb +26 -12
- data/lib/formtastic/inputs/check_boxes_input.rb +39 -31
- data/lib/formtastic/inputs/color_input.rb +41 -0
- data/lib/formtastic/inputs/country_input.rb +24 -5
- data/lib/formtastic/inputs/datalist_input.rb +41 -0
- data/lib/formtastic/inputs/date_picker_input.rb +93 -0
- data/lib/formtastic/inputs/{date_input.rb → date_select_input.rb} +1 -1
- data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
- data/lib/formtastic/inputs/{datetime_input.rb → datetime_select_input.rb} +1 -1
- data/lib/formtastic/inputs/file_input.rb +2 -2
- data/lib/formtastic/inputs/hidden_input.rb +2 -6
- data/lib/formtastic/inputs/radio_input.rb +28 -22
- data/lib/formtastic/inputs/select_input.rb +36 -39
- data/lib/formtastic/inputs/time_picker_input.rb +99 -0
- data/lib/formtastic/inputs/{time_input.rb → time_select_input.rb} +6 -2
- data/lib/formtastic/inputs/time_zone_input.rb +16 -6
- data/lib/formtastic/inputs.rb +32 -21
- data/lib/formtastic/localized_string.rb +1 -1
- data/lib/formtastic/localizer.rb +24 -24
- data/lib/formtastic/namespaced_class_finder.rb +99 -0
- data/lib/formtastic/version.rb +1 -1
- data/lib/formtastic.rb +20 -10
- data/lib/generators/formtastic/form/form_generator.rb +10 -4
- data/lib/generators/formtastic/input/input_generator.rb +46 -0
- data/lib/generators/formtastic/install/install_generator.rb +5 -19
- data/lib/generators/templates/_form.html.slim +2 -2
- data/lib/generators/templates/formtastic.rb +46 -25
- data/lib/generators/templates/input.rb +19 -0
- data/sample/basic_inputs.html +23 -3
- data/script/integration-template.rb +74 -0
- data/script/integration.sh +19 -0
- data/spec/action_class_finder_spec.rb +12 -0
- data/spec/actions/button_action_spec.rb +8 -8
- data/spec/actions/generic_action_spec.rb +92 -56
- data/spec/actions/input_action_spec.rb +7 -7
- data/spec/actions/link_action_spec.rb +10 -10
- data/spec/builder/custom_builder_spec.rb +36 -20
- data/spec/builder/error_proc_spec.rb +4 -4
- data/spec/builder/semantic_fields_for_spec.rb +28 -29
- data/spec/fast_spec_helper.rb +12 -0
- data/spec/generators/formtastic/form/form_generator_spec.rb +45 -32
- data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
- data/spec/generators/formtastic/install/install_generator_spec.rb +9 -9
- data/spec/helpers/action_helper_spec.rb +75 -103
- data/spec/helpers/actions_helper_spec.rb +17 -17
- data/spec/helpers/form_helper_spec.rb +84 -33
- data/spec/helpers/input_helper_spec.rb +333 -285
- data/spec/helpers/inputs_helper_spec.rb +167 -121
- data/spec/helpers/reflection_helper_spec.rb +3 -3
- data/spec/helpers/semantic_errors_helper_spec.rb +23 -23
- data/spec/i18n_spec.rb +26 -26
- data/spec/input_class_finder_spec.rb +10 -0
- data/spec/inputs/base/collections_spec.rb +76 -0
- data/spec/inputs/base/validations_spec.rb +480 -0
- data/spec/inputs/boolean_input_spec.rb +100 -65
- data/spec/inputs/check_boxes_input_spec.rb +200 -101
- data/spec/inputs/color_input_spec.rb +85 -0
- data/spec/inputs/country_input_spec.rb +20 -20
- data/spec/inputs/custom_input_spec.rb +3 -4
- data/spec/inputs/datalist_input_spec.rb +61 -0
- data/spec/inputs/date_picker_input_spec.rb +449 -0
- data/spec/inputs/date_select_input_spec.rb +249 -0
- data/spec/inputs/datetime_picker_input_spec.rb +490 -0
- data/spec/inputs/datetime_select_input_spec.rb +209 -0
- data/spec/inputs/email_input_spec.rb +5 -5
- data/spec/inputs/file_input_spec.rb +6 -6
- data/spec/inputs/hidden_input_spec.rb +22 -35
- data/spec/inputs/include_blank_spec.rb +11 -11
- data/spec/inputs/label_spec.rb +62 -25
- data/spec/inputs/number_input_spec.rb +112 -112
- data/spec/inputs/password_input_spec.rb +5 -5
- data/spec/inputs/phone_input_spec.rb +5 -5
- data/spec/inputs/placeholder_spec.rb +6 -6
- data/spec/inputs/radio_input_spec.rb +99 -55
- data/spec/inputs/range_input_spec.rb +66 -66
- data/spec/inputs/readonly_spec.rb +50 -0
- data/spec/inputs/search_input_spec.rb +5 -5
- data/spec/inputs/select_input_spec.rb +170 -170
- data/spec/inputs/string_input_spec.rb +68 -16
- data/spec/inputs/text_input_spec.rb +16 -16
- data/spec/inputs/time_picker_input_spec.rb +455 -0
- data/spec/inputs/time_select_input_spec.rb +261 -0
- data/spec/inputs/time_zone_input_spec.rb +54 -28
- data/spec/inputs/url_input_spec.rb +5 -5
- data/spec/inputs/with_options_spec.rb +7 -7
- data/spec/localizer_spec.rb +39 -17
- data/spec/namespaced_class_finder_spec.rb +79 -0
- data/spec/schema.rb +21 -0
- data/spec/spec_helper.rb +254 -221
- data/spec/support/custom_macros.rb +128 -95
- data/spec/support/shared_examples.rb +12 -0
- data/spec/support/specialized_class_finder_shared_example.rb +27 -0
- data/spec/support/test_environment.rb +26 -10
- metadata +177 -238
- data/.travis.yml +0 -8
- data/Appraisals +0 -11
- data/CHANGELOG +0 -371
- data/gemfiles/rails-3.0.gemfile +0 -7
- data/gemfiles/rails-3.1.gemfile +0 -7
- data/gemfiles/rails-3.2.gemfile +0 -7
- data/lib/formtastic/helpers/buttons_helper.rb +0 -310
- data/lib/formtastic/inputs/base/grouped_collections.rb +0 -77
- data/lib/formtastic/util.rb +0 -25
- data/lib/tasks/verify_rcov.rb +0 -44
- data/spec/helpers/buttons_helper_spec.rb +0 -166
- data/spec/helpers/commit_button_helper_spec.rb +0 -530
- data/spec/inputs/date_input_spec.rb +0 -227
- data/spec/inputs/datetime_input_spec.rb +0 -185
- data/spec/inputs/time_input_spec.rb +0 -267
- 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, :
|
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 =>
|
18
|
-
: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 (`:
|
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 `:
|
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 => :
|
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 `:
|
60
|
-
# <%= f.input :publish_at, :as => :
|
59
|
+
# @example `:time_select` input
|
60
|
+
# <%= f.input :publish_at, :as => :time_select %>
|
61
61
|
#
|
62
|
-
# @example `:
|
63
|
-
# <%= f.input :publish_at, :as => :
|
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 => :
|
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 => :
|
70
|
-
# <%= f.input :publish_at, :as => :
|
71
|
-
# <%= f.input :publish_at, :as => :
|
72
|
-
# <%= f.input :publish_at, :as => :
|
73
|
-
# <%= f.input :publish_at, :as => :
|
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 => :
|
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 => :
|
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 => :
|
83
|
-
# <%= f.input :publish_at, :as => :
|
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
|
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
|
-
|
187
|
-
|
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]
|
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]
|
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]
|
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 (
|
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.
|
135
|
+
validations.any? { |validator|
|
138
136
|
if validator.options.key?(:on)
|
139
|
-
|
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
|
-
}
|
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 =
|
18
|
-
opts[:class] =
|
19
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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 &&
|
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(
|
103
|
-
|
104
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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 =
|
181
|
-
|
182
|
-
[*selected_items
|
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
|