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
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
module Formtastic
|
|
2
2
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# Defines a new configurable option
|
|
5
|
+
# @param [Symbol] name the configuration name
|
|
6
|
+
# @param [Object] default the configuration default value
|
|
7
|
+
# @private
|
|
8
|
+
#
|
|
9
|
+
# @!macro [new] configure
|
|
10
|
+
# @!scope class
|
|
11
|
+
# @!attribute [rw] $1
|
|
12
|
+
# @api public
|
|
13
|
+
def self.configure(name, default = nil)
|
|
5
14
|
class_attribute(name)
|
|
6
|
-
self.send(:"#{name}=",
|
|
15
|
+
self.send(:"#{name}=", default)
|
|
7
16
|
end
|
|
8
17
|
|
|
9
18
|
configure :custom_namespace
|
|
@@ -12,13 +21,12 @@ module Formtastic
|
|
|
12
21
|
configure :default_text_area_width
|
|
13
22
|
configure :all_fields_required_by_default, true
|
|
14
23
|
configure :include_blank_for_select_by_default, true
|
|
15
|
-
configure :required_string, proc {
|
|
24
|
+
configure :required_string, proc { %{<abbr title="#{Formtastic::I18n.t(:required)}">*</abbr>}.html_safe }
|
|
16
25
|
configure :optional_string, ''
|
|
17
26
|
configure :inline_errors, :sentence
|
|
18
27
|
configure :label_str_method, :humanize
|
|
19
28
|
configure :collection_label_methods, %w[to_label display_name full_name name title username login value to_s]
|
|
20
29
|
configure :collection_value_methods, %w[id to_s]
|
|
21
|
-
configure :custom_inline_order, {}
|
|
22
30
|
configure :file_methods, [ :file?, :public_filename, :filename ]
|
|
23
31
|
configure :file_metadata_suffixes, ['content_type', 'file_name', 'file_size']
|
|
24
32
|
configure :priority_countries, ["Australia", "Canada", "United Kingdom", "United States"]
|
|
@@ -32,6 +40,15 @@ module Formtastic
|
|
|
32
40
|
configure :default_hint_class, 'inline-hints'
|
|
33
41
|
configure :use_required_attribute, false
|
|
34
42
|
configure :perform_browser_validations, false
|
|
43
|
+
# Check {Formtastic::InputClassFinder} to see how are inputs resolved.
|
|
44
|
+
configure :input_namespaces, [::Object, ::Formtastic::Inputs]
|
|
45
|
+
configure :input_class_finder, Formtastic::InputClassFinder
|
|
46
|
+
# Check {Formtastic::ActionClassFinder} to see how are inputs resolved.
|
|
47
|
+
configure :action_namespaces, [::Object, ::Formtastic::Actions]
|
|
48
|
+
configure :action_class_finder, Formtastic::ActionClassFinder
|
|
49
|
+
|
|
50
|
+
configure :skipped_columns, [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
|
51
|
+
configure :priority_time_zones, []
|
|
35
52
|
|
|
36
53
|
attr_reader :template
|
|
37
54
|
|
|
@@ -41,15 +58,14 @@ module Formtastic
|
|
|
41
58
|
|
|
42
59
|
include Formtastic::Helpers::InputHelper
|
|
43
60
|
include Formtastic::Helpers::InputsHelper
|
|
44
|
-
include Formtastic::Helpers::ButtonsHelper
|
|
45
61
|
include Formtastic::Helpers::ActionHelper
|
|
46
62
|
include Formtastic::Helpers::ActionsHelper
|
|
47
63
|
include Formtastic::Helpers::ErrorsHelper
|
|
48
|
-
|
|
49
|
-
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
|
64
|
+
|
|
65
|
+
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
|
50
66
|
# provided to ensure that the `:builder` from `semantic_form_for` was passed down into
|
|
51
|
-
# the nested `fields_for`. Rails
|
|
52
|
-
# provided purely for backwards compatibility and DSL consistency.
|
|
67
|
+
# the nested `fields_for`. Our supported versions of Rails no longer require us to do this,
|
|
68
|
+
# so this method is provided purely for backwards compatibility and DSL consistency.
|
|
53
69
|
#
|
|
54
70
|
# When constructing a `fields_for` form fragment *outside* of `semantic_form_for`, please use
|
|
55
71
|
# `Formtastic::Helpers::FormHelper#semantic_fields_for`.
|
|
@@ -75,14 +91,17 @@ module Formtastic
|
|
|
75
91
|
#
|
|
76
92
|
# @todo is there a way to test the params structure of the Rails helper we wrap to ensure forward compatibility?
|
|
77
93
|
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
|
78
|
-
|
|
79
|
-
options = args.extract_options!
|
|
80
|
-
options[:parent_builder] ||= self
|
|
81
|
-
|
|
82
|
-
# Wrap the Rails helper
|
|
83
|
-
fields_for(record_or_name_or_array, *(args << options), &block)
|
|
94
|
+
fields_for(record_or_name_or_array, *args, &block)
|
|
84
95
|
end
|
|
85
|
-
|
|
96
|
+
|
|
97
|
+
def initialize(object_name, object, template, options)
|
|
98
|
+
super
|
|
99
|
+
|
|
100
|
+
if respond_to?('multipart=') && options.is_a?(Hash) && options[:html]
|
|
101
|
+
self.multipart = options[:html][:multipart]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
86
105
|
end
|
|
87
106
|
|
|
88
107
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
module Formtastic
|
|
3
3
|
module Helpers
|
|
4
4
|
module ActionHelper
|
|
5
|
-
|
|
6
5
|
# Renders an action for the form (such as a subit/reset button, or a cancel link).
|
|
7
6
|
#
|
|
8
7
|
# Each action is wrapped in an `<li class="action">` tag with other classes added based on the
|
|
@@ -12,6 +11,22 @@ module Formtastic
|
|
|
12
11
|
# The textual value of the label can be changed from the default through the `:label`
|
|
13
12
|
# argument or through i18n.
|
|
14
13
|
#
|
|
14
|
+
# If using i18n, you'll need to provide the following translations:
|
|
15
|
+
#
|
|
16
|
+
# en:
|
|
17
|
+
# formtastic:
|
|
18
|
+
# actions:
|
|
19
|
+
# create: "Create new %{model}"
|
|
20
|
+
# update: "Save %{model}"
|
|
21
|
+
# cancel: "Cancel"
|
|
22
|
+
# reset: "Reset form"
|
|
23
|
+
# submit: "Submit"
|
|
24
|
+
#
|
|
25
|
+
# For forms with an object present, the `update` key will be used if calling `persisted?` on
|
|
26
|
+
# the object returns true (saving changes to a record), otherwise the `create` key will be
|
|
27
|
+
# used. The `submit` key is used as a fallback when there is no object or we cannot determine
|
|
28
|
+
# if `create` or `update` is appropriate.
|
|
29
|
+
#
|
|
15
30
|
# @example Basic usage
|
|
16
31
|
# # form
|
|
17
32
|
# <%= semantic_form_for @post do |f| %>
|
|
@@ -64,46 +79,37 @@ module Formtastic
|
|
|
64
79
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
|
65
80
|
options[:as] ||= default_action_type(method, options)
|
|
66
81
|
|
|
67
|
-
klass =
|
|
82
|
+
klass = namespaced_action_class(options[:as])
|
|
68
83
|
|
|
69
84
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
|
70
85
|
end
|
|
71
86
|
|
|
72
87
|
protected
|
|
73
88
|
|
|
74
|
-
def default_action_type(method, options = {})
|
|
89
|
+
def default_action_type(method, options = {}) # @private
|
|
75
90
|
case method
|
|
76
91
|
when :submit then :input
|
|
77
|
-
when :reset
|
|
92
|
+
when :reset then :input
|
|
78
93
|
when :cancel then :link
|
|
94
|
+
else method
|
|
79
95
|
end
|
|
80
96
|
end
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# :as => :button # => ButtonAction
|
|
98
|
-
def custom_action_class_name(as)
|
|
99
|
-
"#{as.to_s.camelize}Action"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# :as => :button # => Formtastic::Actions::ButtonAction
|
|
103
|
-
def standard_action_class_name(as)
|
|
104
|
-
"Formtastic::Actions::#{as.to_s.camelize}Action"
|
|
98
|
+
# Takes the `:as` option and attempts to return the corresponding action
|
|
99
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
|
100
|
+
# top level `AwesomeAction` class (to allow the application to subclass
|
|
101
|
+
# and modify to suit), falling back to `Formtastic::Actions::AwesomeAction`.
|
|
102
|
+
#
|
|
103
|
+
# Custom action namespaces to look into can be configured via the
|
|
104
|
+
# {Formtastic::FormBuilder.action_namespaces} configuration setting.
|
|
105
|
+
# @see Helpers::InputHelper#namespaced_input_class
|
|
106
|
+
# @see Formtastic::ActionClassFinder
|
|
107
|
+
def namespaced_action_class(as)
|
|
108
|
+
@action_class_finder ||= action_class_finder.new(self)
|
|
109
|
+
@action_class_finder.find(as)
|
|
110
|
+
rescue Formtastic::ActionClassFinder::NotFoundError => e
|
|
111
|
+
raise Formtastic::UnknownActionError, "Unable to find action #{e.message}"
|
|
105
112
|
end
|
|
106
|
-
|
|
107
113
|
end
|
|
108
114
|
end
|
|
109
115
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Formtastic
|
|
2
|
+
module Helpers
|
|
3
|
+
# @private
|
|
4
|
+
module Enum
|
|
5
|
+
# Returns the enum (if defined) for the given method
|
|
6
|
+
def enum_for(method) # @private
|
|
7
|
+
if @object.respond_to?(:defined_enums)
|
|
8
|
+
@object.defined_enums[method.to_s]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -52,7 +52,7 @@ module Formtastic
|
|
|
52
52
|
return nil if full_errors.blank?
|
|
53
53
|
html_options[:class] ||= "errors"
|
|
54
54
|
template.content_tag(:ul, html_options) do
|
|
55
|
-
|
|
55
|
+
full_errors.map { |error| template.content_tag(:li, error) }.join.html_safe
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -78,4 +78,4 @@ module Formtastic
|
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
|
-
end
|
|
81
|
+
end
|
|
@@ -21,8 +21,8 @@ module Formtastic
|
|
|
21
21
|
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
|
22
22
|
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
|
23
23
|
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
|
24
|
-
def field_set_and_list_wrapping(*args, &block)
|
|
25
|
-
contents = args.
|
|
24
|
+
def field_set_and_list_wrapping(*args, &block) # @private
|
|
25
|
+
contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
|
|
26
26
|
html_options = args.extract_options!
|
|
27
27
|
|
|
28
28
|
if block_given?
|
|
@@ -33,12 +33,15 @@ module Formtastic
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Work-around for empty contents block
|
|
37
|
+
contents ||= ""
|
|
38
|
+
|
|
36
39
|
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
|
37
40
|
contents = contents.join if contents.respond_to?(:join)
|
|
38
41
|
|
|
39
42
|
legend = field_set_legend(html_options)
|
|
40
43
|
fieldset = template.content_tag(:fieldset,
|
|
41
|
-
|
|
44
|
+
legend.html_safe << template.content_tag(:ol, contents.html_safe),
|
|
42
45
|
html_options.except(:builder, :parent, :name)
|
|
43
46
|
)
|
|
44
47
|
|
|
@@ -47,32 +50,33 @@ module Formtastic
|
|
|
47
50
|
|
|
48
51
|
def field_set_legend(html_options)
|
|
49
52
|
legend = (html_options[:name] || '').to_s
|
|
50
|
-
|
|
51
|
-
legend
|
|
53
|
+
# only applying if String includes '%i' avoids argument error when $DEBUG is true
|
|
54
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i')
|
|
55
|
+
legend = template.content_tag(:legend, template.content_tag(:span, legend.html_safe)) unless legend.blank?
|
|
52
56
|
legend
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
# Gets the nested_child_index value from the parent builder. It returns a hash with each
|
|
56
60
|
# association that the parent builds.
|
|
57
|
-
def parent_child_index(parent)
|
|
61
|
+
def parent_child_index(parent) # @private
|
|
58
62
|
# Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
|
|
59
63
|
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
|
60
|
-
|
|
64
|
+
|
|
61
65
|
# Could be symbol for the association, or a model (or an array of either, I think? TODO)
|
|
62
66
|
child = parent[:for]
|
|
63
67
|
# Pull a sybol or model out of Array (TODO: check if there's an Array)
|
|
64
68
|
child = child.first if child.respond_to?(:first)
|
|
65
69
|
# If it's an object, get a symbol from the class name
|
|
66
70
|
child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
|
|
67
|
-
|
|
71
|
+
|
|
68
72
|
key = "#{parent[:builder].object_name}[#{child}_attributes]"
|
|
69
73
|
|
|
70
|
-
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
|
74
|
+
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
|
71
75
|
# in the legend, so if we have a number, return it with a +1 until we can verify this scenario.
|
|
72
|
-
return duck + 1 if duck.is_a?(
|
|
73
|
-
|
|
76
|
+
return duck + 1 if duck.is_a?(Integer)
|
|
77
|
+
|
|
74
78
|
# First try to extract key from duck Hash, then try child
|
|
75
|
-
|
|
79
|
+
(duck[key] || duck[child]).to_i + 1
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
end
|
|
@@ -58,6 +58,17 @@ module Formtastic
|
|
|
58
58
|
@@default_form_class = 'formtastic'
|
|
59
59
|
mattr_accessor :default_form_class
|
|
60
60
|
|
|
61
|
+
# Allows to set a custom proc to handle the class infered from the model's name. By default it
|
|
62
|
+
# will infer the name from the class name (eg. Post will be "post").
|
|
63
|
+
@@default_form_model_class_proc = proc { |model_class_name| model_class_name }
|
|
64
|
+
mattr_accessor :default_form_model_class_proc
|
|
65
|
+
|
|
66
|
+
# Allows to set a custom field_error_proc wrapper. By default this wrapper
|
|
67
|
+
# is disabled since `formtastic` already adds an error class to the LI tag
|
|
68
|
+
# containing the input. Change this from `config/initializers/formtastic.rb`.
|
|
69
|
+
@@formtastic_field_error_proc = proc { |html_tag, instance_tag| html_tag }
|
|
70
|
+
mattr_accessor :formtastic_field_error_proc
|
|
71
|
+
|
|
61
72
|
# Wrapper around Rails' own `form_for` helper to set the `:builder` option to
|
|
62
73
|
# `Formtastic::FormBuilder` and to set some class names on the `<form>` tag such as
|
|
63
74
|
# `formtastic` and the downcased and underscored model name (eg `post`).
|
|
@@ -144,18 +155,20 @@ module Formtastic
|
|
|
144
155
|
options[:builder] ||= @@builder
|
|
145
156
|
options[:html] ||= {}
|
|
146
157
|
options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
|
|
147
|
-
|
|
158
|
+
options[:custom_namespace] = options.delete(:namespace)
|
|
148
159
|
|
|
149
160
|
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
|
150
161
|
|
|
151
162
|
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|
|
152
163
|
class_names << @@default_form_class
|
|
153
|
-
|
|
164
|
+
model_class_name = case record_or_name_or_array
|
|
154
165
|
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
|
155
|
-
when Array then options[:as] || singularizer.call(record_or_name_or_array.
|
|
166
|
+
when Array then options[:as] || singularizer.call(record_or_name_or_array[-1].class) # [@post, @comment] # => "comment"
|
|
156
167
|
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
|
157
168
|
end
|
|
158
|
-
|
|
169
|
+
class_names << @@default_form_model_class_proc.call(model_class_name)
|
|
170
|
+
|
|
171
|
+
options[:html][:class] = class_names.compact.join(" ")
|
|
159
172
|
|
|
160
173
|
with_custom_field_error_proc do
|
|
161
174
|
self.form_for(record_or_name_or_array, *(args << options), &proc)
|
|
@@ -169,7 +182,7 @@ module Formtastic
|
|
|
169
182
|
def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
|
|
170
183
|
options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
|
|
171
184
|
options[:builder] ||= @@builder
|
|
172
|
-
|
|
185
|
+
options[:custom_namespace] = options.delete(:namespace)
|
|
173
186
|
|
|
174
187
|
with_custom_field_error_proc do
|
|
175
188
|
self.fields_for(record_name, record_object, options, &block)
|
|
@@ -178,23 +191,13 @@ module Formtastic
|
|
|
178
191
|
|
|
179
192
|
protected
|
|
180
193
|
|
|
181
|
-
# Override the default ActiveRecordHelper behaviour of wrapping the input.
|
|
182
|
-
# This gets taken care of semantically by adding an error class to the LI tag
|
|
183
|
-
# containing the input.
|
|
184
|
-
# @private
|
|
185
|
-
FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
|
|
186
|
-
html_tag
|
|
187
|
-
end
|
|
188
|
-
|
|
189
194
|
def with_custom_field_error_proc(&block)
|
|
190
195
|
default_field_error_proc = ::ActionView::Base.field_error_proc
|
|
191
|
-
::ActionView::Base.field_error_proc =
|
|
196
|
+
::ActionView::Base.field_error_proc = @@formtastic_field_error_proc
|
|
192
197
|
yield
|
|
193
198
|
ensure
|
|
194
199
|
::ActionView::Base.field_error_proc = default_field_error_proc
|
|
195
200
|
end
|
|
196
|
-
|
|
197
|
-
|
|
198
201
|
end
|
|
199
202
|
end
|
|
200
203
|
end
|
|
@@ -37,6 +37,7 @@ module Formtastic
|
|
|
37
37
|
# @see Formtastic::Helpers::FormHelper#semantic_form_for
|
|
38
38
|
module InputHelper
|
|
39
39
|
include Formtastic::Helpers::Reflection
|
|
40
|
+
include Formtastic::Helpers::Enum
|
|
40
41
|
include Formtastic::Helpers::FileColumnDetection
|
|
41
42
|
|
|
42
43
|
# Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
|
|
@@ -86,25 +87,26 @@ module Formtastic
|
|
|
86
87
|
#
|
|
87
88
|
# Available input styles:
|
|
88
89
|
#
|
|
89
|
-
# * `:boolean`
|
|
90
|
-
# * `:check_boxes`
|
|
91
|
-
# * `:
|
|
92
|
-
# * `:
|
|
93
|
-
# * `:
|
|
94
|
-
# * `:
|
|
95
|
-
# * `:
|
|
96
|
-
# * `:
|
|
97
|
-
# * `:
|
|
98
|
-
# * `:
|
|
99
|
-
# * `:
|
|
100
|
-
# * `:
|
|
101
|
-
# * `:
|
|
102
|
-
# * `:
|
|
103
|
-
# * `:
|
|
104
|
-
# * `:
|
|
105
|
-
# * `:
|
|
106
|
-
# * `:
|
|
107
|
-
# * `:
|
|
90
|
+
# * `:boolean` (see {Inputs::BooleanInput})
|
|
91
|
+
# * `:check_boxes` (see {Inputs::CheckBoxesInput})
|
|
92
|
+
# * `:color` (see {Inputs::ColorInput})
|
|
93
|
+
# * `:country` (see {Inputs::CountryInput})
|
|
94
|
+
# * `:datetime_select` (see {Inputs::DatetimeSelectInput})
|
|
95
|
+
# * `:date_select` (see {Inputs::DateSelectInput})
|
|
96
|
+
# * `:email` (see {Inputs::EmailInput})
|
|
97
|
+
# * `:file` (see {Inputs::FileInput})
|
|
98
|
+
# * `:hidden` (see {Inputs::HiddenInput})
|
|
99
|
+
# * `:number` (see {Inputs::NumberInput})
|
|
100
|
+
# * `:password` (see {Inputs::PasswordInput})
|
|
101
|
+
# * `:phone` (see {Inputs::PhoneInput})
|
|
102
|
+
# * `:radio` (see {Inputs::RadioInput})
|
|
103
|
+
# * `:search` (see {Inputs::SearchInput})
|
|
104
|
+
# * `:select` (see {Inputs::SelectInput})
|
|
105
|
+
# * `:string` (see {Inputs::StringInput})
|
|
106
|
+
# * `:text` (see {Inputs::TextInput})
|
|
107
|
+
# * `:time_zone` (see {Inputs::TimeZoneInput})
|
|
108
|
+
# * `:time_select` (see {Inputs::TimeSelectInput})
|
|
109
|
+
# * `:url` (see {Inputs::UrlInput})
|
|
108
110
|
#
|
|
109
111
|
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
|
|
110
112
|
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
|
|
@@ -130,6 +132,7 @@ module Formtastic
|
|
|
130
132
|
#
|
|
131
133
|
# @option options :input_html [Hash]
|
|
132
134
|
# Override or add to the HTML attributes to be passed down to the `<input>` tag
|
|
135
|
+
# (If you use attr_readonly method in your model, formtastic will automatically set those attributes's input readonly)
|
|
133
136
|
#
|
|
134
137
|
# @option options :wrapper_html [Hash]
|
|
135
138
|
# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
|
|
@@ -137,40 +140,16 @@ module Formtastic
|
|
|
137
140
|
# @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
|
|
138
141
|
# Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
|
|
139
142
|
#
|
|
140
|
-
# @option options :member_label [Symbol, Proc, Method]
|
|
141
|
-
# Override the method called on each object in the `:collection` for use as the `<label>` content (`:check_boxes` & `:radio` inputs) or `<option>` content (`:select` inputs)
|
|
142
|
-
#
|
|
143
|
-
# @option options :member_value [Symbol, Proc, Method]
|
|
144
|
-
# Override the method called on each object in the `:collection` for use as the `value` attribute in the `<input>` (`:check_boxes` & `:radio` inputs) or `<option>` (`:select` inputs)
|
|
145
|
-
#
|
|
146
|
-
# @option options :hint_class [String]
|
|
147
|
-
# Override the `class` attribute applied to the `<p>` tag used when a `:hint` is rendered for an input
|
|
148
|
-
#
|
|
149
|
-
# @option options :error_class [String]
|
|
150
|
-
# Override the `class` attribute applied to the `<p>` or `<ol>` tag used when inline errors are rendered for an input
|
|
151
|
-
#
|
|
152
143
|
# @option options :multiple [Boolean]
|
|
153
144
|
# Specify if the `:select` input should allow multiple selections or not (defaults to `belongs_to` associations, and `true` for `has_many` and `has_and_belongs_to_many` associations)
|
|
154
145
|
#
|
|
155
|
-
# @option options :group_by [Symbol]
|
|
156
|
-
# TODO will probably be deprecated
|
|
157
|
-
#
|
|
158
|
-
# @option options :find_options [Symbol]
|
|
159
|
-
# TODO will probably be deprecated
|
|
160
|
-
#
|
|
161
|
-
# @option options :group_label [Symbol]
|
|
162
|
-
# TODO will probably be deprecated
|
|
163
|
-
#
|
|
164
146
|
# @option options :include_blank [Boolean]
|
|
165
147
|
# Specify if a `:select` input should include a blank option or not (defaults to `include_blank_for_select_by_default` configuration)
|
|
166
148
|
#
|
|
167
149
|
# @option options :prompt [String]
|
|
168
150
|
# Specify the text in the first ('blank') `:select` input `<option>` to prompt a user to make a selection (implicitly sets `:include_blank` to `true`)
|
|
169
151
|
#
|
|
170
|
-
# @todo Can we kill `:
|
|
171
|
-
# @todo Can we kill `:group_by` & `:group_label`? Should be done with :collection => grouped_options_for_select(...)
|
|
172
|
-
# @todo Can we kill `:find_options`? Should be done with MyModel.some_scope.where(...).order(...).whatever_scope
|
|
173
|
-
# @todo Can we kill `:label`, `:hint` & `:prompt`? All strings could be shifted to i18n!
|
|
152
|
+
# @todo Can we deprecate & kill `:label`, `:hint` & `:prompt`? All strings could be shifted to i18n!
|
|
174
153
|
#
|
|
175
154
|
# @example Accept all default options
|
|
176
155
|
# <%= f.input :title %>
|
|
@@ -247,13 +226,15 @@ module Formtastic
|
|
|
247
226
|
# first_name: "Joe"
|
|
248
227
|
# last_name: "Smith"
|
|
249
228
|
#
|
|
229
|
+
# @see #namespaced_input_class
|
|
250
230
|
# @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
|
|
251
231
|
# @todo More i18n examples.
|
|
252
232
|
def input(method, options = {})
|
|
233
|
+
method = method.to_sym
|
|
253
234
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
|
254
235
|
options[:as] ||= default_input_type(method, options)
|
|
255
236
|
|
|
256
|
-
klass =
|
|
237
|
+
klass = namespaced_input_class(options[:as])
|
|
257
238
|
|
|
258
239
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
|
259
240
|
end
|
|
@@ -270,14 +251,15 @@ module Formtastic
|
|
|
270
251
|
#
|
|
271
252
|
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
|
272
253
|
# default is a :string, a similar behaviour to Rails' scaffolding.
|
|
273
|
-
def default_input_type(method, options = {})
|
|
254
|
+
def default_input_type(method, options = {}) # @private
|
|
274
255
|
if @object
|
|
275
256
|
return :select if reflection_for(method)
|
|
276
257
|
|
|
277
258
|
return :file if is_file?(method, options)
|
|
278
259
|
end
|
|
279
260
|
|
|
280
|
-
|
|
261
|
+
column = column_for(method)
|
|
262
|
+
if column && column.type
|
|
281
263
|
# Special cases where the column type doesn't map to an input method.
|
|
282
264
|
case column.type
|
|
283
265
|
when :string
|
|
@@ -288,13 +270,23 @@ module Formtastic
|
|
|
288
270
|
return :url if method.to_s =~ /^url$|^website$|_url$/
|
|
289
271
|
return :phone if method.to_s =~ /(phone|fax)/
|
|
290
272
|
return :search if method.to_s =~ /^search$/
|
|
273
|
+
return :color if method.to_s =~ /color/
|
|
291
274
|
when :integer
|
|
292
275
|
return :select if reflection_for(method)
|
|
276
|
+
return :select if enum_for(method)
|
|
293
277
|
return :number
|
|
294
278
|
when :float, :decimal
|
|
295
279
|
return :number
|
|
296
|
-
when :timestamp
|
|
297
|
-
return :
|
|
280
|
+
when :datetime, :timestamp
|
|
281
|
+
return :datetime_select
|
|
282
|
+
when :time
|
|
283
|
+
return :time_select
|
|
284
|
+
when :date
|
|
285
|
+
return :date_select
|
|
286
|
+
when :hstore, :json, :jsonb
|
|
287
|
+
return :text
|
|
288
|
+
when :citext, :inet
|
|
289
|
+
return :string
|
|
298
290
|
end
|
|
299
291
|
|
|
300
292
|
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
|
@@ -309,16 +301,30 @@ module Formtastic
|
|
|
309
301
|
end
|
|
310
302
|
|
|
311
303
|
# Get a column object for a specified attribute method - if possible.
|
|
312
|
-
|
|
313
|
-
|
|
304
|
+
# @return [ActiveModel::Type::Value, #type] in case of rails 5 attributes api
|
|
305
|
+
# @return [ActiveRecord::ConnectionAdapters::Column] in case of rails 4
|
|
306
|
+
def column_for(method) # @private
|
|
307
|
+
case
|
|
308
|
+
when @object.class.respond_to?(:type_for_attribute)
|
|
309
|
+
@object.class.type_for_attribute(method.to_s)
|
|
310
|
+
when @object.class.respond_to?(:column_for_attribute)
|
|
311
|
+
@object.class.column_for_attribute(method)
|
|
312
|
+
when @object.respond_to?(:column_for_attribute)
|
|
313
|
+
# Remove deprecation wrapper & review after Rails 5.0 ships
|
|
314
|
+
ActiveSupport::Deprecation.silence do
|
|
315
|
+
@object.column_for_attribute(method)
|
|
316
|
+
end
|
|
317
|
+
else nil
|
|
318
|
+
end
|
|
314
319
|
end
|
|
315
320
|
|
|
316
|
-
# Takes the `:as` option and attempts to return the corresponding input
|
|
317
|
-
# `:as => :
|
|
318
|
-
#
|
|
321
|
+
# Takes the `:as` option and attempts to return the corresponding input
|
|
322
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
|
323
|
+
# top level `AwesomeInput` class (to allow the application to subclass
|
|
324
|
+
# and modify to suit), falling back to `Formtastic::Inputs::AwesomeInput`.
|
|
319
325
|
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
326
|
+
# Custom input namespaces to look into can be configured via the
|
|
327
|
+
# {Formtastic::FormBuilder.input_namespaces} configuration setting.
|
|
322
328
|
#
|
|
323
329
|
# @param [Symbol] as A symbol representing the type of input to render
|
|
324
330
|
# @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
|
|
@@ -330,48 +336,14 @@ module Formtastic
|
|
|
330
336
|
#
|
|
331
337
|
# @example When a top-level class is found
|
|
332
338
|
# input_class(:string) #=> StringInput
|
|
333
|
-
# input_class(:awesome) #=> AwesomeInput
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
@
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
# prevent exceptions in production environment for better performance
|
|
342
|
-
def input_class_with_const_defined(as)
|
|
343
|
-
input_class_name = custom_input_class_name(as)
|
|
344
|
-
|
|
345
|
-
if ::Object.const_defined?(input_class_name)
|
|
346
|
-
input_class_name.constantize
|
|
347
|
-
elsif Formtastic::Inputs.const_defined?(input_class_name)
|
|
348
|
-
standard_input_class_name(as).constantize
|
|
349
|
-
else
|
|
350
|
-
raise Formtastic::UnknownInputError
|
|
351
|
-
end
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# use auto-loading in development environment
|
|
355
|
-
def input_class_by_trying(as)
|
|
356
|
-
begin
|
|
357
|
-
custom_input_class_name(as).constantize
|
|
358
|
-
rescue NameError
|
|
359
|
-
standard_input_class_name(as).constantize
|
|
360
|
-
end
|
|
361
|
-
rescue NameError
|
|
362
|
-
raise Formtastic::UnknownInputError
|
|
339
|
+
# input_class(:awesome) #=> AwesomeInput
|
|
340
|
+
# @see NamespacedClassFinder#find
|
|
341
|
+
def namespaced_input_class(as)
|
|
342
|
+
@input_class_finder ||= input_class_finder.new(self)
|
|
343
|
+
@input_class_finder.find(as)
|
|
344
|
+
rescue Formtastic::InputClassFinder::NotFoundError
|
|
345
|
+
raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}"
|
|
363
346
|
end
|
|
364
|
-
|
|
365
|
-
# :as => :string # => StringInput
|
|
366
|
-
def custom_input_class_name(as)
|
|
367
|
-
"#{as.to_s.camelize}Input"
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# :as => :string # => Formtastic::Inputs::StringInput
|
|
371
|
-
def standard_input_class_name(as)
|
|
372
|
-
"Formtastic::Inputs::#{as.to_s.camelize}Input"
|
|
373
|
-
end
|
|
374
|
-
|
|
375
347
|
end
|
|
376
348
|
end
|
|
377
349
|
end
|