nuatt-formtastic 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.textile +635 -0
- data/lib/formtastic.rb +24 -0
- data/lib/formtastic/form_builder.rb +75 -0
- data/lib/formtastic/helpers.rb +15 -0
- data/lib/formtastic/helpers/buttons_helper.rb +277 -0
- data/lib/formtastic/helpers/errors_helper.rb +124 -0
- data/lib/formtastic/helpers/fieldset_wrapper.rb +62 -0
- data/lib/formtastic/helpers/file_column_detection.rb +16 -0
- data/lib/formtastic/helpers/form_helper.rb +221 -0
- data/lib/formtastic/helpers/input_helper.rb +357 -0
- data/lib/formtastic/helpers/inputs_helper.rb +381 -0
- data/lib/formtastic/helpers/reflection.rb +12 -0
- data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
- data/lib/formtastic/html_attributes.rb +21 -0
- data/lib/formtastic/i18n.rb +32 -0
- data/lib/formtastic/inputs.rb +29 -0
- data/lib/formtastic/inputs/base.rb +50 -0
- data/lib/formtastic/inputs/base/associations.rb +33 -0
- data/lib/formtastic/inputs/base/choices.rb +88 -0
- data/lib/formtastic/inputs/base/collections.rb +94 -0
- data/lib/formtastic/inputs/base/database.rb +17 -0
- data/lib/formtastic/inputs/base/errors.rb +58 -0
- data/lib/formtastic/inputs/base/fileish.rb +23 -0
- data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
- data/lib/formtastic/inputs/base/hints.rb +31 -0
- data/lib/formtastic/inputs/base/html.rb +51 -0
- data/lib/formtastic/inputs/base/labelling.rb +53 -0
- data/lib/formtastic/inputs/base/naming.rb +54 -0
- data/lib/formtastic/inputs/base/options.rb +18 -0
- data/lib/formtastic/inputs/base/stringish.rb +30 -0
- data/lib/formtastic/inputs/base/timeish.rb +125 -0
- data/lib/formtastic/inputs/base/validations.rb +125 -0
- data/lib/formtastic/inputs/base/wrapping.rb +38 -0
- data/lib/formtastic/inputs/boolean_input.rb +87 -0
- data/lib/formtastic/inputs/check_boxes_input.rb +169 -0
- data/lib/formtastic/inputs/country_input.rb +66 -0
- data/lib/formtastic/inputs/date_input.rb +14 -0
- data/lib/formtastic/inputs/datetime_input.rb +9 -0
- data/lib/formtastic/inputs/email_input.rb +40 -0
- data/lib/formtastic/inputs/file_input.rb +42 -0
- data/lib/formtastic/inputs/hidden_input.rb +66 -0
- data/lib/formtastic/inputs/number_input.rb +72 -0
- data/lib/formtastic/inputs/numeric_input.rb +20 -0
- data/lib/formtastic/inputs/password_input.rb +40 -0
- data/lib/formtastic/inputs/phone_input.rb +41 -0
- data/lib/formtastic/inputs/radio_input.rb +146 -0
- data/lib/formtastic/inputs/search_input.rb +40 -0
- data/lib/formtastic/inputs/select_input.rb +208 -0
- data/lib/formtastic/inputs/string_input.rb +34 -0
- data/lib/formtastic/inputs/text_input.rb +47 -0
- data/lib/formtastic/inputs/time_input.rb +14 -0
- data/lib/formtastic/inputs/time_zone_input.rb +48 -0
- data/lib/formtastic/inputs/url_input.rb +40 -0
- data/lib/formtastic/localized_string.rb +96 -0
- data/lib/formtastic/railtie.rb +12 -0
- data/lib/formtastic/semantic_form_builder.rb +11 -0
- data/lib/formtastic/util.rb +25 -0
- data/lib/generators/formtastic/form/form_generator.rb +95 -0
- data/lib/generators/formtastic/install/install_generator.rb +23 -0
- data/lib/generators/templates/_form.html.erb +7 -0
- data/lib/generators/templates/_form.html.haml +5 -0
- data/lib/generators/templates/formtastic.css +145 -0
- data/lib/generators/templates/formtastic.rb +74 -0
- data/lib/generators/templates/formtastic_changes.css +14 -0
- data/lib/locale/en.yml +7 -0
- data/lib/tasks/verify_rcov.rb +44 -0
- metadata +206 -19
@@ -0,0 +1,62 @@
|
|
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) #:nodoc:
|
25
|
+
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
|
26
|
+
html_options = args.extract_options!
|
27
|
+
|
28
|
+
legend = html_options.dup.delete(:name).to_s
|
29
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
|
30
|
+
legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
contents = if template.respond_to?(:is_haml?) && template.is_haml?
|
34
|
+
template.capture_haml(&block)
|
35
|
+
else
|
36
|
+
template.capture(&block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
41
|
+
contents = contents.join if contents.respond_to?(:join)
|
42
|
+
fieldset = template.content_tag(:fieldset,
|
43
|
+
Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
|
44
|
+
html_options.except(:builder, :parent)
|
45
|
+
)
|
46
|
+
|
47
|
+
fieldset
|
48
|
+
end
|
49
|
+
|
50
|
+
# Gets the nested_child_index value from the parent builder. It returns a hash with each
|
51
|
+
# association that the parent builds.
|
52
|
+
def parent_child_index(parent) #:nodoc:
|
53
|
+
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
54
|
+
|
55
|
+
child = parent[:for]
|
56
|
+
child = child.first if child.respond_to?(:first)
|
57
|
+
duck[child].to_i + 1
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
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,221 @@
|
|
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
|
+
# Wrapper around Rails' own `form_for` helper to set the `:builder` option to
|
62
|
+
# `Formtastic::FormBuilder` and to set some class names on the `<form>` tag such as
|
63
|
+
# `formtastic` and the downcased and underscored model name (eg `post`).
|
64
|
+
#
|
65
|
+
# See Rails' `form_for` for full documentation of all supported arguments and options.
|
66
|
+
#
|
67
|
+
# Since `Formtastic::FormBuilder` subclasses Rails' own FormBuilder, you have access to all
|
68
|
+
# of Rails' built-in form helper methods such as `text_field`, `check_box`, `radio_button`,
|
69
|
+
# etc **in addition to** all of Formtastic's additional helpers like {InputsHelper#inputs inputs},
|
70
|
+
# {InputsHelper#input input}, {ButtonsHelper#buttons buttons}, etc.
|
71
|
+
#
|
72
|
+
# Most of the examples below have been adapted from the examples found in the Rails `form_for`
|
73
|
+
# documentation.
|
74
|
+
#
|
75
|
+
# @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html Rails' FormHelper documentation (`form_for`, etc)
|
76
|
+
# @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html Rails' FormBuilder documentaion (`text_field`, etc)
|
77
|
+
# @see FormHelper The overview of the FormBuilder module
|
78
|
+
#
|
79
|
+
# @example Resource-oriented form generation
|
80
|
+
# <%= semantic_form_for @user do |f| %>
|
81
|
+
# <%= f.input :name %>
|
82
|
+
# <%= f.input :email %>
|
83
|
+
# <%= f.input :password %>
|
84
|
+
# <% end %>
|
85
|
+
#
|
86
|
+
# @example Generic form generation
|
87
|
+
# <%= semantic_form_for :user do |f| %>
|
88
|
+
# <%= f.input :name %>
|
89
|
+
# <%= f.input :email %>
|
90
|
+
# <%= f.input :password %>
|
91
|
+
# <% end %>
|
92
|
+
#
|
93
|
+
# @example Resource-oriented with custom URL
|
94
|
+
# <%= semantic_form_for(@post, :url => super_post_path(@post)) do |f| %>
|
95
|
+
# ...
|
96
|
+
# <% end %>
|
97
|
+
#
|
98
|
+
# @example Resource-oriented with namespaced routes
|
99
|
+
# <%= semantic_form_for([:admin, @post]) do |f| %>
|
100
|
+
# ...
|
101
|
+
# <% end %>
|
102
|
+
#
|
103
|
+
# @example Resource-oriented with nested routes
|
104
|
+
# <%= semantic_form_for([@user, @post]) do |f| %>
|
105
|
+
# ...
|
106
|
+
# <% end %>
|
107
|
+
#
|
108
|
+
# @example Rename the resource
|
109
|
+
# <%= semantic_form_for(@post, :as => :article) do |f| %>
|
110
|
+
# ...
|
111
|
+
# <% end %>
|
112
|
+
#
|
113
|
+
# @example Remote forms (unobtrusive JavaScript)
|
114
|
+
# <%= semantic_form_for(@post, :remote => true) do |f| %>
|
115
|
+
# ...
|
116
|
+
# <% end %>
|
117
|
+
#
|
118
|
+
# @example Namespaced forms all multiple Formtastic forms to exist on the one page without DOM id clashes and invalid HTML documents.
|
119
|
+
# <%= semantic_form_for(@post, :namespace => 'first') do |f| %>
|
120
|
+
# ...
|
121
|
+
# <% end %>
|
122
|
+
#
|
123
|
+
# @example Accessing a mixture of Formtastic helpers and Rails FormBuilder helpers.
|
124
|
+
# <%= semantic_form_for(@post) do |f| %>
|
125
|
+
# <%= f.input :title %>
|
126
|
+
# <%= f.input :body %>
|
127
|
+
# <li class="something-custom">
|
128
|
+
# <label><%= f.check_box :published %></label>
|
129
|
+
# </li>
|
130
|
+
# <% end %>
|
131
|
+
#
|
132
|
+
# @param record_or_name_or_array
|
133
|
+
# Same behavior as Rails' `form_for`
|
134
|
+
#
|
135
|
+
# @option *args [Hash] :html
|
136
|
+
# Pass HTML attributes into the `<form>` tag. Same behavior as Rails' `form_for`, except we add in some of our own classes.
|
137
|
+
#
|
138
|
+
# @option *args [String, Hash] :url
|
139
|
+
# 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`.
|
140
|
+
#
|
141
|
+
# @option *args [String] :namespace
|
142
|
+
def semantic_form_for(record_or_name_or_array, *args, &proc)
|
143
|
+
form_helper_wrapper(:form_for, record_or_name_or_array, *args, &proc)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Wrapper around Rails' own `fields_for` helper to set the `:builder` option to
|
147
|
+
# `Formtastic::FormBuilder`.
|
148
|
+
#
|
149
|
+
# @see #semantic_form_for
|
150
|
+
def semantic_fields_for(record_or_name_or_array, *args, &proc)
|
151
|
+
form_helper_wrapper(:fields_for, record_or_name_or_array, *args, &proc)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Wrapper around Rails' own `remote_form_for` helper to set the `:builder` option to
|
155
|
+
# `Formtastic::FormBuilder` and to set some class names on the `<form>` tag such as
|
156
|
+
# `formtastic` and the downcased and underscored model name.
|
157
|
+
#
|
158
|
+
# @see #semantic_form_for
|
159
|
+
# @todo why isn't YARD rendering this method?
|
160
|
+
def semantic_remote_form_for(record_or_name_or_array, *args, &proc)
|
161
|
+
form_helper_wrapper(:remote_form_for, record_or_name_or_array, *args, &proc)
|
162
|
+
end
|
163
|
+
|
164
|
+
def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
|
165
|
+
options = args.extract_options!
|
166
|
+
if respond_to? :remote_form_for
|
167
|
+
semantic_remote_form_for_real(record_or_name_or_array, *(args << options), &proc)
|
168
|
+
else
|
169
|
+
options[:remote] = true
|
170
|
+
semantic_form_for(record_or_name_or_array, *(args << options), &proc)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
alias :semantic_remote_form_for_real :semantic_remote_form_for
|
174
|
+
alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
|
175
|
+
alias :semantic_form_remote_for :semantic_remote_form_for
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
# @todo pretty sure some of this (like HTML classes and record naming are exlusive to `form_for`)
|
180
|
+
def form_helper_wrapper(rails_helper_method_name, record_or_name_or_array, *args, &proc)
|
181
|
+
options = args.extract_options!
|
182
|
+
options[:builder] ||= @@builder
|
183
|
+
options[:html] ||= {}
|
184
|
+
@@builder.custom_namespace = options[:namespace].to_s
|
185
|
+
|
186
|
+
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
187
|
+
|
188
|
+
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|
189
|
+
class_names << @@default_form_class
|
190
|
+
class_names << case record_or_name_or_array
|
191
|
+
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
192
|
+
when Array then options[:as] || singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
|
193
|
+
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
194
|
+
end
|
195
|
+
options[:html][:class] = class_names.join(" ")
|
196
|
+
|
197
|
+
with_custom_field_error_proc do
|
198
|
+
self.send(rails_helper_method_name, record_or_name_or_array, *(args << options), &proc)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Override the default ActiveRecordHelper behaviour of wrapping the input.
|
203
|
+
# This gets taken care of semantically by adding an error class to the LI tag
|
204
|
+
# containing the input.
|
205
|
+
# @private
|
206
|
+
FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
|
207
|
+
html_tag
|
208
|
+
end
|
209
|
+
|
210
|
+
def with_custom_field_error_proc(&block)
|
211
|
+
default_field_error_proc = ::ActionView::Base.field_error_proc
|
212
|
+
::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
|
213
|
+
yield
|
214
|
+
ensure
|
215
|
+
::ActionView::Base.field_error_proc = default_field_error_proc
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
module Formtastic
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# {#input} is used to render all content (labels, form widgets, error messages, hints, etc) for
|
5
|
+
# a single form input (or field), usually representing a single method or attribute on the
|
6
|
+
# form's object or model.
|
7
|
+
#
|
8
|
+
# The content is wrapped in an `<li>` tag, so it's usually called inside an {Formtastic::Helpers::InputsHelper#inputs inputs} block
|
9
|
+
# (which renders an `<ol>` inside a `<fieldset>`), which should be inside a {Formtastic::Helpers::FormHelper#semantic_form_for `semantic_form_for`}
|
10
|
+
# block:
|
11
|
+
#
|
12
|
+
# <%= semantic_form_for @post do |f| %>
|
13
|
+
# <%= f.inputs do %>
|
14
|
+
# <%= f.input :title %>
|
15
|
+
# <%= f.input :body %>
|
16
|
+
# <% end %>
|
17
|
+
# <% end %>
|
18
|
+
#
|
19
|
+
# The HTML output will be something like:
|
20
|
+
#
|
21
|
+
# <form class="formtastic" method="post" action="...">
|
22
|
+
# <fieldset>
|
23
|
+
# <ol>
|
24
|
+
# <li class="string required" id="post_title_input">
|
25
|
+
# ...
|
26
|
+
# </li>
|
27
|
+
# <li class="text required" id="post_body_input">
|
28
|
+
# ...
|
29
|
+
# </li>
|
30
|
+
# </ol>
|
31
|
+
# </fieldset>
|
32
|
+
# </form>
|
33
|
+
#
|
34
|
+
# @see #input
|
35
|
+
# @see Formtastic::Helpers::InputsHelper#inputs
|
36
|
+
# @see Formtastic::Helpers::FormHelper#semantic_form_for
|
37
|
+
module InputHelper
|
38
|
+
include Formtastic::Helpers::Reflection
|
39
|
+
include Formtastic::Helpers::FileColumnDetection
|
40
|
+
|
41
|
+
# Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
|
42
|
+
# an `<li>` wrapper tag with appropriate `class` and `id` attribute hooks for CSS and JS.
|
43
|
+
# In many cases, the contents of the wrapper will be as simple as a `<label>` and an `<input>`:
|
44
|
+
#
|
45
|
+
# <%= f.input :title, :as => :string, :required => true %>
|
46
|
+
#
|
47
|
+
# <li class="string required" id="post_title_input">
|
48
|
+
# <label for="post_title">Title<abbr title="Required">*</abbr></label>
|
49
|
+
# <input type="text" name="post[title]" value="" id="post_title" required="required">
|
50
|
+
# </li>
|
51
|
+
#
|
52
|
+
# In other cases (like a series of checkboxes for a `has_many` relationship), the wrapper may
|
53
|
+
# include more complex markup, like a nested `<fieldset>` with a `<legend>` and an `<ol>` of
|
54
|
+
# checkbox/label pairs for each choice:
|
55
|
+
#
|
56
|
+
# <%= f.input :categories, :as => :check_boxes, :collection => Category.active.ordered %>
|
57
|
+
#
|
58
|
+
# <li class="check_boxes" id="post_categories_input">
|
59
|
+
# <fieldset>
|
60
|
+
# <legend>Categories</legend>
|
61
|
+
# <ol>
|
62
|
+
# <li>
|
63
|
+
# <label><input type="checkbox" name="post[categories][1]" value="1"> Ruby</label>
|
64
|
+
# </li>
|
65
|
+
# <li>
|
66
|
+
# <label><input type="checkbox" name="post[categories][2]" value="2"> Rails</label>
|
67
|
+
# </li>
|
68
|
+
# <li>
|
69
|
+
# <label><input type="checkbox" name="post[categories][2]" value="2"> Awesome</label>
|
70
|
+
# </li>
|
71
|
+
# </ol>
|
72
|
+
# </fieldset>
|
73
|
+
# </li>
|
74
|
+
#
|
75
|
+
# Sensible defaults for all options are guessed by looking at the method name, database column
|
76
|
+
# information, association information, validation information, etc. For example, a `:string`
|
77
|
+
# database column will map to a `:string` input, but if the method name contains 'email', will
|
78
|
+
# map to an `:email` input instead. `belongs_to` associations will have a `:select` input, etc.
|
79
|
+
#
|
80
|
+
# Formtastic supports many different styles of inputs, and you can/should override the default
|
81
|
+
# with the `:as` option. Internally, the symbol is used to map to a protected method
|
82
|
+
# responsible for the details. For example, `:as => :string` will map to `string_input`,
|
83
|
+
# defined in a module of the same name. Detailed documentation for each input style and it's
|
84
|
+
# supported options is available on the `*_input` method in each module (links provided below).
|
85
|
+
#
|
86
|
+
# Available input styles:
|
87
|
+
#
|
88
|
+
# * `:boolean` (see {Inputs::BooleanInput})
|
89
|
+
# * `:check_boxes` (see {Inputs::CheckBoxesInput})
|
90
|
+
# * `:country` (see {Inputs::CountryInput})
|
91
|
+
# * `:datetime` (see {Inputs::DatetimeInput})
|
92
|
+
# * `:date` (see {Inputs::DateInput})
|
93
|
+
# * `:email` (see {Inputs::EmailInput})
|
94
|
+
# * `:file` (see {Inputs::FileInput})
|
95
|
+
# * `:hidden` (see {Inputs::HiddenInput})
|
96
|
+
# * `:number` (see {Inputs::NumberInput})
|
97
|
+
# * `:password` (see {Inputs::PasswordInput})
|
98
|
+
# * `:phone` (see {Inputs::PhoneInput})
|
99
|
+
# * `:radio` (see {Inputs::RadioInput})
|
100
|
+
# * `:search` (see {Inputs::SearchInput})
|
101
|
+
# * `:select` (see {Inputs::SelectInput})
|
102
|
+
# * `:string` (see {Inputs::StringInput})
|
103
|
+
# * `:text` (see {Inputs::TextInput})
|
104
|
+
# * `:time_zone` (see {Inputs::TimeZoneInput})
|
105
|
+
# * `:time` (see {Inputs::TimeInput})
|
106
|
+
# * `:url` (see {Inputs::UrlInput})
|
107
|
+
#
|
108
|
+
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
|
109
|
+
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
|
110
|
+
# namespace StringInput, meaning you can subclass and modify `Formtastic::Inputs::StringInput`
|
111
|
+
# in `app/inputs/`. This also means you can create your own new input types in `app/inputs/`.
|
112
|
+
#
|
113
|
+
# @todo document the "guessing" of input style
|
114
|
+
#
|
115
|
+
# @param [Symbol] method
|
116
|
+
# The database column or method name on the form object that this input represents
|
117
|
+
#
|
118
|
+
# @option options :as [Symbol]
|
119
|
+
# Override the style of input should be rendered
|
120
|
+
#
|
121
|
+
# @option options :label [String, Symbol, false]
|
122
|
+
# Override the label text
|
123
|
+
#
|
124
|
+
# @option options :hint [String, Symbol, false]
|
125
|
+
# Override hint text
|
126
|
+
#
|
127
|
+
# @option options :required [Boolean]
|
128
|
+
# 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>`
|
129
|
+
#
|
130
|
+
# @option options :input_html [Hash]
|
131
|
+
# Override or add to the HTML attributes to be passed down to the `<input>` tag
|
132
|
+
#
|
133
|
+
# @option options :wrapper_html [Hash]
|
134
|
+
# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
|
135
|
+
#
|
136
|
+
# @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
|
137
|
+
# Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
|
138
|
+
#
|
139
|
+
# @option options :label_method [Symbol, Proc]
|
140
|
+
# Override the method called on each object in the `:collection` for use as the `<label>` content (`:check_boxes` & `:radio` inputs) or `<option>` content (`:select` inputs)
|
141
|
+
#
|
142
|
+
# @option options :value_method [Symbol, Proc]
|
143
|
+
# Override the method called on each object in the `:collection` for use as the `value` attribute in the `<input>` (`:check_boxes` & `:radio` inputs) or `<option>` (`:select` inputs)
|
144
|
+
#
|
145
|
+
# @option options :hint_class [String]
|
146
|
+
# Override the `class` attribute applied to the `<p>` tag used when a `:hint` is rendered for an input
|
147
|
+
#
|
148
|
+
# @option options :error_class [String]
|
149
|
+
# Override the `class` attribute applied to the `<p>` or `<ol>` tag used when inline errors are rendered for an input
|
150
|
+
#
|
151
|
+
# @option options :multiple [Boolean]
|
152
|
+
# 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)
|
153
|
+
#
|
154
|
+
# @option options :group_by [Symbol]
|
155
|
+
# TODO will probably be deprecated
|
156
|
+
#
|
157
|
+
# @option options :find_options [Symbol]
|
158
|
+
# TODO will probably be deprecated
|
159
|
+
#
|
160
|
+
# @option options :group_label_method [Symbol]
|
161
|
+
# TODO will probably be deprecated
|
162
|
+
#
|
163
|
+
# @option options :include_blank [Boolean]
|
164
|
+
# Specify if a `:select` input should include a blank option or not (defaults to `include_blank_for_select_by_default` configuration)
|
165
|
+
#
|
166
|
+
# @option options :prompt [String]
|
167
|
+
# Specify the text in the first ('blank') `:select` input `<option>` to prompt a user to make a selection (implicitly sets `:include_blank` to `true`)
|
168
|
+
#
|
169
|
+
# @todo Can we kill `:hint_class` & `:error_class`? What's the use case for input-by-input? Shift to config or burn!
|
170
|
+
# @todo Can we kill `:group_by` & `:group_label_method`? Should be done with :collection => grouped_options_for_select(...)
|
171
|
+
# @todo Can we kill `:find_options`? Should be done with MyModel.some_scope.where(...).order(...).whatever_scope
|
172
|
+
# @todo Can we kill `:label`, `:hint` & `:prompt`? All strings could be shifted to i18n!
|
173
|
+
#
|
174
|
+
# @example Accept all default options
|
175
|
+
# <%= f.input :title %>
|
176
|
+
#
|
177
|
+
# @example Change the input type
|
178
|
+
# <%= f.input :title, :as => :string %>
|
179
|
+
#
|
180
|
+
# @example Changing the label with a String
|
181
|
+
# <%= f.input :title, :label => "Post title" %>
|
182
|
+
#
|
183
|
+
# @example Disabling the label with false, even if an i18n translation exists
|
184
|
+
# <%= f.input :title, :label => false %>
|
185
|
+
#
|
186
|
+
# @example Changing the hint with a String
|
187
|
+
# <%= f.input :title, :hint => "Every post needs a title!" %>
|
188
|
+
#
|
189
|
+
# @example Disabling the hint with false, even if an i18n translation exists
|
190
|
+
# <%= f.input :title, :hint => false %>
|
191
|
+
#
|
192
|
+
# @example Marking a field as required or not (even if validations do not enforce it)
|
193
|
+
# <%= f.input :title, :required => true %>
|
194
|
+
# <%= f.input :title, :required => false %>
|
195
|
+
#
|
196
|
+
# @example Changing or adding to HTML attributes in the main `<input>` or `<select>` tag
|
197
|
+
# <%= f.input :title, :input_html => { :onchange => "somethingAwesome();", :class => 'awesome' } %>
|
198
|
+
#
|
199
|
+
# @example Changing or adding to HTML attributes in the wrapper `<li>` tag
|
200
|
+
# <%= f.input :title, :wrapper_html => { :class => "important-input" } %>
|
201
|
+
#
|
202
|
+
# @example Changing the association choices with `:collection`
|
203
|
+
# <%= f.input :author, :collection => User.active %>
|
204
|
+
# <%= f.input :categories, :collection => Category.where(...).order(...) %>
|
205
|
+
# <%= f.input :status, :collection => ["Draft", "Published"] %>
|
206
|
+
# <%= f.input :status, :collection => [:draft, :published] %>
|
207
|
+
# <%= f.input :status, :collection => {"Draft" => 0, "Published" => 1} %>
|
208
|
+
# <%= f.input :status, :collection => OrderedHash.new("Draft" => 0, "Published" => 1) %>
|
209
|
+
# <%= f.input :status, :collection => [["Draft", 0], ["Published", 1]] %>
|
210
|
+
# <%= f.input :status, :collection => grouped_options_for_select(...) %>
|
211
|
+
# <%= f.input :status, :collection => options_for_select(...) %>
|
212
|
+
#
|
213
|
+
# @example Specifying if a `:select` should allow multiple selections:
|
214
|
+
# <%= f.input :cateogies, :as => :select, :multiple => true %>
|
215
|
+
# <%= f.input :cateogies, :as => :select, :multiple => false %>
|
216
|
+
#
|
217
|
+
# @example Specifying if a `:select` should have a 'blank' first option to prompt selection:
|
218
|
+
# <%= f.input :author, :as => :select, :include_blank => true %>
|
219
|
+
# <%= f.input :author, :as => :select, :include_blank => false %>
|
220
|
+
#
|
221
|
+
# @example Specifying the text for a `:select` input's 'blank' first option to prompt selection:
|
222
|
+
# <%= f.input :author, :as => :select, :prompt => "Select an Author" %>
|
223
|
+
#
|
224
|
+
# @example Modifying an input to suit your needs in `app/inputs`:
|
225
|
+
# class StringInput < Formtastic::Inputs::StringInput
|
226
|
+
# def to_html
|
227
|
+
# puts "this is my custom version of StringInput"
|
228
|
+
# super
|
229
|
+
# end
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# @example Creating your own input to suit your needs in `app/inputs`:
|
233
|
+
# class DatePickerInput
|
234
|
+
# include Formtastic::Inputs::Base
|
235
|
+
# def to_html
|
236
|
+
# # ...
|
237
|
+
# end
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# @example Providing HTML5 placeholder text through i18n:
|
241
|
+
# en:
|
242
|
+
# formtastic:
|
243
|
+
# placeholders:
|
244
|
+
# user:
|
245
|
+
# email: "you@yours.com"
|
246
|
+
# first_name: "Joe"
|
247
|
+
# last_name: "Smith"
|
248
|
+
#
|
249
|
+
# @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
|
250
|
+
# @todo More i18n examples.
|
251
|
+
def input(method, options = {})
|
252
|
+
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
253
|
+
options[:as] ||= default_input_type(method, options)
|
254
|
+
|
255
|
+
klass = input_class(options[:as])
|
256
|
+
klass.new(self, template, @object, @object_name, method, options).to_html
|
257
|
+
end
|
258
|
+
|
259
|
+
protected
|
260
|
+
|
261
|
+
# For methods that have a database column, take a best guess as to what the input method
|
262
|
+
# should be. In most cases, it will just return the column type (eg :string), but for special
|
263
|
+
# cases it will simplify (like the case of :integer, :float & :decimal to :number), or do
|
264
|
+
# something different (like :password and :select).
|
265
|
+
#
|
266
|
+
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
267
|
+
# default is a :string, a similar behaviour to Rails' scaffolding.
|
268
|
+
def default_input_type(method, options = {}) #:nodoc:
|
269
|
+
if column = column_for(method)
|
270
|
+
# Special cases where the column type doesn't map to an input method.
|
271
|
+
case column.type
|
272
|
+
when :string
|
273
|
+
return :password if method.to_s =~ /password/
|
274
|
+
return :country if method.to_s =~ /country$/
|
275
|
+
return :time_zone if method.to_s =~ /time_zone/
|
276
|
+
return :email if method.to_s =~ /email/
|
277
|
+
return :url if method.to_s =~ /^url$|^website$|_url$/
|
278
|
+
return :phone if method.to_s =~ /(phone|fax)/
|
279
|
+
return :search if method.to_s =~ /^search$/
|
280
|
+
when :integer
|
281
|
+
return :select if reflection_for(method)
|
282
|
+
return :number
|
283
|
+
when :float, :decimal
|
284
|
+
return :number
|
285
|
+
when :timestamp
|
286
|
+
return :datetime
|
287
|
+
end
|
288
|
+
|
289
|
+
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
290
|
+
return :select if column.type == :string && options.key?(:collection)
|
291
|
+
# Try 3: Assume the input name will be the same as the column type (e.g. string_input).
|
292
|
+
return column.type
|
293
|
+
else
|
294
|
+
if @object
|
295
|
+
return :select if reflection_for(method)
|
296
|
+
|
297
|
+
return :file if is_file?(method, options)
|
298
|
+
end
|
299
|
+
|
300
|
+
return :select if options.key?(:collection)
|
301
|
+
return :password if method.to_s =~ /password/
|
302
|
+
return :string
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Get a column object for a specified attribute method - if possible.
|
307
|
+
def column_for(method) #:nodoc:
|
308
|
+
@object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Takes the `:as` option and attempts to return the corresponding input class. In the case of
|
312
|
+
# `:as => :string` it will first attempt to find a top level `StringInput` class (to allow the
|
313
|
+
# application to subclass and modify to suit), falling back to `Formtastic::Inputs::StringInput`.
|
314
|
+
#
|
315
|
+
# This also means that the application can define it's own custom inputs in the top level
|
316
|
+
# namespace (eg `DatepickerInput`).
|
317
|
+
#
|
318
|
+
# @param [Symbol] as A symbol representing the type of input to render
|
319
|
+
# @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
|
320
|
+
# @return [Class] An input class constant
|
321
|
+
#
|
322
|
+
# @example Normal use
|
323
|
+
# input_class(:string) #=> Formtastic::Inputs::StringInput
|
324
|
+
# input_class(:date) #=> Formtastic::Inputs::DateInput
|
325
|
+
#
|
326
|
+
# @example When a top-level class is found
|
327
|
+
# input_class(:string) #=> StringInput
|
328
|
+
# input_class(:awesome) #=> AwesomeInput
|
329
|
+
def input_class(as)
|
330
|
+
@input_classes_cache ||= {}
|
331
|
+
@input_classes_cache[as] ||= begin
|
332
|
+
begin
|
333
|
+
begin
|
334
|
+
custom_input_class_name(as).constantize
|
335
|
+
rescue NameError
|
336
|
+
standard_input_class_name(as).constantize
|
337
|
+
end
|
338
|
+
rescue NameError
|
339
|
+
raise Formtastic::UnknownInputError
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# :as => :string # => StringInput
|
345
|
+
def custom_input_class_name(as)
|
346
|
+
"#{as.to_s.camelize}Input"
|
347
|
+
end
|
348
|
+
|
349
|
+
# :as => :string # => Formtastic::Inputs::StringInput
|
350
|
+
def standard_input_class_name(as)
|
351
|
+
"Formtastic::Inputs::#{as.to_s.camelize}Input"
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|