formtastic 1.2.4 → 3.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +46 -0
- data/.yardopts +1 -0
- data/Appraisals +43 -0
- data/CHANGELOG +54 -0
- data/DEPRECATIONS +52 -0
- data/Gemfile +3 -0
- data/README.md +629 -0
- data/RELEASE_PROCESS +6 -0
- data/Rakefile +35 -0
- data/app/assets/stylesheets/formtastic.css +289 -0
- data/app/assets/stylesheets/formtastic_ie6.css +33 -0
- data/app/assets/stylesheets/formtastic_ie7.css +23 -0
- data/formtastic.gemspec +42 -0
- data/gemfiles/rails_3.2.gemfile +9 -0
- data/gemfiles/rails_4.0.4.gemfile +8 -0
- data/gemfiles/rails_4.1.gemfile +8 -0
- data/gemfiles/rails_4.2.gemfile +8 -0
- data/gemfiles/rails_4.gemfile +8 -0
- data/gemfiles/rails_5.0.gemfile +8 -0
- data/gemfiles/rails_edge.gemfile +15 -0
- data/lib/formtastic.rb +40 -1945
- data/lib/formtastic/action_class_finder.rb +18 -0
- data/lib/formtastic/actions.rb +11 -0
- data/lib/formtastic/actions/base.rb +156 -0
- data/lib/formtastic/actions/button_action.rb +67 -0
- data/lib/formtastic/actions/buttonish.rb +17 -0
- data/lib/formtastic/actions/input_action.rb +70 -0
- data/lib/formtastic/actions/link_action.rb +88 -0
- data/lib/formtastic/deprecation.rb +42 -0
- data/lib/formtastic/engine.rb +11 -0
- data/lib/formtastic/form_builder.rb +124 -0
- data/lib/formtastic/helpers.rb +16 -0
- data/lib/formtastic/helpers/action_helper.rb +162 -0
- data/lib/formtastic/helpers/actions_helper.rb +168 -0
- data/lib/formtastic/helpers/enum.rb +13 -0
- data/lib/formtastic/helpers/errors_helper.rb +81 -0
- data/lib/formtastic/helpers/fieldset_wrapper.rb +80 -0
- data/lib/formtastic/helpers/file_column_detection.rb +16 -0
- data/lib/formtastic/helpers/form_helper.rb +203 -0
- data/lib/formtastic/helpers/input_helper.rb +407 -0
- data/lib/formtastic/helpers/inputs_helper.rb +411 -0
- data/lib/formtastic/helpers/reflection.rb +37 -0
- data/lib/formtastic/html_attributes.rb +32 -0
- data/lib/formtastic/i18n.rb +4 -2
- data/lib/formtastic/input_class_finder.rb +18 -0
- data/lib/formtastic/inputs.rb +39 -0
- data/lib/formtastic/inputs/base.rb +76 -0
- data/lib/formtastic/inputs/base/associations.rb +31 -0
- data/lib/formtastic/inputs/base/choices.rb +108 -0
- data/lib/formtastic/inputs/base/collections.rb +159 -0
- data/lib/formtastic/inputs/base/database.rb +22 -0
- data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
- data/lib/formtastic/inputs/base/errors.rb +58 -0
- data/lib/formtastic/inputs/base/fileish.rb +23 -0
- data/lib/formtastic/inputs/base/hints.rb +31 -0
- data/lib/formtastic/inputs/base/html.rb +53 -0
- data/lib/formtastic/inputs/base/labelling.rb +52 -0
- data/lib/formtastic/inputs/base/naming.rb +42 -0
- data/lib/formtastic/inputs/base/numeric.rb +50 -0
- data/lib/formtastic/inputs/base/options.rb +17 -0
- data/lib/formtastic/inputs/base/placeholder.rb +17 -0
- data/lib/formtastic/inputs/base/stringish.rb +38 -0
- data/lib/formtastic/inputs/base/timeish.rb +241 -0
- data/lib/formtastic/inputs/base/validations.rb +215 -0
- data/lib/formtastic/inputs/base/wrapping.rb +50 -0
- data/lib/formtastic/inputs/boolean_input.rb +118 -0
- data/lib/formtastic/inputs/check_boxes_input.rb +197 -0
- data/lib/formtastic/inputs/color_input.rb +42 -0
- data/lib/formtastic/inputs/country_input.rb +86 -0
- data/lib/formtastic/inputs/datalist_input.rb +41 -0
- data/lib/formtastic/inputs/date_picker_input.rb +93 -0
- data/lib/formtastic/inputs/date_select_input.rb +34 -0
- data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
- data/lib/formtastic/inputs/datetime_select_input.rb +12 -0
- data/lib/formtastic/inputs/email_input.rb +41 -0
- data/lib/formtastic/inputs/file_input.rb +42 -0
- data/lib/formtastic/inputs/hidden_input.rb +62 -0
- data/lib/formtastic/inputs/number_input.rb +88 -0
- data/lib/formtastic/inputs/password_input.rb +41 -0
- data/lib/formtastic/inputs/phone_input.rb +42 -0
- data/lib/formtastic/inputs/radio_input.rb +163 -0
- data/lib/formtastic/inputs/range_input.rb +95 -0
- data/lib/formtastic/inputs/search_input.rb +41 -0
- data/lib/formtastic/inputs/select_input.rb +235 -0
- data/lib/formtastic/inputs/string_input.rb +36 -0
- data/lib/formtastic/inputs/text_input.rb +48 -0
- data/lib/formtastic/inputs/time_picker_input.rb +99 -0
- data/lib/formtastic/inputs/time_select_input.rb +38 -0
- data/lib/formtastic/inputs/time_zone_input.rb +58 -0
- data/lib/formtastic/inputs/url_input.rb +41 -0
- data/lib/formtastic/localized_string.rb +17 -0
- data/lib/formtastic/localizer.rb +152 -0
- data/lib/formtastic/namespaced_class_finder.rb +99 -0
- data/lib/formtastic/util.rb +35 -16
- data/lib/formtastic/version.rb +3 -0
- data/lib/generators/formtastic/form/form_generator.rb +64 -37
- data/lib/generators/formtastic/input/input_generator.rb +46 -0
- data/lib/generators/formtastic/install/install_generator.rb +13 -5
- data/lib/generators/templates/_form.html.erb +10 -4
- data/lib/generators/templates/_form.html.haml +8 -4
- data/lib/generators/templates/_form.html.slim +8 -0
- data/lib/generators/templates/formtastic.rb +77 -44
- data/lib/generators/templates/input.rb +19 -0
- data/lib/locale/en.yml +3 -0
- data/sample/basic_inputs.html +224 -0
- data/sample/config.ru +69 -0
- data/sample/index.html +14 -0
- data/spec/action_class_finder_spec.rb +12 -0
- data/spec/actions/button_action_spec.rb +63 -0
- data/spec/actions/generic_action_spec.rb +521 -0
- data/spec/actions/input_action_spec.rb +59 -0
- data/spec/actions/link_action_spec.rb +92 -0
- data/spec/builder/custom_builder_spec.rb +116 -0
- data/spec/builder/error_proc_spec.rb +27 -0
- data/spec/builder/semantic_fields_for_spec.rb +142 -0
- data/spec/fast_spec_helper.rb +12 -0
- data/spec/generators/formtastic/form/form_generator_spec.rb +131 -0
- data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
- data/spec/generators/formtastic/install/install_generator_spec.rb +47 -0
- data/spec/helpers/action_helper_spec.rb +19 -0
- data/spec/helpers/actions_helper_spec.rb +143 -0
- data/spec/helpers/form_helper_spec.rb +218 -0
- data/spec/helpers/input_helper_spec.rb +6 -0
- data/spec/helpers/inputs_helper_spec.rb +655 -0
- data/spec/helpers/namespaced_action_helper_spec.rb +43 -0
- data/spec/helpers/namespaced_input_helper_spec.rb +36 -0
- data/spec/helpers/reflection_helper_spec.rb +32 -0
- data/spec/helpers/semantic_errors_helper_spec.rb +112 -0
- data/spec/i18n_spec.rb +210 -0
- 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 +342 -0
- data/spec/inputs/boolean_input_spec.rb +254 -0
- data/spec/inputs/check_boxes_input_spec.rb +546 -0
- data/spec/inputs/color_input_spec.rb +97 -0
- data/spec/inputs/country_input_spec.rb +133 -0
- data/spec/inputs/custom_input_spec.rb +55 -0
- 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 +235 -0
- data/spec/inputs/datetime_picker_input_spec.rb +490 -0
- data/spec/inputs/datetime_select_input_spec.rb +193 -0
- data/spec/inputs/email_input_spec.rb +85 -0
- data/spec/inputs/file_input_spec.rb +89 -0
- data/spec/inputs/hidden_input_spec.rb +135 -0
- data/spec/inputs/include_blank_spec.rb +78 -0
- data/spec/inputs/label_spec.rb +149 -0
- data/spec/inputs/number_input_spec.rb +815 -0
- data/spec/inputs/password_input_spec.rb +99 -0
- data/spec/inputs/phone_input_spec.rb +85 -0
- data/spec/inputs/placeholder_spec.rb +71 -0
- data/spec/inputs/radio_input_spec.rb +328 -0
- data/spec/inputs/range_input_spec.rb +505 -0
- data/spec/inputs/readonly_spec.rb +50 -0
- data/spec/inputs/search_input_spec.rb +84 -0
- data/spec/inputs/select_input_spec.rb +615 -0
- data/spec/inputs/string_input_spec.rb +260 -0
- data/spec/inputs/text_input_spec.rb +187 -0
- data/spec/inputs/time_picker_input_spec.rb +455 -0
- data/spec/inputs/time_select_input_spec.rb +248 -0
- data/spec/inputs/time_zone_input_spec.rb +143 -0
- data/spec/inputs/url_input_spec.rb +85 -0
- data/spec/inputs/with_options_spec.rb +43 -0
- data/spec/localizer_spec.rb +130 -0
- data/spec/namespaced_class_finder_spec.rb +79 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +525 -0
- data/spec/support/custom_macros.rb +564 -0
- data/spec/support/deprecation.rb +6 -0
- data/spec/support/shared_examples.rb +1313 -0
- data/spec/support/specialized_class_finder_shared_example.rb +27 -0
- data/spec/support/test_environment.rb +31 -0
- data/spec/util_spec.rb +66 -0
- metadata +434 -161
- data/README.textile +0 -682
- data/generators/form/USAGE +0 -16
- data/generators/form/form_generator.rb +0 -111
- data/generators/formtastic/formtastic_generator.rb +0 -26
- data/init.rb +0 -5
- data/lib/formtastic/layout_helper.rb +0 -12
- data/lib/formtastic/railtie.rb +0 -14
- data/lib/generators/templates/formtastic.css +0 -145
- data/lib/generators/templates/formtastic_changes.css +0 -14
- data/lib/generators/templates/rails2/_form.html.erb +0 -5
- data/lib/generators/templates/rails2/_form.html.haml +0 -4
- data/rails/init.rb +0 -2
@@ -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
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Helpers
|
3
|
+
module ErrorsHelper
|
4
|
+
include Formtastic::Helpers::FileColumnDetection
|
5
|
+
include Formtastic::Helpers::Reflection
|
6
|
+
include Formtastic::LocalizedString
|
7
|
+
|
8
|
+
INLINE_ERROR_TYPES = [:sentence, :list, :first]
|
9
|
+
|
10
|
+
# Generates an unordered list of error messages on the base object and optionally for a given
|
11
|
+
# set of named attribute. This is idea for rendering a block of error messages at the top of
|
12
|
+
# the form for hidden/special/virtual attributes (the Paperclip Rails plugin does this), or
|
13
|
+
# errors on the base model.
|
14
|
+
#
|
15
|
+
# A hash can be used as the last set of arguments to pass HTML attributes to the `<ul>`
|
16
|
+
# wrapper.
|
17
|
+
#
|
18
|
+
# @example A list of errors on the base model
|
19
|
+
# <%= semantic_form_for ... %>
|
20
|
+
# <%= f.semantic_errors %>
|
21
|
+
# ...
|
22
|
+
# <% end %>
|
23
|
+
#
|
24
|
+
# @example A list of errors on the base and named attributes
|
25
|
+
# <%= semantic_form_for ... %>
|
26
|
+
# <%= f.semantic_errors :something_special %>
|
27
|
+
# ...
|
28
|
+
# <% end %>
|
29
|
+
#
|
30
|
+
# @example A list of errors on the base model, with custom HTML attributes
|
31
|
+
# <%= semantic_form_for ... %>
|
32
|
+
# <%= f.semantic_errors :class => "awesome" %>
|
33
|
+
# ...
|
34
|
+
# <% end %>
|
35
|
+
#
|
36
|
+
# @example A list of errors on the base model and named attributes, with custom HTML attributes
|
37
|
+
# <%= semantic_form_for ... %>
|
38
|
+
# <%= f.semantic_errors :something_special, :something_else, :class => "awesome", :onclick => "Awesome();" %>
|
39
|
+
# ...
|
40
|
+
# <% end %>
|
41
|
+
def semantic_errors(*args)
|
42
|
+
html_options = args.extract_options!
|
43
|
+
args = args - [:base]
|
44
|
+
full_errors = args.inject([]) do |array, method|
|
45
|
+
attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
|
46
|
+
errors = Array(@object.errors[method.to_sym]).to_sentence
|
47
|
+
errors.present? ? array << [attribute, errors].join(" ") : array ||= []
|
48
|
+
end
|
49
|
+
full_errors << @object.errors[:base]
|
50
|
+
full_errors.flatten!
|
51
|
+
full_errors.compact!
|
52
|
+
return nil if full_errors.blank?
|
53
|
+
html_options[:class] ||= "errors"
|
54
|
+
template.content_tag(:ul, html_options) do
|
55
|
+
Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def error_keys(method, options)
|
62
|
+
@methods_for_error ||= {}
|
63
|
+
@methods_for_error[method] ||= begin
|
64
|
+
methods_for_error = [method.to_sym]
|
65
|
+
methods_for_error << file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
|
66
|
+
methods_for_error << [association_primary_key_for_method(method)] if [:belongs_to, :has_many].include? association_macro_for_method(method)
|
67
|
+
methods_for_error.flatten.compact.uniq
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def has_errors?(method, options)
|
72
|
+
methods_for_error = error_keys(method,options)
|
73
|
+
@object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
|
74
|
+
end
|
75
|
+
|
76
|
+
def render_inline_errors?
|
77
|
+
@object && @object.respond_to?(:errors) && Formtastic::FormBuilder::INLINE_ERROR_TYPES.include?(inline_errors)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Helpers
|
3
|
+
# @private
|
4
|
+
module FieldsetWrapper
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Generates a fieldset and wraps the content in an ordered list. When working
|
9
|
+
# with nested attributes, it allows %i as interpolation option in :name. So you can do:
|
10
|
+
#
|
11
|
+
# f.inputs :name => 'Task #%i', :for => :tasks
|
12
|
+
#
|
13
|
+
# or the shorter equivalent:
|
14
|
+
#
|
15
|
+
# f.inputs 'Task #%i', :for => :tasks
|
16
|
+
#
|
17
|
+
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
|
18
|
+
# 'Task #3' and so on.
|
19
|
+
#
|
20
|
+
# Note: Special case for the inline inputs (non-block):
|
21
|
+
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
22
|
+
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
23
|
+
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
24
|
+
def field_set_and_list_wrapping(*args, &block) # @private
|
25
|
+
contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
|
26
|
+
html_options = args.extract_options!
|
27
|
+
|
28
|
+
if block_given?
|
29
|
+
contents = if template.respond_to?(:is_haml?) && template.is_haml?
|
30
|
+
template.capture_haml(&block)
|
31
|
+
else
|
32
|
+
template.capture(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
37
|
+
contents = contents.join if contents.respond_to?(:join)
|
38
|
+
|
39
|
+
legend = field_set_legend(html_options)
|
40
|
+
fieldset = template.content_tag(:fieldset,
|
41
|
+
Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
|
42
|
+
html_options.except(:builder, :parent, :name)
|
43
|
+
)
|
44
|
+
|
45
|
+
fieldset
|
46
|
+
end
|
47
|
+
|
48
|
+
def field_set_legend(html_options)
|
49
|
+
legend = (html_options[:name] || '').to_s
|
50
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i') # only applying if String includes '%i' avoids argument error when $DEBUG is true
|
51
|
+
legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
|
52
|
+
legend
|
53
|
+
end
|
54
|
+
|
55
|
+
# Gets the nested_child_index value from the parent builder. It returns a hash with each
|
56
|
+
# association that the parent builds.
|
57
|
+
def parent_child_index(parent) # @private
|
58
|
+
# Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
|
59
|
+
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
60
|
+
|
61
|
+
# Could be symbol for the association, or a model (or an array of either, I think? TODO)
|
62
|
+
child = parent[:for]
|
63
|
+
# Pull a sybol or model out of Array (TODO: check if there's an Array)
|
64
|
+
child = child.first if child.respond_to?(:first)
|
65
|
+
# If it's an object, get a symbol from the class name
|
66
|
+
child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
|
67
|
+
|
68
|
+
key = "#{parent[:builder].object_name}[#{child}_attributes]"
|
69
|
+
|
70
|
+
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
71
|
+
# 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?(Integer)
|
73
|
+
|
74
|
+
# First try to extract key from duck Hash, then try child
|
75
|
+
(duck[key] || duck[child]).to_i + 1
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Helpers
|
3
|
+
# @private
|
4
|
+
module FileColumnDetection
|
5
|
+
|
6
|
+
def is_file?(method, options = {})
|
7
|
+
@files ||= {}
|
8
|
+
@files[method] ||= (options[:as].present? && options[:as] == :file) || begin
|
9
|
+
file = @object.send(method) if @object && @object.respond_to?(method)
|
10
|
+
file && file_methods.any?{|m| file.respond_to?(m)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# FormHelper provides a handful of wrappers around Rails' built-in form helpers methods to set
|
5
|
+
# the `:builder` option to `Formtastic::FormBuilder` and apply some class names to the `<form>`
|
6
|
+
# tag.
|
7
|
+
#
|
8
|
+
# The following methods are wrapped:
|
9
|
+
#
|
10
|
+
# * `semantic_form_for` to `form_for`
|
11
|
+
# * `semantic_fields_for` to `fields_for`
|
12
|
+
# * `semantic_remote_form_for` and `semantic_form_remote_for` to `remote_form_for`
|
13
|
+
#
|
14
|
+
# The following two examples are effectively equivalent:
|
15
|
+
#
|
16
|
+
# <%= form_for(@post, :builder => Formtastic::FormBuilder, :class => 'formtastic post') do |f| %>
|
17
|
+
# #...
|
18
|
+
# <% end %>
|
19
|
+
#
|
20
|
+
# <%= semantic_form_for(@post) do |f| %>
|
21
|
+
# #...
|
22
|
+
# <% end %>
|
23
|
+
#
|
24
|
+
# This simple wrapping means that all arguments, options and variations supported by Rails' own
|
25
|
+
# helpers are also supported by Formtastic.
|
26
|
+
#
|
27
|
+
# Since `Formtastic::FormBuilder` subclasses Rails' own `FormBuilder`, you have access to all
|
28
|
+
# of Rails' built-in form helper methods such as `text_field`, `check_box`, `radio_button`,
|
29
|
+
# etc **in addition to** all of Formtastic's additional helpers like {InputsHelper#inputs inputs},
|
30
|
+
# {InputsHelper#input input}, {ButtonsHelper#buttons buttons}, etc:
|
31
|
+
#
|
32
|
+
# <%= semantic_form_for(@post) do |f| %>
|
33
|
+
#
|
34
|
+
# <!-- Formtastic -->
|
35
|
+
# <%= f.input :title %>
|
36
|
+
#
|
37
|
+
# <!-- Rails -->
|
38
|
+
# <li class='something-custom'>
|
39
|
+
# <%= f.label :title %>
|
40
|
+
# <%= f.text_field :title %>
|
41
|
+
# <p class='hints'>...</p>
|
42
|
+
# </li>
|
43
|
+
# <% end %>
|
44
|
+
#
|
45
|
+
# Formtastic is a superset of Rails' FormBuilder. It deliberately avoids overriding or modifying
|
46
|
+
# the behavior of Rails' own form helpers so that you can use Formtastic helpers when suited,
|
47
|
+
# and fall back to regular Rails helpers, ERB and HTML when needed. In other words, you're never
|
48
|
+
# fully committed to The Formtastic Way.
|
49
|
+
module FormHelper
|
50
|
+
|
51
|
+
# Allows the `:builder` option on `form_for` etc to be changed to your own which subclasses
|
52
|
+
# `Formtastic::FormBuilder`. Change this from `config/initializers/formtastic.rb`.
|
53
|
+
@@builder = Formtastic::FormBuilder
|
54
|
+
mattr_accessor :builder
|
55
|
+
|
56
|
+
# Allows the default class we add to all `<form>` tags to be changed from `formtastic` to
|
57
|
+
# `whatever`. Change this from `config/initializers/formtastic.rb`.
|
58
|
+
@@default_form_class = 'formtastic'
|
59
|
+
mattr_accessor :default_form_class
|
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
|
+
|
72
|
+
# Wrapper around Rails' own `form_for` helper to set the `:builder` option to
|
73
|
+
# `Formtastic::FormBuilder` and to set some class names on the `<form>` tag such as
|
74
|
+
# `formtastic` and the downcased and underscored model name (eg `post`).
|
75
|
+
#
|
76
|
+
# See Rails' `form_for` for full documentation of all supported arguments and options.
|
77
|
+
#
|
78
|
+
# Since `Formtastic::FormBuilder` subclasses Rails' own FormBuilder, you have access to all
|
79
|
+
# of Rails' built-in form helper methods such as `text_field`, `check_box`, `radio_button`,
|
80
|
+
# etc **in addition to** all of Formtastic's additional helpers like {InputsHelper#inputs inputs},
|
81
|
+
# {InputsHelper#input input}, {ButtonsHelper#buttons buttons}, etc.
|
82
|
+
#
|
83
|
+
# Most of the examples below have been adapted from the examples found in the Rails `form_for`
|
84
|
+
# documentation.
|
85
|
+
#
|
86
|
+
# @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html Rails' FormHelper documentation (`form_for`, etc)
|
87
|
+
# @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html Rails' FormBuilder documentaion (`text_field`, etc)
|
88
|
+
# @see FormHelper The overview of the FormBuilder module
|
89
|
+
#
|
90
|
+
# @example Resource-oriented form generation
|
91
|
+
# <%= semantic_form_for @user do |f| %>
|
92
|
+
# <%= f.input :name %>
|
93
|
+
# <%= f.input :email %>
|
94
|
+
# <%= f.input :password %>
|
95
|
+
# <% end %>
|
96
|
+
#
|
97
|
+
# @example Generic form generation
|
98
|
+
# <%= semantic_form_for :user do |f| %>
|
99
|
+
# <%= f.input :name %>
|
100
|
+
# <%= f.input :email %>
|
101
|
+
# <%= f.input :password %>
|
102
|
+
# <% end %>
|
103
|
+
#
|
104
|
+
# @example Resource-oriented with custom URL
|
105
|
+
# <%= semantic_form_for(@post, :url => super_post_path(@post)) do |f| %>
|
106
|
+
# ...
|
107
|
+
# <% end %>
|
108
|
+
#
|
109
|
+
# @example Resource-oriented with namespaced routes
|
110
|
+
# <%= semantic_form_for([:admin, @post]) do |f| %>
|
111
|
+
# ...
|
112
|
+
# <% end %>
|
113
|
+
#
|
114
|
+
# @example Resource-oriented with nested routes
|
115
|
+
# <%= semantic_form_for([@user, @post]) do |f| %>
|
116
|
+
# ...
|
117
|
+
# <% end %>
|
118
|
+
#
|
119
|
+
# @example Rename the resource
|
120
|
+
# <%= semantic_form_for(@post, :as => :article) do |f| %>
|
121
|
+
# ...
|
122
|
+
# <% end %>
|
123
|
+
#
|
124
|
+
# @example Remote forms (unobtrusive JavaScript)
|
125
|
+
# <%= semantic_form_for(@post, :remote => true) do |f| %>
|
126
|
+
# ...
|
127
|
+
# <% end %>
|
128
|
+
#
|
129
|
+
# @example Namespaced forms all multiple Formtastic forms to exist on the one page without DOM id clashes and invalid HTML documents.
|
130
|
+
# <%= semantic_form_for(@post, :namespace => 'first') do |f| %>
|
131
|
+
# ...
|
132
|
+
# <% end %>
|
133
|
+
#
|
134
|
+
# @example Accessing a mixture of Formtastic helpers and Rails FormBuilder helpers.
|
135
|
+
# <%= semantic_form_for(@post) do |f| %>
|
136
|
+
# <%= f.input :title %>
|
137
|
+
# <%= f.input :body %>
|
138
|
+
# <li class="something-custom">
|
139
|
+
# <label><%= f.check_box :published %></label>
|
140
|
+
# </li>
|
141
|
+
# <% end %>
|
142
|
+
#
|
143
|
+
# @param record_or_name_or_array
|
144
|
+
# Same behavior as Rails' `form_for`
|
145
|
+
#
|
146
|
+
# @option *args [Hash] :html
|
147
|
+
# Pass HTML attributes into the `<form>` tag. Same behavior as Rails' `form_for`, except we add in some of our own classes.
|
148
|
+
#
|
149
|
+
# @option *args [String, Hash] :url
|
150
|
+
# A hash of URL components just like you pass into `link_to` or `url_for`, or a named route (eg `posts_path`). Same behavior as Rails' `form_for`.
|
151
|
+
#
|
152
|
+
# @option *args [String] :namespace
|
153
|
+
def semantic_form_for(record_or_name_or_array, *args, &proc)
|
154
|
+
options = args.extract_options!
|
155
|
+
options[:builder] ||= @@builder
|
156
|
+
options[:html] ||= {}
|
157
|
+
options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
|
158
|
+
options[:custom_namespace] = options.delete(:namespace)
|
159
|
+
|
160
|
+
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
161
|
+
|
162
|
+
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|
163
|
+
class_names << @@default_form_class
|
164
|
+
model_class_name = case record_or_name_or_array
|
165
|
+
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[-1].class) # [@post, @comment] # => "comment"
|
167
|
+
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
168
|
+
end
|
169
|
+
class_names << @@default_form_model_class_proc.call(model_class_name)
|
170
|
+
|
171
|
+
options[:html][:class] = class_names.compact.join(" ")
|
172
|
+
|
173
|
+
with_custom_field_error_proc do
|
174
|
+
self.form_for(record_or_name_or_array, *(args << options), &proc)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Wrapper around Rails' own `fields_for` helper to set the `:builder` option to
|
179
|
+
# `Formtastic::FormBuilder`.
|
180
|
+
#
|
181
|
+
# @see #semantic_form_for
|
182
|
+
def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
|
183
|
+
options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
|
184
|
+
options[:builder] ||= @@builder
|
185
|
+
options[:custom_namespace] = options.delete(:namespace)
|
186
|
+
|
187
|
+
with_custom_field_error_proc do
|
188
|
+
self.fields_for(record_name, record_object, options, &block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
protected
|
193
|
+
|
194
|
+
def with_custom_field_error_proc(&block)
|
195
|
+
default_field_error_proc = ::ActionView::Base.field_error_proc
|
196
|
+
::ActionView::Base.field_error_proc = @@formtastic_field_error_proc
|
197
|
+
yield
|
198
|
+
ensure
|
199
|
+
::ActionView::Base.field_error_proc = default_field_error_proc
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,407 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module Formtastic
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
# {#input} is used to render all content (labels, form widgets, error messages, hints, etc) for
|
6
|
+
# a single form input (or field), usually representing a single method or attribute on the
|
7
|
+
# form's object or model.
|
8
|
+
#
|
9
|
+
# The content is wrapped in an `<li>` tag, so it's usually called inside an {Formtastic::Helpers::InputsHelper#inputs inputs} block
|
10
|
+
# (which renders an `<ol>` inside a `<fieldset>`), which should be inside a {Formtastic::Helpers::FormHelper#semantic_form_for `semantic_form_for`}
|
11
|
+
# block:
|
12
|
+
#
|
13
|
+
# <%= semantic_form_for @post do |f| %>
|
14
|
+
# <%= f.inputs do %>
|
15
|
+
# <%= f.input :title %>
|
16
|
+
# <%= f.input :body %>
|
17
|
+
# <% end %>
|
18
|
+
# <% end %>
|
19
|
+
#
|
20
|
+
# The HTML output will be something like:
|
21
|
+
#
|
22
|
+
# <form class="formtastic" method="post" action="...">
|
23
|
+
# <fieldset>
|
24
|
+
# <ol>
|
25
|
+
# <li class="string required" id="post_title_input">
|
26
|
+
# ...
|
27
|
+
# </li>
|
28
|
+
# <li class="text required" id="post_body_input">
|
29
|
+
# ...
|
30
|
+
# </li>
|
31
|
+
# </ol>
|
32
|
+
# </fieldset>
|
33
|
+
# </form>
|
34
|
+
#
|
35
|
+
# @see #input
|
36
|
+
# @see Formtastic::Helpers::InputsHelper#inputs
|
37
|
+
# @see Formtastic::Helpers::FormHelper#semantic_form_for
|
38
|
+
module InputHelper
|
39
|
+
INPUT_CLASS_DEPRECATION = 'configure Formtastic::FormBuilder.input_class_finder instead (upgrade guide on wiki: http://bit.ly/1F9QtKc )'.freeze
|
40
|
+
private_constant(:INPUT_CLASS_DEPRECATION)
|
41
|
+
|
42
|
+
include Formtastic::Helpers::Reflection
|
43
|
+
include Formtastic::Helpers::Enum
|
44
|
+
include Formtastic::Helpers::FileColumnDetection
|
45
|
+
|
46
|
+
# Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
|
47
|
+
# an `<li>` wrapper tag with appropriate `class` and `id` attribute hooks for CSS and JS.
|
48
|
+
# In many cases, the contents of the wrapper will be as simple as a `<label>` and an `<input>`:
|
49
|
+
#
|
50
|
+
# <%= f.input :title, :as => :string, :required => true %>
|
51
|
+
#
|
52
|
+
# <li class="string required" id="post_title_input">
|
53
|
+
# <label for="post_title">Title<abbr title="Required">*</abbr></label>
|
54
|
+
# <input type="text" name="post[title]" value="" id="post_title" required="required">
|
55
|
+
# </li>
|
56
|
+
#
|
57
|
+
# In other cases (like a series of checkboxes for a `has_many` relationship), the wrapper may
|
58
|
+
# include more complex markup, like a nested `<fieldset>` with a `<legend>` and an `<ol>` of
|
59
|
+
# checkbox/label pairs for each choice:
|
60
|
+
#
|
61
|
+
# <%= f.input :categories, :as => :check_boxes, :collection => Category.active.ordered %>
|
62
|
+
#
|
63
|
+
# <li class="check_boxes" id="post_categories_input">
|
64
|
+
# <fieldset>
|
65
|
+
# <legend>Categories</legend>
|
66
|
+
# <ol>
|
67
|
+
# <li>
|
68
|
+
# <label><input type="checkbox" name="post[categories][1]" value="1"> Ruby</label>
|
69
|
+
# </li>
|
70
|
+
# <li>
|
71
|
+
# <label><input type="checkbox" name="post[categories][2]" value="2"> Rails</label>
|
72
|
+
# </li>
|
73
|
+
# <li>
|
74
|
+
# <label><input type="checkbox" name="post[categories][2]" value="2"> Awesome</label>
|
75
|
+
# </li>
|
76
|
+
# </ol>
|
77
|
+
# </fieldset>
|
78
|
+
# </li>
|
79
|
+
#
|
80
|
+
# Sensible defaults for all options are guessed by looking at the method name, database column
|
81
|
+
# information, association information, validation information, etc. For example, a `:string`
|
82
|
+
# database column will map to a `:string` input, but if the method name contains 'email', will
|
83
|
+
# map to an `:email` input instead. `belongs_to` associations will have a `:select` input, etc.
|
84
|
+
#
|
85
|
+
# Formtastic supports many different styles of inputs, and you can/should override the default
|
86
|
+
# with the `:as` option. Internally, the symbol is used to map to a protected method
|
87
|
+
# responsible for the details. For example, `:as => :string` will map to `string_input`,
|
88
|
+
# defined in a module of the same name. Detailed documentation for each input style and it's
|
89
|
+
# supported options is available on the `*_input` method in each module (links provided below).
|
90
|
+
#
|
91
|
+
# Available input styles:
|
92
|
+
#
|
93
|
+
# * `:boolean` (see {Inputs::BooleanInput})
|
94
|
+
# * `:check_boxes` (see {Inputs::CheckBoxesInput})
|
95
|
+
# * `:color` (see {Inputs::ColorInput})
|
96
|
+
# * `:country` (see {Inputs::CountryInput})
|
97
|
+
# * `:datetime_select` (see {Inputs::DatetimeSelectInput})
|
98
|
+
# * `:date_select` (see {Inputs::DateSelectInput})
|
99
|
+
# * `:email` (see {Inputs::EmailInput})
|
100
|
+
# * `:file` (see {Inputs::FileInput})
|
101
|
+
# * `:hidden` (see {Inputs::HiddenInput})
|
102
|
+
# * `:number` (see {Inputs::NumberInput})
|
103
|
+
# * `:password` (see {Inputs::PasswordInput})
|
104
|
+
# * `:phone` (see {Inputs::PhoneInput})
|
105
|
+
# * `:radio` (see {Inputs::RadioInput})
|
106
|
+
# * `:search` (see {Inputs::SearchInput})
|
107
|
+
# * `:select` (see {Inputs::SelectInput})
|
108
|
+
# * `:string` (see {Inputs::StringInput})
|
109
|
+
# * `:text` (see {Inputs::TextInput})
|
110
|
+
# * `:time_zone` (see {Inputs::TimeZoneInput})
|
111
|
+
# * `:time_select` (see {Inputs::TimeSelectInput})
|
112
|
+
# * `:url` (see {Inputs::UrlInput})
|
113
|
+
#
|
114
|
+
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
|
115
|
+
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
|
116
|
+
# namespace StringInput, meaning you can subclass and modify `Formtastic::Inputs::StringInput`
|
117
|
+
# in `app/inputs/`. This also means you can create your own new input types in `app/inputs/`.
|
118
|
+
#
|
119
|
+
# @todo document the "guessing" of input style
|
120
|
+
#
|
121
|
+
# @param [Symbol] method
|
122
|
+
# The database column or method name on the form object that this input represents
|
123
|
+
#
|
124
|
+
# @option options :as [Symbol]
|
125
|
+
# Override the style of input should be rendered
|
126
|
+
#
|
127
|
+
# @option options :label [String, Symbol, false]
|
128
|
+
# Override the label text
|
129
|
+
#
|
130
|
+
# @option options :hint [String, Symbol, false]
|
131
|
+
# Override hint text
|
132
|
+
#
|
133
|
+
# @option options :required [Boolean]
|
134
|
+
# Override to mark the input as required (or not) — adds a required/optional class to the wrapper, and a HTML5 required attribute to the `<input>`
|
135
|
+
#
|
136
|
+
# @option options :input_html [Hash]
|
137
|
+
# Override or add to the HTML attributes to be passed down to the `<input>` tag
|
138
|
+
# (If you use attr_readonly method in your model, formtastic will automatically set those attributes's input readonly)
|
139
|
+
#
|
140
|
+
# @option options :wrapper_html [Hash]
|
141
|
+
# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
|
142
|
+
#
|
143
|
+
# @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
|
144
|
+
# Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
|
145
|
+
#
|
146
|
+
# @option options :multiple [Boolean]
|
147
|
+
# 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)
|
148
|
+
#
|
149
|
+
# @option options :include_blank [Boolean]
|
150
|
+
# Specify if a `:select` input should include a blank option or not (defaults to `include_blank_for_select_by_default` configuration)
|
151
|
+
#
|
152
|
+
# @option options :prompt [String]
|
153
|
+
# Specify the text in the first ('blank') `:select` input `<option>` to prompt a user to make a selection (implicitly sets `:include_blank` to `true`)
|
154
|
+
#
|
155
|
+
# @todo Can we deprecate & kill `:label`, `:hint` & `:prompt`? All strings could be shifted to i18n!
|
156
|
+
#
|
157
|
+
# @example Accept all default options
|
158
|
+
# <%= f.input :title %>
|
159
|
+
#
|
160
|
+
# @example Change the input type
|
161
|
+
# <%= f.input :title, :as => :string %>
|
162
|
+
#
|
163
|
+
# @example Changing the label with a String
|
164
|
+
# <%= f.input :title, :label => "Post title" %>
|
165
|
+
#
|
166
|
+
# @example Disabling the label with false, even if an i18n translation exists
|
167
|
+
# <%= f.input :title, :label => false %>
|
168
|
+
#
|
169
|
+
# @example Changing the hint with a String
|
170
|
+
# <%= f.input :title, :hint => "Every post needs a title!" %>
|
171
|
+
#
|
172
|
+
# @example Disabling the hint with false, even if an i18n translation exists
|
173
|
+
# <%= f.input :title, :hint => false %>
|
174
|
+
#
|
175
|
+
# @example Marking a field as required or not (even if validations do not enforce it)
|
176
|
+
# <%= f.input :title, :required => true %>
|
177
|
+
# <%= f.input :title, :required => false %>
|
178
|
+
#
|
179
|
+
# @example Changing or adding to HTML attributes in the main `<input>` or `<select>` tag
|
180
|
+
# <%= f.input :title, :input_html => { :onchange => "somethingAwesome();", :class => 'awesome' } %>
|
181
|
+
#
|
182
|
+
# @example Changing or adding to HTML attributes in the wrapper `<li>` tag
|
183
|
+
# <%= f.input :title, :wrapper_html => { :class => "important-input" } %>
|
184
|
+
#
|
185
|
+
# @example Changing the association choices with `:collection`
|
186
|
+
# <%= f.input :author, :collection => User.active %>
|
187
|
+
# <%= f.input :categories, :collection => Category.where(...).order(...) %>
|
188
|
+
# <%= f.input :status, :collection => ["Draft", "Published"] %>
|
189
|
+
# <%= f.input :status, :collection => [:draft, :published] %>
|
190
|
+
# <%= f.input :status, :collection => {"Draft" => 0, "Published" => 1} %>
|
191
|
+
# <%= f.input :status, :collection => OrderedHash.new("Draft" => 0, "Published" => 1) %>
|
192
|
+
# <%= f.input :status, :collection => [["Draft", 0], ["Published", 1]] %>
|
193
|
+
# <%= f.input :status, :collection => grouped_options_for_select(...) %>
|
194
|
+
# <%= f.input :status, :collection => options_for_select(...) %>
|
195
|
+
#
|
196
|
+
# @example Specifying if a `:select` should allow multiple selections:
|
197
|
+
# <%= f.input :cateogies, :as => :select, :multiple => true %>
|
198
|
+
# <%= f.input :cateogies, :as => :select, :multiple => false %>
|
199
|
+
#
|
200
|
+
# @example Specifying if a `:select` should have a 'blank' first option to prompt selection:
|
201
|
+
# <%= f.input :author, :as => :select, :include_blank => true %>
|
202
|
+
# <%= f.input :author, :as => :select, :include_blank => false %>
|
203
|
+
#
|
204
|
+
# @example Specifying the text for a `:select` input's 'blank' first option to prompt selection:
|
205
|
+
# <%= f.input :author, :as => :select, :prompt => "Select an Author" %>
|
206
|
+
#
|
207
|
+
# @example Modifying an input to suit your needs in `app/inputs`:
|
208
|
+
# class StringInput < Formtastic::Inputs::StringInput
|
209
|
+
# def to_html
|
210
|
+
# puts "this is my custom version of StringInput"
|
211
|
+
# super
|
212
|
+
# end
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# @example Creating your own input to suit your needs in `app/inputs`:
|
216
|
+
# class DatePickerInput
|
217
|
+
# include Formtastic::Inputs::Base
|
218
|
+
# def to_html
|
219
|
+
# # ...
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# @example Providing HTML5 placeholder text through i18n:
|
224
|
+
# en:
|
225
|
+
# formtastic:
|
226
|
+
# placeholders:
|
227
|
+
# user:
|
228
|
+
# email: "you@yours.com"
|
229
|
+
# first_name: "Joe"
|
230
|
+
# last_name: "Smith"
|
231
|
+
#
|
232
|
+
# @see #namespaced_input_class
|
233
|
+
# @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
|
234
|
+
# @todo More i18n examples.
|
235
|
+
def input(method, options = {})
|
236
|
+
method = method.to_sym
|
237
|
+
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
238
|
+
options[:as] ||= default_input_type(method, options)
|
239
|
+
|
240
|
+
klass = input_class(options[:as])
|
241
|
+
|
242
|
+
klass.new(self, template, @object, @object_name, method, options).to_html
|
243
|
+
end
|
244
|
+
|
245
|
+
protected
|
246
|
+
|
247
|
+
# First try if we can detect special things like :file. With CarrierWave the method does have
|
248
|
+
# an underlying column so we don't want :string to get selected.
|
249
|
+
#
|
250
|
+
# For methods that have a database column, take a best guess as to what the input method
|
251
|
+
# should be. In most cases, it will just return the column type (eg :string), but for special
|
252
|
+
# cases it will simplify (like the case of :integer, :float & :decimal to :number), or do
|
253
|
+
# something different (like :password and :select).
|
254
|
+
#
|
255
|
+
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
256
|
+
# default is a :string, a similar behaviour to Rails' scaffolding.
|
257
|
+
def default_input_type(method, options = {}) # @private
|
258
|
+
if @object
|
259
|
+
return :select if reflection_for(method)
|
260
|
+
|
261
|
+
return :file if is_file?(method, options)
|
262
|
+
end
|
263
|
+
|
264
|
+
column = column_for(method)
|
265
|
+
if column && column.type
|
266
|
+
# Special cases where the column type doesn't map to an input method.
|
267
|
+
case column.type
|
268
|
+
when :string
|
269
|
+
return :password if method.to_s =~ /password/
|
270
|
+
return :country if method.to_s =~ /country$/
|
271
|
+
return :time_zone if method.to_s =~ /time_zone/
|
272
|
+
return :email if method.to_s =~ /email/
|
273
|
+
return :url if method.to_s =~ /^url$|^website$|_url$/
|
274
|
+
return :phone if method.to_s =~ /(phone|fax)/
|
275
|
+
return :search if method.to_s =~ /^search$/
|
276
|
+
return :color if method.to_s =~ /color/
|
277
|
+
when :integer
|
278
|
+
return :select if reflection_for(method)
|
279
|
+
return :select if enum_for(method)
|
280
|
+
return :number
|
281
|
+
when :float, :decimal
|
282
|
+
return :number
|
283
|
+
when :datetime, :timestamp
|
284
|
+
return :datetime_select
|
285
|
+
when :time
|
286
|
+
return :time_select
|
287
|
+
when :date
|
288
|
+
return :date_select
|
289
|
+
end
|
290
|
+
|
291
|
+
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
292
|
+
return :select if column.type == :string && options.key?(:collection)
|
293
|
+
# Try 3: Assume the input name will be the same as the column type (e.g. string_input).
|
294
|
+
return column.type
|
295
|
+
else
|
296
|
+
return :select if options.key?(:collection)
|
297
|
+
return :password if method.to_s =~ /password/
|
298
|
+
return :string
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Get a column object for a specified attribute method - if possible.
|
303
|
+
def column_for(method) # @private
|
304
|
+
if @object.respond_to?(:column_for_attribute)
|
305
|
+
# Remove deprecation wrapper & review after Rails 5.0 ships
|
306
|
+
ActiveSupport::Deprecation.silence do
|
307
|
+
@object.column_for_attribute(method)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Takes the `:as` option and attempts to return the corresponding input
|
313
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
314
|
+
# top level `AwesomeInput` class (to allow the application to subclass
|
315
|
+
# and modify to suit), falling back to `Formtastic::Inputs::AwesomeInput`.
|
316
|
+
#
|
317
|
+
# Custom input namespaces to look into can be configured via the
|
318
|
+
# {Formtastic::FormBuilder.input_namespaces} configuration setting.
|
319
|
+
#
|
320
|
+
# @param [Symbol] as A symbol representing the type of input to render
|
321
|
+
# @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
|
322
|
+
# @return [Class] An input class constant
|
323
|
+
#
|
324
|
+
# @example Normal use
|
325
|
+
# input_class(:string) #=> Formtastic::Inputs::StringInput
|
326
|
+
# input_class(:date) #=> Formtastic::Inputs::DateInput
|
327
|
+
#
|
328
|
+
# @example When a top-level class is found
|
329
|
+
# input_class(:string) #=> StringInput
|
330
|
+
# input_class(:awesome) #=> AwesomeInput
|
331
|
+
# @see NamespacedClassFinder#find
|
332
|
+
def namespaced_input_class(as)
|
333
|
+
@input_class_finder ||= input_class_finder.new(self)
|
334
|
+
@input_class_finder.find(as)
|
335
|
+
rescue Formtastic::InputClassFinder::NotFoundError
|
336
|
+
raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}"
|
337
|
+
end
|
338
|
+
|
339
|
+
# @api private
|
340
|
+
# @deprecated Use {#namespaced_input_class} instead.
|
341
|
+
def input_class(as)
|
342
|
+
return namespaced_input_class(as) if input_class_finder
|
343
|
+
|
344
|
+
input_class_deprecation_warning(__method__)
|
345
|
+
|
346
|
+
@input_classes_cache ||= {}
|
347
|
+
@input_classes_cache[as] ||= begin
|
348
|
+
config = Rails.application.config
|
349
|
+
use_const_defined = config.respond_to?(:eager_load) ? config.eager_load : config.cache_classes
|
350
|
+
use_const_defined ? input_class_with_const_defined(as) : input_class_by_trying(as)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# @api private
|
355
|
+
# @deprecated Use {InputClassFinder#find} instead.
|
356
|
+
# prevent exceptions in production environment for better performance
|
357
|
+
def input_class_with_const_defined(as)
|
358
|
+
input_class_name = custom_input_class_name(as)
|
359
|
+
|
360
|
+
if ::Object.const_defined?(input_class_name)
|
361
|
+
input_class_name.constantize
|
362
|
+
elsif Formtastic::Inputs.const_defined?(input_class_name)
|
363
|
+
standard_input_class_name(as).constantize
|
364
|
+
else
|
365
|
+
raise Formtastic::UnknownInputError, "Unable to find input class #{input_class_name}"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# @api private
|
370
|
+
# @deprecated Use {InputClassFinder#find} instead.
|
371
|
+
# use auto-loading in development environment
|
372
|
+
def input_class_by_trying(as)
|
373
|
+
begin
|
374
|
+
custom_input_class_name(as).constantize
|
375
|
+
rescue NameError
|
376
|
+
standard_input_class_name(as).constantize
|
377
|
+
end
|
378
|
+
rescue NameError
|
379
|
+
raise Formtastic::UnknownInputError, "Unable to find input class for #{as}"
|
380
|
+
end
|
381
|
+
|
382
|
+
# @api private
|
383
|
+
# @deprecated Use {InputClassFinder#class_name} instead.
|
384
|
+
# :as => :string # => StringInput
|
385
|
+
def custom_input_class_name(as)
|
386
|
+
input_class_deprecation_warning(__method__)
|
387
|
+
"#{as.to_s.camelize}Input"
|
388
|
+
end
|
389
|
+
|
390
|
+
# @api private
|
391
|
+
# @deprecated Use {InputClassFinder#class_name} instead.
|
392
|
+
# :as => :string # => {Formtastic::Inputs::StringInput}
|
393
|
+
def standard_input_class_name(as)
|
394
|
+
input_class_deprecation_warning(__method__)
|
395
|
+
"Formtastic::Inputs::#{as.to_s.camelize}Input"
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
|
400
|
+
def input_class_deprecation_warning(method)
|
401
|
+
@input_class_deprecation_warned ||=
|
402
|
+
Formtastic.deprecation.deprecation_warning(method, INPUT_CLASS_DEPRECATION, caller(2))
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|