nuatt-formtastic 0.2.2 → 0.2.3
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.
- 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
|
+
|