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.
- 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
|