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.
Files changed (68) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +635 -0
  3. data/lib/formtastic.rb +24 -0
  4. data/lib/formtastic/form_builder.rb +75 -0
  5. data/lib/formtastic/helpers.rb +15 -0
  6. data/lib/formtastic/helpers/buttons_helper.rb +277 -0
  7. data/lib/formtastic/helpers/errors_helper.rb +124 -0
  8. data/lib/formtastic/helpers/fieldset_wrapper.rb +62 -0
  9. data/lib/formtastic/helpers/file_column_detection.rb +16 -0
  10. data/lib/formtastic/helpers/form_helper.rb +221 -0
  11. data/lib/formtastic/helpers/input_helper.rb +357 -0
  12. data/lib/formtastic/helpers/inputs_helper.rb +381 -0
  13. data/lib/formtastic/helpers/reflection.rb +12 -0
  14. data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
  15. data/lib/formtastic/html_attributes.rb +21 -0
  16. data/lib/formtastic/i18n.rb +32 -0
  17. data/lib/formtastic/inputs.rb +29 -0
  18. data/lib/formtastic/inputs/base.rb +50 -0
  19. data/lib/formtastic/inputs/base/associations.rb +33 -0
  20. data/lib/formtastic/inputs/base/choices.rb +88 -0
  21. data/lib/formtastic/inputs/base/collections.rb +94 -0
  22. data/lib/formtastic/inputs/base/database.rb +17 -0
  23. data/lib/formtastic/inputs/base/errors.rb +58 -0
  24. data/lib/formtastic/inputs/base/fileish.rb +23 -0
  25. data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
  26. data/lib/formtastic/inputs/base/hints.rb +31 -0
  27. data/lib/formtastic/inputs/base/html.rb +51 -0
  28. data/lib/formtastic/inputs/base/labelling.rb +53 -0
  29. data/lib/formtastic/inputs/base/naming.rb +54 -0
  30. data/lib/formtastic/inputs/base/options.rb +18 -0
  31. data/lib/formtastic/inputs/base/stringish.rb +30 -0
  32. data/lib/formtastic/inputs/base/timeish.rb +125 -0
  33. data/lib/formtastic/inputs/base/validations.rb +125 -0
  34. data/lib/formtastic/inputs/base/wrapping.rb +38 -0
  35. data/lib/formtastic/inputs/boolean_input.rb +87 -0
  36. data/lib/formtastic/inputs/check_boxes_input.rb +169 -0
  37. data/lib/formtastic/inputs/country_input.rb +66 -0
  38. data/lib/formtastic/inputs/date_input.rb +14 -0
  39. data/lib/formtastic/inputs/datetime_input.rb +9 -0
  40. data/lib/formtastic/inputs/email_input.rb +40 -0
  41. data/lib/formtastic/inputs/file_input.rb +42 -0
  42. data/lib/formtastic/inputs/hidden_input.rb +66 -0
  43. data/lib/formtastic/inputs/number_input.rb +72 -0
  44. data/lib/formtastic/inputs/numeric_input.rb +20 -0
  45. data/lib/formtastic/inputs/password_input.rb +40 -0
  46. data/lib/formtastic/inputs/phone_input.rb +41 -0
  47. data/lib/formtastic/inputs/radio_input.rb +146 -0
  48. data/lib/formtastic/inputs/search_input.rb +40 -0
  49. data/lib/formtastic/inputs/select_input.rb +208 -0
  50. data/lib/formtastic/inputs/string_input.rb +34 -0
  51. data/lib/formtastic/inputs/text_input.rb +47 -0
  52. data/lib/formtastic/inputs/time_input.rb +14 -0
  53. data/lib/formtastic/inputs/time_zone_input.rb +48 -0
  54. data/lib/formtastic/inputs/url_input.rb +40 -0
  55. data/lib/formtastic/localized_string.rb +96 -0
  56. data/lib/formtastic/railtie.rb +12 -0
  57. data/lib/formtastic/semantic_form_builder.rb +11 -0
  58. data/lib/formtastic/util.rb +25 -0
  59. data/lib/generators/formtastic/form/form_generator.rb +95 -0
  60. data/lib/generators/formtastic/install/install_generator.rb +23 -0
  61. data/lib/generators/templates/_form.html.erb +7 -0
  62. data/lib/generators/templates/_form.html.haml +5 -0
  63. data/lib/generators/templates/formtastic.css +145 -0
  64. data/lib/generators/templates/formtastic.rb +74 -0
  65. data/lib/generators/templates/formtastic_changes.css +14 -0
  66. data/lib/locale/en.yml +7 -0
  67. data/lib/tasks/verify_rcov.rb +44 -0
  68. 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
+