formtastic 3.0.0 → 5.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 +5 -13
- data/.gitattributes +1 -0
- data/.github/workflows/test.yml +61 -0
- data/.gitignore +3 -2
- data/CHANGELOG.md +61 -0
- data/Gemfile.lock +140 -0
- data/MIT-LICENSE +1 -1
- data/{README.textile → README.md} +191 -168
- data/RELEASE_PROCESS +3 -1
- data/Rakefile +24 -8
- data/app/assets/stylesheets/formtastic.css +1 -1
- data/bin/appraisal +8 -0
- data/formtastic.gemspec +13 -17
- data/gemfiles/rails_6.0/Gemfile +5 -0
- data/gemfiles/rails_6.1/Gemfile +5 -0
- data/gemfiles/rails_7.0/Gemfile +5 -0
- data/gemfiles/rails_7.1/Gemfile +5 -0
- data/gemfiles/rails_edge/Gemfile +13 -0
- data/lib/formtastic/action_class_finder.rb +19 -0
- data/lib/formtastic/actions/base.rb +1 -0
- data/lib/formtastic/actions/button_action.rb +56 -53
- data/lib/formtastic/actions/buttonish.rb +1 -0
- data/lib/formtastic/actions/input_action.rb +60 -57
- data/lib/formtastic/actions/link_action.rb +69 -67
- data/lib/formtastic/actions.rb +7 -3
- data/lib/formtastic/deprecation.rb +6 -0
- data/lib/formtastic/engine.rb +4 -1
- data/lib/formtastic/form_builder.rb +32 -25
- data/lib/formtastic/helpers/action_helper.rb +22 -31
- data/lib/formtastic/helpers/actions_helper.rb +1 -0
- data/lib/formtastic/helpers/enum.rb +14 -0
- data/lib/formtastic/helpers/errors_helper.rb +3 -2
- data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -11
- data/lib/formtastic/helpers/file_column_detection.rb +1 -0
- data/lib/formtastic/helpers/form_helper.rb +4 -3
- data/lib/formtastic/helpers/input_helper.rb +59 -80
- data/lib/formtastic/helpers/inputs_helper.rb +33 -27
- data/lib/formtastic/helpers/reflection.rb +5 -4
- data/lib/formtastic/helpers.rb +2 -2
- data/lib/formtastic/html_attributes.rb +13 -1
- data/lib/formtastic/i18n.rb +2 -1
- data/lib/formtastic/input_class_finder.rb +19 -0
- data/lib/formtastic/inputs/base/associations.rb +1 -0
- data/lib/formtastic/inputs/base/choices.rb +4 -3
- data/lib/formtastic/inputs/base/collections.rb +47 -11
- data/lib/formtastic/inputs/base/database.rb +8 -5
- data/lib/formtastic/inputs/base/datetime_pickerish.rb +1 -0
- data/lib/formtastic/inputs/base/errors.rb +7 -6
- data/lib/formtastic/inputs/base/fileish.rb +1 -0
- data/lib/formtastic/inputs/base/hints.rb +2 -1
- data/lib/formtastic/inputs/base/html.rb +12 -10
- data/lib/formtastic/inputs/base/labelling.rb +3 -2
- data/lib/formtastic/inputs/base/naming.rb +5 -4
- data/lib/formtastic/inputs/base/numeric.rb +1 -0
- data/lib/formtastic/inputs/base/options.rb +3 -3
- data/lib/formtastic/inputs/base/placeholder.rb +1 -0
- data/lib/formtastic/inputs/base/stringish.rb +1 -0
- data/lib/formtastic/inputs/base/timeish.rb +9 -4
- data/lib/formtastic/inputs/base/validations.rb +39 -12
- data/lib/formtastic/inputs/base/wrapping.rb +2 -3
- data/lib/formtastic/inputs/base.rb +17 -12
- data/lib/formtastic/inputs/boolean_input.rb +2 -1
- data/lib/formtastic/inputs/check_boxes_input.rb +16 -24
- data/lib/formtastic/inputs/color_input.rb +1 -1
- data/lib/formtastic/inputs/country_input.rb +4 -1
- data/lib/formtastic/inputs/datalist_input.rb +42 -0
- data/lib/formtastic/inputs/date_picker_input.rb +1 -0
- data/lib/formtastic/inputs/date_select_input.rb +1 -0
- data/lib/formtastic/inputs/datetime_picker_input.rb +1 -0
- data/lib/formtastic/inputs/datetime_select_input.rb +1 -0
- data/lib/formtastic/inputs/email_input.rb +1 -0
- data/lib/formtastic/inputs/file_input.rb +3 -2
- data/lib/formtastic/inputs/hidden_input.rb +3 -2
- data/lib/formtastic/inputs/number_input.rb +1 -0
- data/lib/formtastic/inputs/password_input.rb +1 -0
- data/lib/formtastic/inputs/phone_input.rb +1 -0
- data/lib/formtastic/inputs/radio_input.rb +26 -21
- data/lib/formtastic/inputs/range_input.rb +1 -0
- data/lib/formtastic/inputs/search_input.rb +1 -0
- data/lib/formtastic/inputs/select_input.rb +32 -10
- data/lib/formtastic/inputs/string_input.rb +1 -0
- data/lib/formtastic/inputs/text_input.rb +1 -0
- data/lib/formtastic/inputs/time_picker_input.rb +1 -0
- data/lib/formtastic/inputs/time_select_input.rb +1 -0
- data/lib/formtastic/inputs/time_zone_input.rb +17 -6
- data/lib/formtastic/inputs/url_input.rb +1 -0
- data/lib/formtastic/inputs.rb +33 -28
- data/lib/formtastic/localized_string.rb +2 -1
- data/lib/formtastic/localizer.rb +23 -24
- data/lib/formtastic/namespaced_class_finder.rb +98 -0
- data/lib/formtastic/version.rb +2 -1
- data/lib/formtastic.rb +19 -14
- data/lib/generators/formtastic/form/form_generator.rb +8 -2
- data/lib/generators/formtastic/input/input_generator.rb +47 -0
- data/lib/generators/formtastic/install/install_generator.rb +2 -0
- data/lib/generators/templates/formtastic.rb +29 -7
- data/lib/generators/templates/input.rb +19 -0
- data/sample/basic_inputs.html +1 -1
- data/script/integration-template.rb +73 -0
- data/script/integration.sh +19 -0
- data/spec/action_class_finder_spec.rb +13 -0
- data/spec/actions/button_action_spec.rb +21 -20
- data/spec/actions/generic_action_spec.rb +134 -133
- data/spec/actions/input_action_spec.rb +20 -19
- data/spec/actions/link_action_spec.rb +30 -29
- data/spec/builder/custom_builder_spec.rb +39 -22
- data/spec/builder/error_proc_spec.rb +6 -5
- data/spec/builder/semantic_fields_for_spec.rb +46 -45
- data/spec/fast_spec_helper.rb +13 -0
- data/spec/generators/formtastic/form/form_generator_spec.rb +33 -32
- data/spec/generators/formtastic/input/input_generator_spec.rb +125 -0
- data/spec/generators/formtastic/install/install_generator_spec.rb +10 -9
- data/spec/helpers/action_helper_spec.rb +70 -97
- data/spec/helpers/actions_helper_spec.rb +43 -42
- data/spec/helpers/form_helper_spec.rb +56 -39
- data/spec/helpers/input_helper_spec.rb +314 -255
- data/spec/helpers/inputs_helper_spec.rb +217 -202
- data/spec/helpers/reflection_helper_spec.rb +7 -6
- data/spec/helpers/semantic_errors_helper_spec.rb +26 -25
- data/spec/i18n_spec.rb +30 -29
- data/spec/input_class_finder_spec.rb +11 -0
- data/spec/inputs/base/collections_spec.rb +78 -0
- data/spec/inputs/base/validations_spec.rb +481 -0
- data/spec/inputs/boolean_input_spec.rb +73 -72
- data/spec/inputs/check_boxes_input_spec.rb +174 -123
- data/spec/inputs/color_input_spec.rb +53 -64
- data/spec/inputs/country_input_spec.rb +23 -22
- data/spec/inputs/custom_input_spec.rb +3 -6
- data/spec/inputs/datalist_input_spec.rb +62 -0
- data/spec/inputs/date_picker_input_spec.rb +114 -113
- data/spec/inputs/date_select_input_spec.rb +76 -61
- data/spec/inputs/datetime_picker_input_spec.rb +123 -122
- data/spec/inputs/datetime_select_input_spec.rb +85 -68
- data/spec/inputs/email_input_spec.rb +17 -16
- data/spec/inputs/file_input_spec.rb +18 -17
- data/spec/inputs/hidden_input_spec.rb +32 -31
- data/spec/inputs/include_blank_spec.rb +10 -9
- data/spec/inputs/label_spec.rb +36 -31
- data/spec/inputs/number_input_spec.rb +212 -211
- data/spec/inputs/password_input_spec.rb +17 -16
- data/spec/inputs/phone_input_spec.rb +17 -16
- data/spec/inputs/placeholder_spec.rb +18 -17
- data/spec/inputs/radio_input_spec.rb +92 -65
- data/spec/inputs/range_input_spec.rb +136 -135
- data/spec/inputs/readonly_spec.rb +51 -0
- data/spec/inputs/search_input_spec.rb +16 -15
- data/spec/inputs/select_input_spec.rb +209 -102
- data/spec/inputs/string_input_spec.rb +51 -50
- data/spec/inputs/text_input_spec.rb +34 -33
- data/spec/inputs/time_picker_input_spec.rb +115 -114
- data/spec/inputs/time_select_input_spec.rb +84 -70
- data/spec/inputs/time_zone_input_spec.rb +58 -31
- data/spec/inputs/url_input_spec.rb +17 -16
- data/spec/inputs/with_options_spec.rb +9 -8
- data/spec/localizer_spec.rb +18 -17
- data/spec/namespaced_class_finder_spec.rb +91 -0
- data/spec/schema.rb +22 -0
- data/spec/spec_helper.rb +180 -249
- data/spec/support/custom_macros.rb +128 -98
- data/spec/support/deprecation.rb +2 -1
- data/spec/support/shared_examples.rb +13 -0
- data/spec/support/specialized_class_finder_shared_example.rb +28 -0
- data/spec/support/test_environment.rb +25 -10
- metadata +95 -136
- data/.travis.yml +0 -28
- data/Appraisals +0 -25
- data/CHANGELOG +0 -27
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_4.0.4.gemfile +0 -7
- data/gemfiles/rails_4.1.gemfile +0 -7
- data/gemfiles/rails_4.gemfile +0 -7
- data/gemfiles/rails_edge.gemfile +0 -10
- data/lib/formtastic/util.rb +0 -53
- data/spec/support/deferred_garbage_collection.rb +0 -21
- data/spec/util_spec.rb +0 -52
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Formtastic
|
|
2
3
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
# Defines a new configurable option
|
|
6
|
+
# @param [Symbol] name the configuration name
|
|
7
|
+
# @param [Object] default the configuration default value
|
|
8
|
+
# @private
|
|
9
|
+
#
|
|
10
|
+
# @!macro [new] configure
|
|
11
|
+
# @!scope class
|
|
12
|
+
# @!attribute [rw] $1
|
|
13
|
+
# @api public
|
|
14
|
+
def self.configure(name, default = nil)
|
|
5
15
|
class_attribute(name)
|
|
6
|
-
self.send(:"#{name}=",
|
|
16
|
+
self.send(:"#{name}=", default)
|
|
7
17
|
end
|
|
8
18
|
|
|
9
19
|
configure :custom_namespace
|
|
@@ -12,7 +22,7 @@ module Formtastic
|
|
|
12
22
|
configure :default_text_area_width
|
|
13
23
|
configure :all_fields_required_by_default, true
|
|
14
24
|
configure :include_blank_for_select_by_default, true
|
|
15
|
-
configure :required_string, proc {
|
|
25
|
+
configure :required_string, proc { %{<abbr title="#{Formtastic::I18n.t(:required)}">*</abbr>}.html_safe }
|
|
16
26
|
configure :optional_string, ''
|
|
17
27
|
configure :inline_errors, :sentence
|
|
18
28
|
configure :label_str_method, :humanize
|
|
@@ -31,6 +41,15 @@ module Formtastic
|
|
|
31
41
|
configure :default_hint_class, 'inline-hints'
|
|
32
42
|
configure :use_required_attribute, false
|
|
33
43
|
configure :perform_browser_validations, false
|
|
44
|
+
# Check {Formtastic::InputClassFinder} to see how are inputs resolved.
|
|
45
|
+
configure :input_namespaces, [::Object, ::Formtastic::Inputs]
|
|
46
|
+
configure :input_class_finder, Formtastic::InputClassFinder
|
|
47
|
+
# Check {Formtastic::ActionClassFinder} to see how are inputs resolved.
|
|
48
|
+
configure :action_namespaces, [::Object, ::Formtastic::Actions]
|
|
49
|
+
configure :action_class_finder, Formtastic::ActionClassFinder
|
|
50
|
+
|
|
51
|
+
configure :skipped_columns, [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
|
52
|
+
configure :priority_time_zones, []
|
|
34
53
|
|
|
35
54
|
attr_reader :template
|
|
36
55
|
|
|
@@ -43,11 +62,11 @@ module Formtastic
|
|
|
43
62
|
include Formtastic::Helpers::ActionHelper
|
|
44
63
|
include Formtastic::Helpers::ActionsHelper
|
|
45
64
|
include Formtastic::Helpers::ErrorsHelper
|
|
46
|
-
|
|
47
|
-
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
|
65
|
+
|
|
66
|
+
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
|
48
67
|
# provided to ensure that the `:builder` from `semantic_form_for` was passed down into
|
|
49
|
-
# the nested `fields_for`. Rails
|
|
50
|
-
# provided purely for backwards compatibility and DSL consistency.
|
|
68
|
+
# the nested `fields_for`. Our supported versions of Rails no longer require us to do this,
|
|
69
|
+
# so this method is provided purely for backwards compatibility and DSL consistency.
|
|
51
70
|
#
|
|
52
71
|
# When constructing a `fields_for` form fragment *outside* of `semantic_form_for`, please use
|
|
53
72
|
# `Formtastic::Helpers::FormHelper#semantic_fields_for`.
|
|
@@ -73,29 +92,17 @@ module Formtastic
|
|
|
73
92
|
#
|
|
74
93
|
# @todo is there a way to test the params structure of the Rails helper we wrap to ensure forward compatibility?
|
|
75
94
|
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
|
76
|
-
|
|
77
|
-
options = args.extract_options!
|
|
78
|
-
options[:parent_builder] ||= self
|
|
79
|
-
|
|
80
|
-
# Wrap the Rails helper
|
|
81
|
-
fields_for(record_or_name_or_array, *(args << options), &block)
|
|
95
|
+
fields_for(record_or_name_or_array, *args, &block)
|
|
82
96
|
end
|
|
83
|
-
|
|
84
|
-
def initialize(object_name, object, template, options
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# rails 4.1 removes the parameter completely
|
|
88
|
-
if Util.rails3? || Util.rails4_0?
|
|
89
|
-
super
|
|
90
|
-
else # Must be rails4_1 or greater
|
|
91
|
-
super object_name, object, template, options
|
|
92
|
-
end
|
|
93
|
-
|
|
97
|
+
|
|
98
|
+
def initialize(object_name, object, template, options)
|
|
99
|
+
super
|
|
100
|
+
|
|
94
101
|
if respond_to?('multipart=') && options.is_a?(Hash) && options[:html]
|
|
95
102
|
self.multipart = options[:html][:multipart]
|
|
96
103
|
end
|
|
97
104
|
end
|
|
98
|
-
|
|
105
|
+
|
|
99
106
|
end
|
|
100
107
|
|
|
101
108
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
module Formtastic
|
|
3
4
|
module Helpers
|
|
4
5
|
module ActionHelper
|
|
5
|
-
|
|
6
6
|
# Renders an action for the form (such as a subit/reset button, or a cancel link).
|
|
7
7
|
#
|
|
8
8
|
# Each action is wrapped in an `<li class="action">` tag with other classes added based on the
|
|
@@ -23,9 +23,9 @@ module Formtastic
|
|
|
23
23
|
# reset: "Reset form"
|
|
24
24
|
# submit: "Submit"
|
|
25
25
|
#
|
|
26
|
-
# For forms with an object present, the `update` key will be used if calling `persisted?` on
|
|
27
|
-
# the object returns true (saving changes to a record), otherwise the `create`
|
|
28
|
-
# used. The `submit` key is used as a fallback when there is no object or we cannot determine
|
|
26
|
+
# For forms with an object present, the `update` key will be used if calling `persisted?` on
|
|
27
|
+
# the object returns true (saving changes to a record), otherwise the `create` key will be
|
|
28
|
+
# used. The `submit` key is used as a fallback when there is no object or we cannot determine
|
|
29
29
|
# if `create` or `update` is appropriate.
|
|
30
30
|
#
|
|
31
31
|
# @example Basic usage
|
|
@@ -80,46 +80,37 @@ module Formtastic
|
|
|
80
80
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
|
81
81
|
options[:as] ||= default_action_type(method, options)
|
|
82
82
|
|
|
83
|
-
klass =
|
|
83
|
+
klass = namespaced_action_class(options[:as])
|
|
84
84
|
|
|
85
85
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
protected
|
|
89
89
|
|
|
90
|
-
def default_action_type(method, options = {})
|
|
90
|
+
def default_action_type(method, options = {}) # @private
|
|
91
91
|
case method
|
|
92
92
|
when :submit then :input
|
|
93
|
-
when :reset
|
|
93
|
+
when :reset then :input
|
|
94
94
|
when :cancel then :link
|
|
95
|
+
else method
|
|
95
96
|
end
|
|
96
97
|
end
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# :as => :button # => ButtonAction
|
|
114
|
-
def custom_action_class_name(as)
|
|
115
|
-
"#{as.to_s.camelize}Action"
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# :as => :button # => Formtastic::Actions::ButtonAction
|
|
119
|
-
def standard_action_class_name(as)
|
|
120
|
-
"Formtastic::Actions::#{as.to_s.camelize}Action"
|
|
99
|
+
# Takes the `:as` option and attempts to return the corresponding action
|
|
100
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
|
101
|
+
# top level `AwesomeAction` class (to allow the application to subclass
|
|
102
|
+
# and modify to suit), falling back to `Formtastic::Actions::AwesomeAction`.
|
|
103
|
+
#
|
|
104
|
+
# Custom action namespaces to look into can be configured via the
|
|
105
|
+
# {Formtastic::FormBuilder.action_namespaces} configuration setting.
|
|
106
|
+
# @see Helpers::InputHelper#namespaced_input_class
|
|
107
|
+
# @see Formtastic::ActionClassFinder
|
|
108
|
+
def namespaced_action_class(as)
|
|
109
|
+
@action_class_finder ||= action_class_finder.new(self)
|
|
110
|
+
@action_class_finder.find(as)
|
|
111
|
+
rescue Formtastic::ActionClassFinder::NotFoundError => e
|
|
112
|
+
raise Formtastic::UnknownActionError, "Unable to find action #{e.message}"
|
|
121
113
|
end
|
|
122
|
-
|
|
123
114
|
end
|
|
124
115
|
end
|
|
125
116
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Formtastic
|
|
3
|
+
module Helpers
|
|
4
|
+
# @private
|
|
5
|
+
module Enum
|
|
6
|
+
# Returns the enum (if defined) for the given method
|
|
7
|
+
def enum_for(method) # @private
|
|
8
|
+
if @object.respond_to?(:defined_enums)
|
|
9
|
+
@object.defined_enums[method.to_s]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Formtastic
|
|
2
3
|
module Helpers
|
|
3
4
|
module ErrorsHelper
|
|
@@ -52,7 +53,7 @@ module Formtastic
|
|
|
52
53
|
return nil if full_errors.blank?
|
|
53
54
|
html_options[:class] ||= "errors"
|
|
54
55
|
template.content_tag(:ul, html_options) do
|
|
55
|
-
|
|
56
|
+
full_errors.map { |error| template.content_tag(:li, error) }.join.html_safe
|
|
56
57
|
end
|
|
57
58
|
end
|
|
58
59
|
|
|
@@ -78,4 +79,4 @@ module Formtastic
|
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
|
-
end
|
|
82
|
+
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Formtastic
|
|
2
3
|
module Helpers
|
|
3
4
|
# @private
|
|
@@ -21,8 +22,8 @@ module Formtastic
|
|
|
21
22
|
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
|
22
23
|
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
|
23
24
|
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
|
24
|
-
def field_set_and_list_wrapping(*args, &block)
|
|
25
|
-
contents = args.
|
|
25
|
+
def field_set_and_list_wrapping(*args, &block) # @private
|
|
26
|
+
contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
|
|
26
27
|
html_options = args.extract_options!
|
|
27
28
|
|
|
28
29
|
if block_given?
|
|
@@ -33,12 +34,15 @@ module Formtastic
|
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# Work-around for empty contents block
|
|
38
|
+
contents ||= ""
|
|
39
|
+
|
|
36
40
|
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
|
37
41
|
contents = contents.join if contents.respond_to?(:join)
|
|
38
42
|
|
|
39
43
|
legend = field_set_legend(html_options)
|
|
40
44
|
fieldset = template.content_tag(:fieldset,
|
|
41
|
-
|
|
45
|
+
legend.html_safe << template.content_tag(:ol, contents.html_safe),
|
|
42
46
|
html_options.except(:builder, :parent, :name)
|
|
43
47
|
)
|
|
44
48
|
|
|
@@ -47,30 +51,31 @@ module Formtastic
|
|
|
47
51
|
|
|
48
52
|
def field_set_legend(html_options)
|
|
49
53
|
legend = (html_options[:name] || '').to_s
|
|
50
|
-
|
|
51
|
-
legend
|
|
54
|
+
# only applying if String includes '%i' avoids argument error when $DEBUG is true
|
|
55
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i')
|
|
56
|
+
legend = template.content_tag(:legend, template.content_tag(:span, legend.html_safe)) unless legend.blank?
|
|
52
57
|
legend
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
# Gets the nested_child_index value from the parent builder. It returns a hash with each
|
|
56
61
|
# association that the parent builds.
|
|
57
|
-
def parent_child_index(parent)
|
|
62
|
+
def parent_child_index(parent) # @private
|
|
58
63
|
# Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
|
|
59
64
|
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
|
60
|
-
|
|
65
|
+
|
|
61
66
|
# Could be symbol for the association, or a model (or an array of either, I think? TODO)
|
|
62
67
|
child = parent[:for]
|
|
63
68
|
# Pull a sybol or model out of Array (TODO: check if there's an Array)
|
|
64
69
|
child = child.first if child.respond_to?(:first)
|
|
65
70
|
# If it's an object, get a symbol from the class name
|
|
66
71
|
child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
|
|
67
|
-
|
|
72
|
+
|
|
68
73
|
key = "#{parent[:builder].object_name}[#{child}_attributes]"
|
|
69
74
|
|
|
70
|
-
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
|
75
|
+
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
|
71
76
|
# 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
|
-
|
|
77
|
+
return duck + 1 if duck.is_a?(Integer)
|
|
78
|
+
|
|
74
79
|
# First try to extract key from duck Hash, then try child
|
|
75
80
|
(duck[key] || duck[child]).to_i + 1
|
|
76
81
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Formtastic
|
|
2
3
|
module Helpers
|
|
3
4
|
|
|
@@ -155,7 +156,7 @@ module Formtastic
|
|
|
155
156
|
options[:builder] ||= @@builder
|
|
156
157
|
options[:html] ||= {}
|
|
157
158
|
options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
|
|
158
|
-
|
|
159
|
+
options[:custom_namespace] = options.delete(:namespace)
|
|
159
160
|
|
|
160
161
|
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
|
161
162
|
|
|
@@ -163,7 +164,7 @@ module Formtastic
|
|
|
163
164
|
class_names << @@default_form_class
|
|
164
165
|
model_class_name = case record_or_name_or_array
|
|
165
166
|
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
|
166
|
-
when Array then options[:as] || singularizer.call(record_or_name_or_array.
|
|
167
|
+
when Array then options[:as] || singularizer.call(record_or_name_or_array[-1].class) # [@post, @comment] # => "comment"
|
|
167
168
|
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
|
168
169
|
end
|
|
169
170
|
class_names << @@default_form_model_class_proc.call(model_class_name)
|
|
@@ -182,7 +183,7 @@ module Formtastic
|
|
|
182
183
|
def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
|
|
183
184
|
options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
|
|
184
185
|
options[:builder] ||= @@builder
|
|
185
|
-
|
|
186
|
+
options[:custom_namespace] = options.delete(:namespace)
|
|
186
187
|
|
|
187
188
|
with_custom_field_error_proc do
|
|
188
189
|
self.fields_for(record_name, record_object, options, &block)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
module Formtastic
|
|
3
4
|
module Helpers
|
|
4
5
|
|
|
@@ -37,6 +38,7 @@ module Formtastic
|
|
|
37
38
|
# @see Formtastic::Helpers::FormHelper#semantic_form_for
|
|
38
39
|
module InputHelper
|
|
39
40
|
include Formtastic::Helpers::Reflection
|
|
41
|
+
include Formtastic::Helpers::Enum
|
|
40
42
|
include Formtastic::Helpers::FileColumnDetection
|
|
41
43
|
|
|
42
44
|
# Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
|
|
@@ -86,26 +88,26 @@ module Formtastic
|
|
|
86
88
|
#
|
|
87
89
|
# Available input styles:
|
|
88
90
|
#
|
|
89
|
-
# * `:boolean`
|
|
90
|
-
# * `:check_boxes`
|
|
91
|
-
# * `:color`
|
|
92
|
-
# * `:country`
|
|
93
|
-
# * `:datetime_select`
|
|
94
|
-
# * `:date_select`
|
|
95
|
-
# * `:email`
|
|
96
|
-
# * `:file`
|
|
97
|
-
# * `:hidden`
|
|
98
|
-
# * `:number`
|
|
99
|
-
# * `:password`
|
|
100
|
-
# * `:phone`
|
|
101
|
-
# * `:radio`
|
|
102
|
-
# * `:search`
|
|
103
|
-
# * `:select`
|
|
104
|
-
# * `:string`
|
|
105
|
-
# * `:text`
|
|
106
|
-
# * `:time_zone`
|
|
107
|
-
# * `:time_select`
|
|
108
|
-
# * `:url`
|
|
91
|
+
# * `:boolean` (see {Inputs::BooleanInput})
|
|
92
|
+
# * `:check_boxes` (see {Inputs::CheckBoxesInput})
|
|
93
|
+
# * `:color` (see {Inputs::ColorInput})
|
|
94
|
+
# * `:country` (see {Inputs::CountryInput})
|
|
95
|
+
# * `:datetime_select` (see {Inputs::DatetimeSelectInput})
|
|
96
|
+
# * `:date_select` (see {Inputs::DateSelectInput})
|
|
97
|
+
# * `:email` (see {Inputs::EmailInput})
|
|
98
|
+
# * `:file` (see {Inputs::FileInput})
|
|
99
|
+
# * `:hidden` (see {Inputs::HiddenInput})
|
|
100
|
+
# * `:number` (see {Inputs::NumberInput})
|
|
101
|
+
# * `:password` (see {Inputs::PasswordInput})
|
|
102
|
+
# * `:phone` (see {Inputs::PhoneInput})
|
|
103
|
+
# * `:radio` (see {Inputs::RadioInput})
|
|
104
|
+
# * `:search` (see {Inputs::SearchInput})
|
|
105
|
+
# * `:select` (see {Inputs::SelectInput})
|
|
106
|
+
# * `:string` (see {Inputs::StringInput})
|
|
107
|
+
# * `:text` (see {Inputs::TextInput})
|
|
108
|
+
# * `:time_zone` (see {Inputs::TimeZoneInput})
|
|
109
|
+
# * `:time_select` (see {Inputs::TimeSelectInput})
|
|
110
|
+
# * `:url` (see {Inputs::UrlInput})
|
|
109
111
|
#
|
|
110
112
|
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
|
|
111
113
|
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
|
|
@@ -131,6 +133,7 @@ module Formtastic
|
|
|
131
133
|
#
|
|
132
134
|
# @option options :input_html [Hash]
|
|
133
135
|
# Override or add to the HTML attributes to be passed down to the `<input>` tag
|
|
136
|
+
# (If you use attr_readonly method in your model, formtastic will automatically set those attributes's input readonly)
|
|
134
137
|
#
|
|
135
138
|
# @option options :wrapper_html [Hash]
|
|
136
139
|
# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
|
|
@@ -138,12 +141,6 @@ module Formtastic
|
|
|
138
141
|
# @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
|
|
139
142
|
# Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
|
|
140
143
|
#
|
|
141
|
-
# @option options :member_label [Symbol, Proc, Method]
|
|
142
|
-
# 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)
|
|
143
|
-
#
|
|
144
|
-
# @option options :member_value [Symbol, Proc, Method]
|
|
145
|
-
# 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)
|
|
146
|
-
#
|
|
147
144
|
# @option options :multiple [Boolean]
|
|
148
145
|
# 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)
|
|
149
146
|
#
|
|
@@ -230,14 +227,15 @@ module Formtastic
|
|
|
230
227
|
# first_name: "Joe"
|
|
231
228
|
# last_name: "Smith"
|
|
232
229
|
#
|
|
230
|
+
# @see #namespaced_input_class
|
|
233
231
|
# @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
|
|
234
232
|
# @todo More i18n examples.
|
|
235
233
|
def input(method, options = {})
|
|
236
|
-
method = method.to_sym
|
|
234
|
+
method = method.to_sym
|
|
237
235
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
|
238
236
|
options[:as] ||= default_input_type(method, options)
|
|
239
237
|
|
|
240
|
-
klass =
|
|
238
|
+
klass = namespaced_input_class(options[:as])
|
|
241
239
|
|
|
242
240
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
|
243
241
|
end
|
|
@@ -254,14 +252,15 @@ module Formtastic
|
|
|
254
252
|
#
|
|
255
253
|
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
|
256
254
|
# default is a :string, a similar behaviour to Rails' scaffolding.
|
|
257
|
-
def default_input_type(method, options = {})
|
|
255
|
+
def default_input_type(method, options = {}) # @private
|
|
258
256
|
if @object
|
|
259
257
|
return :select if reflection_for(method)
|
|
260
258
|
|
|
261
259
|
return :file if is_file?(method, options)
|
|
262
260
|
end
|
|
263
261
|
|
|
264
|
-
|
|
262
|
+
column = column_for(method)
|
|
263
|
+
if column && column.type
|
|
265
264
|
# Special cases where the column type doesn't map to an input method.
|
|
266
265
|
case column.type
|
|
267
266
|
when :string
|
|
@@ -275,6 +274,7 @@ module Formtastic
|
|
|
275
274
|
return :color if method.to_s =~ /color/
|
|
276
275
|
when :integer
|
|
277
276
|
return :select if reflection_for(method)
|
|
277
|
+
return :select if enum_for(method)
|
|
278
278
|
return :number
|
|
279
279
|
when :float, :decimal
|
|
280
280
|
return :number
|
|
@@ -284,6 +284,10 @@ module Formtastic
|
|
|
284
284
|
return :time_select
|
|
285
285
|
when :date
|
|
286
286
|
return :date_select
|
|
287
|
+
when :hstore, :json, :jsonb
|
|
288
|
+
return :text
|
|
289
|
+
when :citext, :inet
|
|
290
|
+
return :string
|
|
287
291
|
end
|
|
288
292
|
|
|
289
293
|
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
|
@@ -298,16 +302,27 @@ module Formtastic
|
|
|
298
302
|
end
|
|
299
303
|
|
|
300
304
|
# Get a column object for a specified attribute method - if possible.
|
|
301
|
-
|
|
302
|
-
|
|
305
|
+
# @return [ActiveModel::Type::Value, #type] in case of rails 5 attributes api
|
|
306
|
+
# @return [ActiveRecord::ConnectionAdapters::Column] in case of rails 4
|
|
307
|
+
def column_for(method) # @private
|
|
308
|
+
case
|
|
309
|
+
when @object.class.respond_to?(:type_for_attribute)
|
|
310
|
+
@object.class.type_for_attribute(method.to_s)
|
|
311
|
+
when @object.class.respond_to?(:column_for_attribute)
|
|
312
|
+
@object.class.column_for_attribute(method)
|
|
313
|
+
when @object.respond_to?(:column_for_attribute)
|
|
314
|
+
@object.column_for_attribute(method)
|
|
315
|
+
else nil
|
|
316
|
+
end
|
|
303
317
|
end
|
|
304
318
|
|
|
305
|
-
# Takes the `:as` option and attempts to return the corresponding input
|
|
306
|
-
# `:as => :
|
|
307
|
-
#
|
|
319
|
+
# Takes the `:as` option and attempts to return the corresponding input
|
|
320
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
|
321
|
+
# top level `AwesomeInput` class (to allow the application to subclass
|
|
322
|
+
# and modify to suit), falling back to `Formtastic::Inputs::AwesomeInput`.
|
|
308
323
|
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
324
|
+
# Custom input namespaces to look into can be configured via the
|
|
325
|
+
# {Formtastic::FormBuilder.input_namespaces} configuration setting.
|
|
311
326
|
#
|
|
312
327
|
# @param [Symbol] as A symbol representing the type of input to render
|
|
313
328
|
# @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
|
|
@@ -319,50 +334,14 @@ module Formtastic
|
|
|
319
334
|
#
|
|
320
335
|
# @example When a top-level class is found
|
|
321
336
|
# input_class(:string) #=> StringInput
|
|
322
|
-
# input_class(:awesome) #=> AwesomeInput
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
@
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# prevent exceptions in production environment for better performance
|
|
333
|
-
def input_class_with_const_defined(as)
|
|
334
|
-
input_class_name = custom_input_class_name(as)
|
|
335
|
-
|
|
336
|
-
if ::Object.const_defined?(input_class_name)
|
|
337
|
-
input_class_name.constantize
|
|
338
|
-
elsif Formtastic::Inputs.const_defined?(input_class_name)
|
|
339
|
-
standard_input_class_name(as).constantize
|
|
340
|
-
else
|
|
341
|
-
raise Formtastic::UnknownInputError, "Unable to find input class #{input_class_name}"
|
|
342
|
-
end
|
|
337
|
+
# input_class(:awesome) #=> AwesomeInput
|
|
338
|
+
# @see NamespacedClassFinder#find
|
|
339
|
+
def namespaced_input_class(as)
|
|
340
|
+
@input_class_finder ||= input_class_finder.new(self)
|
|
341
|
+
@input_class_finder.find(as)
|
|
342
|
+
rescue Formtastic::InputClassFinder::NotFoundError
|
|
343
|
+
raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}"
|
|
343
344
|
end
|
|
344
|
-
|
|
345
|
-
# use auto-loading in development environment
|
|
346
|
-
def input_class_by_trying(as)
|
|
347
|
-
begin
|
|
348
|
-
custom_input_class_name(as).constantize
|
|
349
|
-
rescue NameError
|
|
350
|
-
standard_input_class_name(as).constantize
|
|
351
|
-
end
|
|
352
|
-
rescue NameError
|
|
353
|
-
raise Formtastic::UnknownInputError, "Unable to find input class for #{as}"
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
# :as => :string # => StringInput
|
|
357
|
-
def custom_input_class_name(as)
|
|
358
|
-
"#{as.to_s.camelize}Input"
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
# :as => :string # => Formtastic::Inputs::StringInput
|
|
362
|
-
def standard_input_class_name(as)
|
|
363
|
-
"Formtastic::Inputs::#{as.to_s.camelize}Input"
|
|
364
|
-
end
|
|
365
|
-
|
|
366
345
|
end
|
|
367
346
|
end
|
|
368
347
|
end
|