formtastic 1.2.4 → 3.1.5
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/.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
|