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.
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
+