formtastic 1.2.4 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (189) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +46 -0
  5. data/.yardopts +1 -0
  6. data/Appraisals +43 -0
  7. data/CHANGELOG +54 -0
  8. data/DEPRECATIONS +52 -0
  9. data/Gemfile +3 -0
  10. data/README.md +629 -0
  11. data/RELEASE_PROCESS +6 -0
  12. data/Rakefile +35 -0
  13. data/app/assets/stylesheets/formtastic.css +289 -0
  14. data/app/assets/stylesheets/formtastic_ie6.css +33 -0
  15. data/app/assets/stylesheets/formtastic_ie7.css +23 -0
  16. data/formtastic.gemspec +42 -0
  17. data/gemfiles/rails_3.2.gemfile +9 -0
  18. data/gemfiles/rails_4.0.4.gemfile +8 -0
  19. data/gemfiles/rails_4.1.gemfile +8 -0
  20. data/gemfiles/rails_4.2.gemfile +8 -0
  21. data/gemfiles/rails_4.gemfile +8 -0
  22. data/gemfiles/rails_5.0.gemfile +8 -0
  23. data/gemfiles/rails_edge.gemfile +15 -0
  24. data/lib/formtastic.rb +40 -1945
  25. data/lib/formtastic/action_class_finder.rb +18 -0
  26. data/lib/formtastic/actions.rb +11 -0
  27. data/lib/formtastic/actions/base.rb +156 -0
  28. data/lib/formtastic/actions/button_action.rb +67 -0
  29. data/lib/formtastic/actions/buttonish.rb +17 -0
  30. data/lib/formtastic/actions/input_action.rb +70 -0
  31. data/lib/formtastic/actions/link_action.rb +88 -0
  32. data/lib/formtastic/deprecation.rb +42 -0
  33. data/lib/formtastic/engine.rb +11 -0
  34. data/lib/formtastic/form_builder.rb +124 -0
  35. data/lib/formtastic/helpers.rb +16 -0
  36. data/lib/formtastic/helpers/action_helper.rb +162 -0
  37. data/lib/formtastic/helpers/actions_helper.rb +168 -0
  38. data/lib/formtastic/helpers/enum.rb +13 -0
  39. data/lib/formtastic/helpers/errors_helper.rb +81 -0
  40. data/lib/formtastic/helpers/fieldset_wrapper.rb +80 -0
  41. data/lib/formtastic/helpers/file_column_detection.rb +16 -0
  42. data/lib/formtastic/helpers/form_helper.rb +203 -0
  43. data/lib/formtastic/helpers/input_helper.rb +407 -0
  44. data/lib/formtastic/helpers/inputs_helper.rb +411 -0
  45. data/lib/formtastic/helpers/reflection.rb +37 -0
  46. data/lib/formtastic/html_attributes.rb +32 -0
  47. data/lib/formtastic/i18n.rb +4 -2
  48. data/lib/formtastic/input_class_finder.rb +18 -0
  49. data/lib/formtastic/inputs.rb +39 -0
  50. data/lib/formtastic/inputs/base.rb +76 -0
  51. data/lib/formtastic/inputs/base/associations.rb +31 -0
  52. data/lib/formtastic/inputs/base/choices.rb +108 -0
  53. data/lib/formtastic/inputs/base/collections.rb +159 -0
  54. data/lib/formtastic/inputs/base/database.rb +22 -0
  55. data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
  56. data/lib/formtastic/inputs/base/errors.rb +58 -0
  57. data/lib/formtastic/inputs/base/fileish.rb +23 -0
  58. data/lib/formtastic/inputs/base/hints.rb +31 -0
  59. data/lib/formtastic/inputs/base/html.rb +53 -0
  60. data/lib/formtastic/inputs/base/labelling.rb +52 -0
  61. data/lib/formtastic/inputs/base/naming.rb +42 -0
  62. data/lib/formtastic/inputs/base/numeric.rb +50 -0
  63. data/lib/formtastic/inputs/base/options.rb +17 -0
  64. data/lib/formtastic/inputs/base/placeholder.rb +17 -0
  65. data/lib/formtastic/inputs/base/stringish.rb +38 -0
  66. data/lib/formtastic/inputs/base/timeish.rb +241 -0
  67. data/lib/formtastic/inputs/base/validations.rb +215 -0
  68. data/lib/formtastic/inputs/base/wrapping.rb +50 -0
  69. data/lib/formtastic/inputs/boolean_input.rb +118 -0
  70. data/lib/formtastic/inputs/check_boxes_input.rb +197 -0
  71. data/lib/formtastic/inputs/color_input.rb +42 -0
  72. data/lib/formtastic/inputs/country_input.rb +86 -0
  73. data/lib/formtastic/inputs/datalist_input.rb +41 -0
  74. data/lib/formtastic/inputs/date_picker_input.rb +93 -0
  75. data/lib/formtastic/inputs/date_select_input.rb +34 -0
  76. data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
  77. data/lib/formtastic/inputs/datetime_select_input.rb +12 -0
  78. data/lib/formtastic/inputs/email_input.rb +41 -0
  79. data/lib/formtastic/inputs/file_input.rb +42 -0
  80. data/lib/formtastic/inputs/hidden_input.rb +62 -0
  81. data/lib/formtastic/inputs/number_input.rb +88 -0
  82. data/lib/formtastic/inputs/password_input.rb +41 -0
  83. data/lib/formtastic/inputs/phone_input.rb +42 -0
  84. data/lib/formtastic/inputs/radio_input.rb +163 -0
  85. data/lib/formtastic/inputs/range_input.rb +95 -0
  86. data/lib/formtastic/inputs/search_input.rb +41 -0
  87. data/lib/formtastic/inputs/select_input.rb +235 -0
  88. data/lib/formtastic/inputs/string_input.rb +36 -0
  89. data/lib/formtastic/inputs/text_input.rb +48 -0
  90. data/lib/formtastic/inputs/time_picker_input.rb +99 -0
  91. data/lib/formtastic/inputs/time_select_input.rb +38 -0
  92. data/lib/formtastic/inputs/time_zone_input.rb +58 -0
  93. data/lib/formtastic/inputs/url_input.rb +41 -0
  94. data/lib/formtastic/localized_string.rb +17 -0
  95. data/lib/formtastic/localizer.rb +152 -0
  96. data/lib/formtastic/namespaced_class_finder.rb +99 -0
  97. data/lib/formtastic/util.rb +35 -16
  98. data/lib/formtastic/version.rb +3 -0
  99. data/lib/generators/formtastic/form/form_generator.rb +64 -37
  100. data/lib/generators/formtastic/input/input_generator.rb +46 -0
  101. data/lib/generators/formtastic/install/install_generator.rb +13 -5
  102. data/lib/generators/templates/_form.html.erb +10 -4
  103. data/lib/generators/templates/_form.html.haml +8 -4
  104. data/lib/generators/templates/_form.html.slim +8 -0
  105. data/lib/generators/templates/formtastic.rb +77 -44
  106. data/lib/generators/templates/input.rb +19 -0
  107. data/lib/locale/en.yml +3 -0
  108. data/sample/basic_inputs.html +224 -0
  109. data/sample/config.ru +69 -0
  110. data/sample/index.html +14 -0
  111. data/spec/action_class_finder_spec.rb +12 -0
  112. data/spec/actions/button_action_spec.rb +63 -0
  113. data/spec/actions/generic_action_spec.rb +521 -0
  114. data/spec/actions/input_action_spec.rb +59 -0
  115. data/spec/actions/link_action_spec.rb +92 -0
  116. data/spec/builder/custom_builder_spec.rb +116 -0
  117. data/spec/builder/error_proc_spec.rb +27 -0
  118. data/spec/builder/semantic_fields_for_spec.rb +142 -0
  119. data/spec/fast_spec_helper.rb +12 -0
  120. data/spec/generators/formtastic/form/form_generator_spec.rb +131 -0
  121. data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
  122. data/spec/generators/formtastic/install/install_generator_spec.rb +47 -0
  123. data/spec/helpers/action_helper_spec.rb +19 -0
  124. data/spec/helpers/actions_helper_spec.rb +143 -0
  125. data/spec/helpers/form_helper_spec.rb +218 -0
  126. data/spec/helpers/input_helper_spec.rb +6 -0
  127. data/spec/helpers/inputs_helper_spec.rb +655 -0
  128. data/spec/helpers/namespaced_action_helper_spec.rb +43 -0
  129. data/spec/helpers/namespaced_input_helper_spec.rb +36 -0
  130. data/spec/helpers/reflection_helper_spec.rb +32 -0
  131. data/spec/helpers/semantic_errors_helper_spec.rb +112 -0
  132. data/spec/i18n_spec.rb +210 -0
  133. data/spec/input_class_finder_spec.rb +10 -0
  134. data/spec/inputs/base/collections_spec.rb +76 -0
  135. data/spec/inputs/base/validations_spec.rb +342 -0
  136. data/spec/inputs/boolean_input_spec.rb +254 -0
  137. data/spec/inputs/check_boxes_input_spec.rb +546 -0
  138. data/spec/inputs/color_input_spec.rb +97 -0
  139. data/spec/inputs/country_input_spec.rb +133 -0
  140. data/spec/inputs/custom_input_spec.rb +55 -0
  141. data/spec/inputs/datalist_input_spec.rb +61 -0
  142. data/spec/inputs/date_picker_input_spec.rb +449 -0
  143. data/spec/inputs/date_select_input_spec.rb +235 -0
  144. data/spec/inputs/datetime_picker_input_spec.rb +490 -0
  145. data/spec/inputs/datetime_select_input_spec.rb +193 -0
  146. data/spec/inputs/email_input_spec.rb +85 -0
  147. data/spec/inputs/file_input_spec.rb +89 -0
  148. data/spec/inputs/hidden_input_spec.rb +135 -0
  149. data/spec/inputs/include_blank_spec.rb +78 -0
  150. data/spec/inputs/label_spec.rb +149 -0
  151. data/spec/inputs/number_input_spec.rb +815 -0
  152. data/spec/inputs/password_input_spec.rb +99 -0
  153. data/spec/inputs/phone_input_spec.rb +85 -0
  154. data/spec/inputs/placeholder_spec.rb +71 -0
  155. data/spec/inputs/radio_input_spec.rb +328 -0
  156. data/spec/inputs/range_input_spec.rb +505 -0
  157. data/spec/inputs/readonly_spec.rb +50 -0
  158. data/spec/inputs/search_input_spec.rb +84 -0
  159. data/spec/inputs/select_input_spec.rb +615 -0
  160. data/spec/inputs/string_input_spec.rb +260 -0
  161. data/spec/inputs/text_input_spec.rb +187 -0
  162. data/spec/inputs/time_picker_input_spec.rb +455 -0
  163. data/spec/inputs/time_select_input_spec.rb +248 -0
  164. data/spec/inputs/time_zone_input_spec.rb +143 -0
  165. data/spec/inputs/url_input_spec.rb +85 -0
  166. data/spec/inputs/with_options_spec.rb +43 -0
  167. data/spec/localizer_spec.rb +130 -0
  168. data/spec/namespaced_class_finder_spec.rb +79 -0
  169. data/spec/spec.opts +2 -0
  170. data/spec/spec_helper.rb +525 -0
  171. data/spec/support/custom_macros.rb +564 -0
  172. data/spec/support/deprecation.rb +6 -0
  173. data/spec/support/shared_examples.rb +1313 -0
  174. data/spec/support/specialized_class_finder_shared_example.rb +27 -0
  175. data/spec/support/test_environment.rb +31 -0
  176. data/spec/util_spec.rb +66 -0
  177. metadata +434 -161
  178. data/README.textile +0 -682
  179. data/generators/form/USAGE +0 -16
  180. data/generators/form/form_generator.rb +0 -111
  181. data/generators/formtastic/formtastic_generator.rb +0 -26
  182. data/init.rb +0 -5
  183. data/lib/formtastic/layout_helper.rb +0 -12
  184. data/lib/formtastic/railtie.rb +0 -14
  185. data/lib/generators/templates/formtastic.css +0 -145
  186. data/lib/generators/templates/formtastic_changes.css +0 -14
  187. data/lib/generators/templates/rails2/_form.html.erb +0 -5
  188. data/lib/generators/templates/rails2/_form.html.haml +0 -4
  189. data/rails/init.rb +0 -2
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~>4.1.0"
6
+ gem "nokogiri", "~>1.6.8", :platform => :mri_20
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~>4.2.0"
6
+ gem "nokogiri", "~>1.6.8", :platform => :mri_20
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.0.0"
6
+ gem "nokogiri", "~>1.6.8", :platform => :mri_20
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 5.0.1"
6
+ gem "rspec-rails", "~> 3.5"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", :github => "rails/rails"
6
+ gem "rack", :github => "rack/rack"
7
+ gem "i18n", :github => "svenfuchs/i18n"
8
+ gem "arel", :github => "rails/arel"
9
+ gem "rspec-rails", :github => "rspec/rspec-rails"
10
+ gem "rspec-mocks", :github => "rspec/rspec-mocks"
11
+ gem "rspec-support", :github => "rspec/rspec-support"
12
+ gem "rspec-core", :github => "rspec/rspec-core"
13
+ gem "rspec-expectations", :github => "rspec/rspec-expectations"
14
+
15
+ gemspec :path => "../"
data/lib/formtastic.rb CHANGED
@@ -1,1954 +1,49 @@
1
1
  # encoding: utf-8
2
- require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
3
- require File.join(File.dirname(__FILE__), *%w[formtastic util])
4
- require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(::Rails::Railtie)
5
-
6
- module Formtastic #:nodoc:
7
-
8
- class SemanticFormBuilder < ActionView::Helpers::FormBuilder
9
-
10
- configurables = [
11
- :default_text_field_size, :default_text_area_height, :default_text_area_width, :all_fields_required_by_default, :include_blank_for_select_by_default,
12
- :required_string, :optional_string, :inline_errors, :label_str_method, :collection_value_methods, :collection_label_methods, :file_metadata_suffixes,
13
- :inline_order, :custom_inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels,
14
- :default_commit_button_accesskey, :default_inline_error_class, :default_hint_class, :default_error_list_class
15
- ]
16
-
17
- if respond_to?(:class_attribute)
18
- class_attribute *configurables
19
- else
20
- class_inheritable_accessor *configurables
21
- end
22
-
23
- cattr_accessor :custom_namespace
24
-
25
- self.default_text_field_size = nil
26
- self.default_text_area_height = 20
27
- self.default_text_area_width = nil
28
- self.all_fields_required_by_default = true
29
- self.include_blank_for_select_by_default = true
30
- self.required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
31
- self.optional_string = ''
32
- self.inline_errors = :sentence
33
- self.label_str_method = :humanize
34
- self.collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
35
- self.collection_value_methods = %w[id to_s]
36
- self.inline_order = [ :input, :hints, :errors ]
37
- self.custom_inline_order = {}
38
- self.file_methods = [ :file?, :public_filename, :filename ]
39
- self.file_metadata_suffixes = ['content_type', 'file_name', 'file_size']
40
- self.priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
41
- self.i18n_lookups_by_default = false
42
- self.escape_html_entities_in_hints_and_labels = true
43
- self.default_commit_button_accesskey = nil
44
- self.default_inline_error_class = 'inline-errors'
45
- self.default_error_list_class = 'errors'
46
- self.default_hint_class = 'inline-hints'
47
-
48
- RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
49
-
50
- INLINE_ERROR_TYPES = [:sentence, :list, :first]
51
-
52
- attr_accessor :template
53
-
54
- # Returns a suitable form input for the given +method+, using the database column information
55
- # and other factors (like the method name) to figure out what you probably want.
56
- #
57
- # Options:
58
- #
59
- # * :as - override the input type (eg force a :string to render as a :password field)
60
- # * :label - use something other than the method name as the label text, when false no label is printed
61
- # * :required - specify if the column is required (true) or not (false)
62
- # * :hint - provide some text to hint or help the user provide the correct information for a field
63
- # * :input_html - provide options that will be passed down to the generated input
64
- # * :wrapper_html - provide options that will be passed down to the li wrapper
65
- #
66
- # Input Types:
67
- #
68
- # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
69
- # but there are a few special cases and some simplification (:integer, :float and :decimal
70
- # columns all map to a single numeric_input, for example).
71
- #
72
- # * :select (a select menu for associations) - default to association names
73
- # * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
74
- # * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
75
- # * :time_zone (a select menu with time zones)
76
- # * :password (a password input) - default for :string column types with 'password' in the method name
77
- # * :text (a textarea) - default for :text column types
78
- # * :date (a date select) - default for :date column types
79
- # * :datetime (a date and time select) - default for :datetime and :timestamp column types
80
- # * :time (a time select) - default for :time column types
81
- # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
82
- # * :string (a text field) - default for :string column types
83
- # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
84
- # * :email (an email input) - default for :string column types with 'email' as the method name.
85
- # * :url (a url input) - default for :string column types with 'url' as the method name.
86
- # * :phone (a tel input) - default for :string column types with 'phone' or 'fax' in the method name.
87
- # * :search (a search input) - default for :string column types with 'search' as the method name.
88
- # * :country (a select menu of country names) - requires a country_select plugin to be installed
89
- # * :email (an email input) - New in HTML5 - needs to be explicitly provided with :as => :email
90
- # * :url (a url input) - New in HTML5 - needs to be explicitly provided with :as => :url
91
- # * :phone (a tel input) - New in HTML5 - needs to be explicitly provided with :as => :phone
92
- # * :search (a search input) - New in HTML5 - needs to be explicity provided with :as => :search
93
- # * :country (a select menu of country names) - requires a country_select plugin to be installed
94
- # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
95
- #
96
- # Example:
97
- #
98
- # <% semantic_form_for @employee do |form| %>
99
- # <% form.inputs do -%>
100
- # <%= form.input :name, :label => "Full Name" %>
101
- # <%= form.input :manager, :as => :radio %>
102
- # <%= form.input :secret, :as => :password, :input_html => { :value => "xxxx" } %>
103
- # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
104
- # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
105
- # <%= form.input :email %>
106
- # <%= form.input :website, :as => :url, :hint => "You may wish to omit the http://" %>
107
- # <% end %>
108
- # <% end %>
109
- #
110
- def input(method, options = {})
111
- options = options.dup # Allow options to be shared without being tainted by Formtastic
112
-
113
- options[:required] = method_required?(method) unless options.key?(:required)
114
- options[:as] ||= default_input_type(method, options)
115
-
116
- html_class = [ options[:as], (options[:required] ? :required : :optional) ]
117
- html_class << 'error' if has_errors?(method, options)
118
-
119
- wrapper_html = options.delete(:wrapper_html) || {}
120
- wrapper_html[:id] ||= generate_html_id(method)
121
- wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
122
-
123
- if options[:input_html] && options[:input_html][:id]
124
- options[:label_html] ||= {}
125
- options[:label_html][:for] ||= options[:input_html][:id]
126
- end
127
-
128
- input_parts = (custom_inline_order[options[:as]] || inline_order).dup
129
- input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
130
-
131
- list_item_content = input_parts.map do |type|
132
- send(:"inline_#{type}_for", method, options)
133
- end.compact.join("\n")
134
-
135
- return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
136
- end
137
-
138
- # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
139
- # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
140
- # or with a list of fields. These two examples are functionally equivalent:
141
- #
142
- # # With a block:
143
- # <% semantic_form_for @post do |form| %>
144
- # <% form.inputs do %>
145
- # <%= form.input :title %>
146
- # <%= form.input :body %>
147
- # <% end %>
148
- # <% end %>
149
- #
150
- # # With a list of fields:
151
- # <% semantic_form_for @post do |form| %>
152
- # <%= form.inputs :title, :body %>
153
- # <% end %>
154
- #
155
- # # Output:
156
- # <form ...>
157
- # <fieldset class="inputs">
158
- # <ol>
159
- # <li class="string">...</li>
160
- # <li class="text">...</li>
161
- # </ol>
162
- # </fieldset>
163
- # </form>
164
- #
165
- # === Quick Forms
166
- #
167
- # When called without a block or a field list, an input is rendered for each column in the
168
- # model's database table, just like Rails' scaffolding. You'll obviously want more control
169
- # than this in a production application, but it's a great way to get started, then come back
170
- # later to customise the form with a field list or a block of inputs. Example:
171
- #
172
- # <% semantic_form_for @post do |form| %>
173
- # <%= form.inputs %>
174
- # <% end %>
175
- #
176
- # With a few arguments:
177
- # <% semantic_form_for @post do |form| %>
178
- # <%= form.inputs "Post details", :title, :body %>
179
- # <% end %>
180
- #
181
- # === Options
182
- #
183
- # All options (with the exception of :name/:title) are passed down to the fieldset as HTML
184
- # attributes (id, class, style, etc). If provided, the :name/:title option is passed into a
185
- # legend tag inside the fieldset.
186
- #
187
- # # With a block:
188
- # <% semantic_form_for @post do |form| %>
189
- # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
190
- # ...
191
- # <% end %>
192
- # <% end %>
193
- #
194
- # # With a list (the options must come after the field list):
195
- # <% semantic_form_for @post do |form| %>
196
- # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
197
- # <% end %>
198
- #
199
- # # ...or the equivalent:
200
- # <% semantic_form_for @post do |form| %>
201
- # <%= form.inputs "Create a new post", :title, :body, :style => "border:1px;" %>
202
- # <% end %>
203
- #
204
- # === It's basically a fieldset!
205
- #
206
- # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
207
- # use inputs:
208
- #
209
- # <% semantic_form_for @post do |f| %>
210
- # <% f.inputs do %>
211
- # <%= f.input :title %>
212
- # <%= f.input :body %>
213
- # <% end %>
214
- # <% f.inputs :name => "Advanced", :id => "advanced" do %>
215
- # <%= f.input :created_at %>
216
- # <%= f.input :user_id, :label => "Author" %>
217
- # <% end %>
218
- # <% f.inputs "Extra" do %>
219
- # <%= f.input :update_at %>
220
- # <% end %>
221
- # <% end %>
222
- #
223
- # # Output:
224
- # <form ...>
225
- # <fieldset class="inputs">
226
- # <ol>
227
- # <li class="string">...</li>
228
- # <li class="text">...</li>
229
- # </ol>
230
- # </fieldset>
231
- # <fieldset class="inputs" id="advanced">
232
- # <legend><span>Advanced</span></legend>
233
- # <ol>
234
- # <li class="datetime">...</li>
235
- # <li class="select">...</li>
236
- # </ol>
237
- # </fieldset>
238
- # <fieldset class="inputs">
239
- # <legend><span>Extra</span></legend>
240
- # <ol>
241
- # <li class="datetime">...</li>
242
- # </ol>
243
- # </fieldset>
244
- # </form>
245
- #
246
- # === Nested attributes
247
- #
248
- # As in Rails, you can use semantic_fields_for to nest attributes:
249
- #
250
- # <% semantic_form_for @post do |form| %>
251
- # <%= form.inputs :title, :body %>
252
- #
253
- # <% form.semantic_fields_for :author, @bob do |author_form| %>
254
- # <% author_form.inputs do %>
255
- # <%= author_form.input :first_name, :required => false %>
256
- # <%= author_form.input :last_name %>
257
- # <% end %>
258
- # <% end %>
259
- # <% end %>
260
- #
261
- # But this does not look formtastic! This is equivalent:
262
- #
263
- # <% semantic_form_for @post do |form| %>
264
- # <%= form.inputs :title, :body %>
265
- # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
266
- # <%= author_form.input :first_name, :required => false %>
267
- # <%= author_form.input :last_name %>
268
- # <% end %>
269
- # <% end %>
270
- #
271
- # And if you don't need to give options to your input call, you could do it
272
- # in just one line:
273
- #
274
- # <% semantic_form_for @post do |form| %>
275
- # <%= form.inputs :title, :body %>
276
- # <%= form.inputs :first_name, :last_name, :for => @bob %>
277
- # <% end %>
278
- #
279
- # Just remember that calling inputs generates a new fieldset to wrap your
280
- # inputs. If you have two separate models, but, semantically, on the page
281
- # they are part of the same fieldset, you should use semantic_fields_for
282
- # instead (just as you would do with Rails' form builder).
283
- #
284
- def inputs(*args, &block)
285
- title = field_set_title_from_args(*args)
286
- html_options = args.extract_options!
287
- html_options[:class] ||= "inputs"
288
- html_options[:name] = title
289
-
290
- if html_options[:for] # Nested form
291
- inputs_for_nested_attributes(*(args << html_options), &block)
292
- elsif block_given?
293
- field_set_and_list_wrapping(*(args << html_options), &block)
294
- else
295
- if @object && args.empty?
296
- args = association_columns(:belongs_to)
297
- args += content_columns
298
- args -= RESERVED_COLUMNS
299
- args.compact!
300
- end
301
- legend = args.shift if args.first.is_a?(::String)
302
- contents = args.collect { |method| input(method.to_sym) }
303
- args.unshift(legend) if legend.present?
304
-
305
- field_set_and_list_wrapping(*((args << html_options) << contents))
306
- end
307
- end
308
-
309
- # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
310
- # See inputs documentation for a full example. The fieldset's default class attriute
311
- # is set to "buttons".
312
- #
313
- # See inputs for html attributes and special options.
314
- def buttons(*args, &block)
315
- html_options = args.extract_options!
316
- html_options[:class] ||= "buttons"
317
-
318
- if block_given?
319
- field_set_and_list_wrapping(html_options, &block)
320
- else
321
- args = [:commit] if args.empty?
322
- contents = args.map { |button_name| send(:"#{button_name}_button") }
323
- field_set_and_list_wrapping(html_options, contents)
324
- end
325
- end
326
-
327
- # Creates a submit input tag with the value "Save [model name]" (for existing records) or
328
- # "Create [model name]" (for new records) by default:
329
- #
330
- # <%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />
331
- #
332
- # The value of the button text can be overridden:
333
- #
334
- # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
335
- # <%= form.commit_button :label => "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
336
- #
337
- # And you can pass html atributes down to the input, with or without the button text:
338
- #
339
- # <%= form.commit_button :button_html => { :class => "pretty" } %> => <input name="commit" type="submit" value="Save Post" class="pretty {create|update|submit}" />
340
- def commit_button(*args)
341
- options = args.extract_options!
342
- text = options.delete(:label) || args.shift
343
-
344
- if @object && (@object.respond_to?(:persisted?) || @object.respond_to?(:new_record?))
345
- if @object.respond_to?(:persisted?) # ActiveModel
346
- key = @object.persisted? ? :update : :create
347
- else # Rails 2
348
- key = @object.new_record? ? :create : :update
349
- end
350
-
351
- # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
352
- # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
353
- # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
354
- # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
355
- if @object.class.model_name.respond_to?(:human)
356
- object_name = @object.class.model_name.human
357
- else
358
- object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
359
- crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
360
- decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
361
- object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
362
- end
363
- else
364
- key = :submit
365
- object_name = @object_name.to_s.send(label_str_method)
366
- end
367
-
368
- text = (localized_string(key, text, :action, :model => object_name) ||
369
- ::Formtastic::I18n.t(key, :model => object_name)) unless text.is_a?(::String)
370
-
371
- button_html = options.delete(:button_html) || {}
372
- button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
373
-
374
- wrapper_html_class = ['commit'] # TODO: Add class reflecting on form action.
375
- wrapper_html = options.delete(:wrapper_html) || {}
376
- wrapper_html[:class] = (wrapper_html_class << wrapper_html[:class]).flatten.compact.join(' ')
377
-
378
- accesskey = (options.delete(:accesskey) || default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
379
- button_html = button_html.merge(:accesskey => accesskey) if accesskey
380
- template.content_tag(:li, Formtastic::Util.html_safe(submit(text, button_html)), wrapper_html)
381
- end
382
-
383
- # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
384
- # for nesting forms:
385
- #
386
- # # Example:
387
- # <% semantic_form_for @post do |post| %>
388
- # <% post.semantic_fields_for :author do |author| %>
389
- # <% author.inputs :name %>
390
- # <% end %>
391
- # <% end %>
392
- #
393
- # # Output:
394
- # <form ...>
395
- # <fieldset class="inputs">
396
- # <ol>
397
- # <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
398
- # </ol>
399
- # </fieldset>
400
- # </form>
401
- #
402
- def semantic_fields_for(record_or_name_or_array, *args, &block)
403
- opts = args.extract_options!
404
- opts[:builder] ||= self.class
405
- args.push(opts)
406
- fields_for(record_or_name_or_array, *args, &block)
407
- end
408
-
409
- # Generates the label for the input. It also accepts the same arguments as
410
- # Rails label method. It has three options that are not supported by Rails
411
- # label method:
412
- #
413
- # * :required - Appends an abbr tag if :required is true
414
- # * :label - An alternative form to give the label content. Whenever label
415
- # is false, a blank string is returned.
416
- # * :input_name - Gives the input to match for. This is needed when you want to
417
- # to call f.label :authors but it should match :author_ids.
418
- #
419
- # == Examples
420
- #
421
- # f.label :title # like in rails, except that it searches the label on I18n API too
422
- #
423
- # f.label :title, "Your post title"
424
- # f.label :title, :label => "Your post title" # Added for formtastic API
425
- #
426
- # f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
427
- #
428
- def label(method, options_or_text=nil, options=nil)
429
- if options_or_text.is_a?(Hash)
430
- return "" if options_or_text[:label] == false
431
- options = options_or_text
432
- text = options.delete(:label)
433
- else
434
- text = options_or_text
435
- options ||= {}
436
- end
437
-
438
- text = localized_string(method, text, :label) || humanized_attribute_name(method)
439
- text += required_or_optional_string(options.delete(:required))
440
- text = Formtastic::Util.html_safe(text)
441
-
442
- # special case for boolean (checkbox) labels, which have a nested input
443
- if options.key?(:label_prefix_for_nested_input)
444
- text = options.delete(:label_prefix_for_nested_input) + text
445
- end
446
-
447
- input_name = options.delete(:input_name) || method
448
- super(input_name, text, options)
449
- end
450
-
451
- # Generates error messages for the given method. Errors can be shown as list,
452
- # as sentence or just the first error can be displayed. If :none is set, no error is shown.
453
- #
454
- # This method is also aliased as errors_on, so you can call on your custom
455
- # inputs as well:
456
- #
457
- # semantic_form_for :post do |f|
458
- # f.text_field(:body)
459
- # f.errors_on(:body)
460
- # end
461
- #
462
- def inline_errors_for(method, options = {}) #:nodoc:
463
- if render_inline_errors?
464
- errors = error_keys(method, options).map{|x| @object.errors[x] }.flatten.compact.uniq
465
- send(:"error_#{inline_errors}", [*errors], options) if errors.any?
466
- else
467
- nil
468
- end
469
- end
470
- alias :errors_on :inline_errors_for
471
-
472
- # Generates error messages for given method names and for base.
473
- # You can pass a hash with html options that will be added to ul tag
474
- #
475
- # == Examples
476
- #
477
- # f.semantic_errors # This will show only errors on base
478
- # f.semantic_errors :state # This will show errors on base and state
479
- # f.semantic_errors :state, :class => "awesome" # errors will be rendered in ul.awesome
480
- #
481
- def semantic_errors(*args)
482
- html_options = args.extract_options!
483
- full_errors = args.inject([]) do |array, method|
484
- attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
485
- errors = Array(@object.errors[method.to_sym]).to_sentence
486
- errors.present? ? array << [attribute, errors].join(" ") : array ||= []
487
- end
488
- full_errors << @object.errors[:base]
489
- full_errors.flatten!
490
- full_errors.compact!
491
- return nil if full_errors.blank?
492
- html_options[:class] ||= "errors"
493
- template.content_tag(:ul, html_options) do
494
- Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
495
- end
496
- end
497
-
498
- protected
499
-
500
- def error_keys(method, options)
501
- @methods_for_error ||= {}
502
- @methods_for_error[method] ||= begin
503
- methods_for_error = [method.to_sym]
504
- methods_for_error << file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
505
- methods_for_error << [association_primary_key(method)] if association_macro_for_method(method) == :belongs_to
506
- methods_for_error.flatten.compact.uniq
507
- end
508
- end
509
-
510
- def has_errors?(method, options)
511
- methods_for_error = error_keys(method,options)
512
- @object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
513
- end
514
-
515
- def render_inline_errors?
516
- @object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(inline_errors)
517
- end
518
-
519
- # Collects content columns (non-relation columns) for the current form object class.
520
- #
521
- def content_columns #:nodoc:
522
- model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
523
- end
524
-
525
- # Collects association columns (relation columns) for the current form object class.
526
- #
527
- def association_columns(*by_associations) #:nodoc:
528
- if @object.present? && @object.class.respond_to?(:reflections)
529
- @object.class.reflections.collect do |name, association_reflection|
530
- if by_associations.present?
531
- name if by_associations.include?(association_reflection.macro)
532
- else
533
- name
534
- end
535
- end.compact
536
- else
537
- []
538
- end
539
- end
540
-
541
- # Returns nil, or a symbol like :belongs_to or :has_many
542
- def association_macro_for_method(method) #:nodoc:
543
- reflection = reflection_for(method)
544
- reflection.macro if reflection
545
- end
546
-
547
- def association_primary_key(method)
548
- reflection = reflection_for(method)
549
- reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
550
- :"#{method}_id"
551
- end
552
-
553
- # Prepare options to be sent to label
554
- #
555
- def options_for_label(options) #:nodoc:
556
- options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
557
- end
558
-
559
- # Deals with :for option when it's supplied to inputs methods. Additional
560
- # options to be passed down to :for should be supplied using :for_options
561
- # key.
562
- #
563
- # It should raise an error if a block with arity zero is given.
564
- #
565
- def inputs_for_nested_attributes(*args, &block) #:nodoc:
566
- options = args.extract_options!
567
- args << options.merge!(:parent => { :builder => self, :for => options[:for] })
568
-
569
- fields_for_block = if block_given?
570
- raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
571
- 'but the block does not accept any argument.' if block.arity <= 0
572
- lambda do |f|
573
- contents = f.inputs(*args){ block.call(f) }
574
- template.concat(contents) if ::Formtastic::Util.rails3?
575
- contents
576
- end
577
- else
578
- lambda do |f|
579
- contents = f.inputs(*args)
580
- template.concat(contents) if ::Formtastic::Util.rails3?
581
- contents
582
- end
583
- end
584
-
585
- fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
586
- semantic_fields_for(*fields_for_args, &fields_for_block)
587
- end
588
-
589
- # Remove any Formtastic-specific options before passing the down options.
590
- #
591
- def strip_formtastic_options(options) #:nodoc:
592
- options.except(:value_method, :label_method, :collection, :required, :label,
593
- :as, :hint, :input_html, :label_html, :value_as_class, :find_options, :class)
594
- end
595
-
596
- # Determins if the attribute (eg :title) should be considered required or not.
597
- #
598
- # * if the :required option was provided in the options hash, the true/false value will be
599
- # returned immediately, allowing the view to override any guesswork that follows:
600
- #
601
- # * if the :required option isn't provided in the options hash, and the ValidationReflection
602
- # plugin is installed (http://github.com/redinger/validation_reflection), or the object is
603
- # an ActiveModel, true is returned
604
- # if the validates_presence_of macro has been used in the class for this attribute, or false
605
- # otherwise.
606
- #
607
- # * if the :required option isn't provided, and validates_presence_of can't be determined, the
608
- # configuration option all_fields_required_by_default is used.
609
- #
610
- def method_required?(attribute) #:nodoc:
611
- attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
612
-
613
- if @object && @object.class.respond_to?(:reflect_on_validations_for)
614
- @object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
615
- (validation.macro == :validates_presence_of || validation.macro == :validates_inclusion_of) &&
616
- validation.name == attribute_sym &&
617
- (validation.options.present? ? options_require_validation?(validation.options) : true)
618
- end
619
- else
620
- if @object && @object.class.respond_to?(:validators_on)
621
- !@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence || validator.kind == :inclusion) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
622
- else
623
- all_fields_required_by_default
624
- end
625
- end
626
- end
627
-
628
- # Determines whether the given options evaluate to true
629
- def options_require_validation?(options) #nodoc
630
- allow_blank = options[:allow_blank]
631
- return !allow_blank unless allow_blank.nil?
632
- if_condition = !options[:if].nil?
633
- condition = if_condition ? options[:if] : options[:unless]
634
-
635
- condition = if condition.respond_to?(:call)
636
- condition.call(@object)
637
- elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
638
- @object.send(condition)
639
- else
640
- condition
641
- end
642
-
643
- if_condition ? !!condition : !condition
644
- end
645
-
646
- def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
647
- html_options = options.delete(:input_html) || {}
648
- html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text, :phone, :search, :url, :email].include?(type)
649
- field_id = generate_html_id(method, "")
650
- html_options[:id] ||= field_id
651
- label_options = options_for_label(options)
652
- label_options[:for] ||= html_options[:id]
653
- label(method, label_options) <<
654
- send(respond_to?(form_helper_method) ? form_helper_method : :text_field, method, html_options)
655
- end
656
-
657
- # Outputs a label and standard Rails text field inside the wrapper.
658
- def string_input(method, options)
659
- basic_input_helper(:text_field, :string, method, options)
660
- end
661
-
662
- # Outputs a label and standard Rails password field inside the wrapper.
663
- def password_input(method, options)
664
- basic_input_helper(:password_field, :password, method, options)
665
- end
666
-
667
- # Outputs a label and standard Rails text field inside the wrapper.
668
- def numeric_input(method, options)
669
- basic_input_helper(:text_field, :numeric, method, options)
670
- end
671
-
672
- # Ouputs a label and standard Rails text area inside the wrapper.
673
- def text_input(method, options)
674
- basic_input_helper(:text_area, :text, method, options)
675
- end
676
-
677
- # Outputs a label and a standard Rails file field inside the wrapper.
678
- def file_input(method, options)
679
- basic_input_helper(:file_field, :file, method, options)
680
- end
681
-
682
- # Outputs a label and a standard Rails email field inside the wrapper.
683
- def email_input(method, options)
684
- basic_input_helper(:email_field, :email, method, options)
685
- end
686
-
687
- # Outputs a label and a standard Rails phone field inside the wrapper.
688
- def phone_input(method, options)
689
- basic_input_helper(:phone_field, :phone, method, options)
690
- end
691
-
692
- # Outputs a label and a standard Rails url field inside the wrapper.
693
- def url_input(method, options)
694
- basic_input_helper(:url_field, :url, method, options)
695
- end
696
-
697
- # Outputs a label and a standard Rails search field inside the wrapper.
698
- def search_input(method, options)
699
- basic_input_helper(:search_field, :search, method, options)
700
- end
701
-
702
- # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
703
- # Additionals options can be given using :input_hml. Should :input_html not be
704
- # specified every option except for formtastic options will be sent straight
705
- # to hidden input element.
706
- #
707
- def hidden_input(method, options)
708
- options ||= {}
709
- html_options = options.delete(:input_html) || strip_formtastic_options(options)
710
- html_options[:id] ||= generate_html_id(method, "")
711
- hidden_field(method, html_options)
712
- end
713
-
714
- # Outputs a label and a select box containing options from the parent
715
- # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
716
- # is has_many or has_and_belongs_to_many the select box will be set as multi-select
717
- # and size = 5
718
- #
719
- # Example (belongs_to):
720
- #
721
- # f.input :author
722
- #
723
- # <label for="book_author_id">Author</label>
724
- # <select id="book_author_id" name="book[author_id]">
725
- # <option value=""></option>
726
- # <option value="1">Justin French</option>
727
- # <option value="2">Jane Doe</option>
728
- # </select>
729
- #
730
- # Example (has_many):
731
- #
732
- # f.input :chapters
733
- #
734
- # <label for="book_chapter_ids">Chapters</label>
735
- # <select id="book_chapter_ids" name="book[chapter_ids]">
736
- # <option value=""></option>
737
- # <option value="1">Chapter 1</option>
738
- # <option value="2">Chapter 2</option>
739
- # </select>
740
- #
741
- # Example (has_and_belongs_to_many):
742
- #
743
- # f.input :authors
744
- #
745
- # <label for="book_author_ids">Authors</label>
746
- # <select id="book_author_ids" name="book[author_ids]">
747
- # <option value=""></option>
748
- # <option value="1">Justin French</option>
749
- # <option value="2">Jane Doe</option>
750
- # </select>
751
- #
752
- #
753
- # You can customize the options available in the select by passing in a collection. A collection can be given
754
- # as an Array, a Hash or as a String (containing pre-rendered HTML options). If not provided, the choices are
755
- # found by inferring the parent's class name from the method name and simply calling all on it
756
- # (VehicleOwner.all in the example above).
757
- #
758
- # Examples:
759
- #
760
- # f.input :author, :collection => @authors
761
- # f.input :author, :collection => Author.all
762
- # f.input :author, :collection => [@justin, @kate]
763
- # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
764
- # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
765
- # f.input :author, :collection => grouped_options_for_select(["North America",[["United States","US"],["Canada","CA"]]])
766
- #
767
- # The :label_method option allows you to customize the text label inside each option tag two ways:
768
- #
769
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
770
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
771
- #
772
- # Examples:
773
- #
774
- # f.input :author, :label_method => :full_name
775
- # f.input :author, :label_method => :login
776
- # f.input :author, :label_method => :full_name_with_post_count
777
- # f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
778
- #
779
- # The :value_method option provides the same customization of the value attribute of each option tag.
780
- #
781
- # Examples:
782
- #
783
- # f.input :author, :value_method => :full_name
784
- # f.input :author, :value_method => :login
785
- # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
786
- #
787
- # You can pass html_options to the select tag using :input_html => {}
788
- #
789
- # Examples:
790
- #
791
- # f.input :authors, :input_html => {:size => 20, :multiple => true}
792
- #
793
- # By default, all select inputs will have a blank option at the top of the list. You can add
794
- # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
795
- #
796
- #
797
- # You can group the options in optgroup elements by passing the :group_by option
798
- # (Note: only tested for belongs_to relations)
799
- #
800
- # Examples:
801
- #
802
- # f.input :author, :group_by => :continent
803
- #
804
- # All the other options should work as expected. If you want to call a custom method on the
805
- # group item. You can include the option:group_label_method
806
- # Examples:
807
- #
808
- # f.input :author, :group_by => :continents, :group_label_method => :something_different
809
- #
810
- def select_input(method, options)
811
- html_options = options.delete(:input_html) || {}
812
- html_options[:multiple] = html_options[:multiple] || options.delete(:multiple)
813
- html_options.delete(:multiple) if html_options[:multiple].nil?
814
-
815
- reflection = reflection_for(method)
816
- if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
817
- html_options[:multiple] = true if html_options[:multiple].nil?
818
- html_options[:size] ||= 5
819
- options[:include_blank] ||= false
820
- end
821
- options = set_include_blank(options)
822
- input_name = generate_association_input_name(method)
823
- html_options[:id] ||= generate_html_id(input_name, "")
824
-
825
- select_html = if options[:group_by]
826
- # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
827
- # The formtastic user however shouldn't notice this too much.
828
- raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
829
- label, value = detect_label_and_value_method!(raw_collection, options)
830
- group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
831
- group_label_method = options[:group_label_method] || detect_label_method(group_collection)
832
- group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
833
- group_association = options[:group_association] || detect_group_association(method, options[:group_by])
834
-
835
- # Here comes the monster with 8 arguments
836
- grouped_collection_select(input_name, group_collection,
837
- group_association, group_label_method,
838
- value, label,
839
- strip_formtastic_options(options), html_options)
840
- else
841
- collection = find_collection_for_column(method, options)
842
-
843
- select(input_name, collection, strip_formtastic_options(options), html_options)
844
- end
845
-
846
- label_options = options_for_label(options).merge(:input_name => input_name)
847
- label_options[:for] ||= html_options[:id]
848
- label(method, label_options) << select_html
849
- end
850
-
851
- # Outputs a timezone select input as Rails' time_zone_select helper. You
852
- # can give priority zones as option.
853
- #
854
- # Examples:
855
- #
856
- # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
857
- def time_zone_input(method, options)
858
- html_options = options.delete(:input_html) || {}
859
- field_id = generate_html_id(method, "")
860
- html_options[:id] ||= field_id
861
- label_options = options_for_label(options)
862
- label_options[:for] ||= html_options[:id]
863
- label(method, label_options) <<
864
- time_zone_select(method, options.delete(:priority_zones),
865
- strip_formtastic_options(options), html_options)
866
- end
867
-
868
- # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
869
- # items, one for each possible choice in the belongs_to association. Each li contains a
870
- # label and a radio input.
871
- #
872
- # Example:
873
- #
874
- # f.input :author, :as => :radio
875
- #
876
- # Output:
877
- #
878
- # <fieldset>
879
- # <legend><span>Author</span></legend>
880
- # <ol>
881
- # <li>
882
- # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
883
- # </li>
884
- # <li>
885
- # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
886
- # </li>
887
- # </ol>
888
- # </fieldset>
889
- #
890
- # You can customize the choices available in the radio button set by passing in a collection (an Array or
891
- # Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
892
- # (Author.all in the example above).
893
- #
894
- # Examples:
895
- #
896
- # f.input :author, :as => :radio, :collection => @authors
897
- # f.input :author, :as => :radio, :collection => Author.all
898
- # f.input :author, :as => :radio, :collection => [@justin, @kate]
899
- # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
900
- #
901
- # The :label_method option allows you to customize the label for each radio button two ways:
902
- #
903
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
904
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
905
- #
906
- # Examples:
907
- #
908
- # f.input :author, :as => :radio, :label_method => :full_name
909
- # f.input :author, :as => :radio, :label_method => :login
910
- # f.input :author, :as => :radio, :label_method => :full_name_with_post_count
911
- # f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
912
- #
913
- # The :value_method option provides the same customization of the value attribute of each option tag.
914
- #
915
- # Examples:
916
- #
917
- # f.input :author, :as => :radio, :value_method => :full_name
918
- # f.input :author, :as => :radio, :value_method => :login
919
- # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
920
- #
921
- # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
922
- # button / label combination to contain a class with the value of the radio button (useful for
923
- # applying specific CSS or Javascript to a particular radio button).
924
- def radio_input(method, options)
925
- collection = find_collection_for_column(method, options)
926
- html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})
927
-
928
- input_name = generate_association_input_name(method)
929
- value_as_class = options.delete(:value_as_class)
930
- input_ids = []
931
-
932
- list_item_content = collection.map do |c|
933
- label = c.is_a?(Array) ? c.first : c
934
- value = c.is_a?(Array) ? c.last : c
935
- input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
936
- input_ids << input_id
937
-
938
- html_options[:id] = input_id
939
-
940
- li_content = template.content_tag(:label,
941
- Formtastic::Util.html_safe("#{radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
942
- :for => input_id
943
- )
944
-
945
- li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
946
- template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
947
- end
948
-
949
- template.content_tag(:fieldset,
950
- legend_tag(method, options) << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
951
- )
952
- end
953
-
954
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
955
- # items (li), one for each fragment for the date (year, month, day). Each li contains a label
956
- # (eg "Year") and a select box. Overwriting the label is possible by adding the :labels option.
957
- # :labels should be a hash with the field (e.g. day) as key and the label text as value.
958
- # See date_or_datetime_input for a more detailed output example.
959
- #
960
- # Some of Rails' options for select_date are supported, but not everything yet, see
961
- # documentation of date_or_datetime_input() for more information.
962
- def date_input(method, options)
963
- options = set_include_blank(options)
964
- date_or_datetime_input(method, options.merge(:discard_hour => true))
965
- end
966
-
967
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
968
- # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
969
- # contains a label (eg "Year") and a select box. Overwriting the label is possible by adding
970
- # the :labels option. :labels should be a hash with the field (e.g. day) as key and the label
971
- # text as value. See date_or_datetime_input for a more detailed output example.
972
- #
973
- # Some of Rails' options for select_date are supported, but not everything yet, see
974
- # documentation of date_or_datetime_input() for more information.
975
- def datetime_input(method, options)
976
- options = set_include_blank(options)
977
- date_or_datetime_input(method, options)
978
- end
979
-
980
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
981
- # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
982
- # (eg "Hour") and a select box. Overwriting the label is possible by adding the :labels option.
983
- # :labels should be a hash with the field (e.g. day) as key and the label text as value.
984
- # See date_or_datetime_input for a more detailed output example.
985
- #
986
- # Some of Rails' options for select_time are supported, but not everything yet, see
987
- # documentation of date_or_datetime_input() for more information.
988
- def time_input(method, options)
989
- options = set_include_blank(options)
990
- date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
991
- end
992
-
993
- # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
994
- # legend (for what would normally be considered the label), and an ordered list of list items
995
- # for year, month, day, hour, etc, each containing a label and a select. Example:
996
- #
997
- # <fieldset>
998
- # <legend>Created At</legend>
999
- # <ol>
1000
- # <li>
1001
- # <label for="user_created_at_1i">Year</label>
1002
- # <select id="user_created_at_1i" name="user[created_at(1i)]">
1003
- # <option value="2003">2003</option>
1004
- # ...
1005
- # <option value="2013">2013</option>
1006
- # </select>
1007
- # </li>
1008
- # <li>
1009
- # <label for="user_created_at_2i">Month</label>
1010
- # <select id="user_created_at_2i" name="user[created_at(2i)]">
1011
- # <option value="1">January</option>
1012
- # ...
1013
- # <option value="12">December</option>
1014
- # </select>
1015
- # </li>
1016
- # <li>
1017
- # <label for="user_created_at_3i">Day</label>
1018
- # <select id="user_created_at_3i" name="user[created_at(3i)]">
1019
- # <option value="1">1</option>
1020
- # ...
1021
- # <option value="31">31</option>
1022
- # </select>
1023
- # </li>
1024
- # </ol>
1025
- # </fieldset>
1026
- #
1027
- # This is an absolute abomination, but so is the official Rails select_date().
1028
- #
1029
- # Options:
1030
- #
1031
- # * @:order => [:month, :day, :year]@
1032
- # * @:include_seconds@ => true@
1033
- # * @:discard_(year|month|day|hour|minute) => true@
1034
- # * @:include_blank => true@
1035
- # * @:labels => {}@
1036
- def date_or_datetime_input(method, options)
1037
- position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
1038
- i18n_date_order = ::I18n.t(:order, :scope => [:date])
1039
- i18n_date_order = nil unless i18n_date_order.is_a?(Array)
1040
- inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
1041
- inputs = [] if options[:ignore_date]
1042
- labels = options.delete(:labels) || {}
1043
-
1044
- time_inputs = [:hour, :minute]
1045
- time_inputs << :second if options[:include_seconds]
1046
-
1047
- list_items_capture = ""
1048
- hidden_fields_capture = ""
1049
-
1050
- datetime = @object.send(method) if @object && @object.send(method)
1051
-
1052
- html_options = options.delete(:input_html) || {}
1053
- input_ids = []
1054
-
1055
- (inputs + time_inputs).each do |input|
1056
- input_ids << input_id = generate_html_id(method, "#{position[input]}i")
1057
-
1058
- field_name = "#{method}(#{position[input]}i)"
1059
- if options[:"discard_#{input}"]
1060
- break if time_inputs.include?(input)
1061
-
1062
- hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
1063
- hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
1064
- else
1065
- opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
1066
- item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
1067
-
1068
- list_items_capture << template.content_tag(:li, Formtastic::Util.html_safe([
1069
- !item_label_text.blank? ? template.content_tag(:label, Formtastic::Util.html_safe(item_label_text), :for => input_id) : "",
1070
- template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
1071
- ].join(""))
1072
- )
1073
- end
1074
- end
1075
-
1076
- hidden_fields_capture << field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_items_capture)
1077
- end
1078
-
1079
- # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
1080
- # items, one for each possible choice in the belongs_to association. Each li contains a
1081
- # label and a check_box input.
1082
- #
1083
- # This is an alternative for has many and has and belongs to many associations.
1084
- #
1085
- # Example:
1086
- #
1087
- # f.input :author, :as => :check_boxes
1088
- #
1089
- # Output:
1090
- #
1091
- # <fieldset>
1092
- # <legend class="label"><label>Authors</label></legend>
1093
- # <ol>
1094
- # <li>
1095
- # <input type="hidden" name="book[author_id][1]" value="">
1096
- # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
1097
- # </li>
1098
- # <li>
1099
- # <input type="hidden" name="book[author_id][2]" value="">
1100
- # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
1101
- # </li>
1102
- # </ol>
1103
- # </fieldset>
1104
- #
1105
- # Notice that the value of the checkbox is the same as the id and the hidden
1106
- # field has empty value. You can override the hidden field value using the
1107
- # unchecked_value option.
1108
- #
1109
- # You can customize the options available in the set by passing in a collection (Array) of
1110
- # ActiveRecord objects through the :collection option. If not provided, the choices are found
1111
- # by inferring the parent's class name from the method name and simply calling all on
1112
- # it (Author.all in the example above).
1113
- #
1114
- # Examples:
1115
- #
1116
- # f.input :author, :as => :check_boxes, :collection => @authors
1117
- # f.input :author, :as => :check_boxes, :collection => Author.all
1118
- # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
1119
- #
1120
- # The :label_method option allows you to customize the label for each checkbox two ways:
1121
- #
1122
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
1123
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
1124
- #
1125
- # Examples:
1126
- #
1127
- # f.input :author, :as => :check_boxes, :label_method => :full_name
1128
- # f.input :author, :as => :check_boxes, :label_method => :login
1129
- # f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
1130
- # f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
1131
- #
1132
- # The :value_method option provides the same customization of the value attribute of each checkbox input tag.
1133
- #
1134
- # Examples:
1135
- #
1136
- # f.input :author, :as => :check_boxes, :value_method => :full_name
1137
- # f.input :author, :as => :check_boxes, :value_method => :login
1138
- # f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
1139
- #
1140
- # Formtastic works around a bug in rails handling of check box collections by
1141
- # not generating the hidden fields for state checking of the checkboxes
1142
- # The :hidden_fields option provides a way to re-enable these hidden inputs by
1143
- # setting it to true.
1144
- #
1145
- # f.input :authors, :as => :check_boxes, :hidden_fields => false
1146
- # f.input :authors, :as => :check_boxes, :hidden_fields => true
1147
- #
1148
- # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
1149
- # combination to contain a class with the value of the radio button (useful for applying specific
1150
- # CSS or Javascript to a particular checkbox).
1151
- #
1152
- def check_boxes_input(method, options)
1153
- collection = find_collection_for_column(method, options)
1154
- html_options = options.delete(:input_html) || {}
1155
-
1156
- input_name = generate_association_input_name(method)
1157
- hidden_fields = options.delete(:hidden_fields)
1158
- value_as_class = options.delete(:value_as_class)
1159
- unchecked_value = options.delete(:unchecked_value) || ''
1160
- html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
1161
- input_ids = []
1162
-
1163
- selected_values = find_selected_values_for_column(method, options)
1164
- disabled_option_is_present = options.key?(:disabled)
1165
- disabled_values = [*options[:disabled]] if disabled_option_is_present
1166
-
1167
- li_options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
1168
-
1169
- list_item_content = collection.map do |c|
1170
- label = c.is_a?(Array) ? c.first : c
1171
- value = c.is_a?(Array) ? c.last : c
1172
- input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
1173
- input_ids << input_id
1174
-
1175
- html_options[:checked] = selected_values.include?(value)
1176
- html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
1177
- html_options[:id] = input_id
1178
-
1179
- li_content = template.content_tag(:label,
1180
- Formtastic::Util.html_safe("#{create_check_boxes(input_name, html_options, value, unchecked_value, hidden_fields)} #{escape_html_entities(label)}"),
1181
- :for => input_id
1182
- )
1183
-
1184
- li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
1185
- template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
1186
- end
1187
-
1188
- fieldset_content = legend_tag(method, options)
1189
- fieldset_content << create_hidden_field_for_check_boxes(input_name, value_as_class) unless hidden_fields
1190
- fieldset_content << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
1191
- template.content_tag(:fieldset, fieldset_content)
1192
- end
1193
-
1194
- # Used by check_boxes input. The selected values will be set by retrieving the value
1195
- # through the association.
1196
- #
1197
- # If the collection is not a hash or an array of strings, fixnums or symbols,
1198
- # we use value_method to retrieve an array with the values
1199
- def find_selected_values_for_column(method, options)
1200
- if object.respond_to?(method)
1201
- collection = [object.send(method)].compact.flatten
1202
- label, value = detect_label_and_value_method!(collection, options)
1203
- [*collection.map { |o| send_or_call(value, o) }].compact
1204
- else
1205
- []
1206
- end
1207
- end
1208
-
1209
- # Outputs a custom hidden field for check_boxes
1210
- def create_hidden_field_for_check_boxes(method, value_as_class) #:nodoc:
1211
- options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
1212
- input_name = "#{object_name}[#{method.to_s}][]"
1213
- template.hidden_field_tag(input_name, '', options)
1214
- end
1215
-
1216
- # Outputs a checkbox tag. If called with no_hidden_input = true a plain check_box_tag is returned,
1217
- # otherwise the helper uses the output generated by the rails check_box method.
1218
- def create_check_boxes(input_name, html_options = {}, checked_value = "1", unchecked_value = "0", hidden_fields = false) #:nodoc:
1219
- return template.check_box_tag(input_name, checked_value, html_options[:checked], html_options) unless hidden_fields == true
1220
- check_box(input_name, html_options, checked_value, unchecked_value)
1221
- end
1222
-
1223
- # Outputs a country select input, wrapping around a regular country_select helper.
1224
- # Rails doesn't come with a country_select helper by default any more, so you'll need to install
1225
- # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
1226
- # same way.
1227
- #
1228
- # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
1229
- #
1230
- # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1231
- # which you can change to suit your market and user base (see README for more info on config).
1232
- #
1233
- # Examples:
1234
- # f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
1235
- # f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
1236
- #
1237
- def country_input(method, options)
1238
- raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless respond_to?(:country_select)
1239
-
1240
- html_options = options.delete(:input_html) || {}
1241
- priority_countries = options.delete(:priority_countries) || self.priority_countries
1242
-
1243
- field_id = generate_html_id(method, "")
1244
- html_options[:id] ||= field_id
1245
- label_options = options_for_label(options)
1246
- label_options[:for] ||= html_options[:id]
1247
-
1248
- label(method, label_options) <<
1249
- country_select(method, priority_countries, strip_formtastic_options(options), html_options)
1250
- end
1251
-
1252
- # Outputs a label containing a checkbox and the label text. The label defaults
1253
- # to the column name (method name) and can be altered with the :label option.
1254
- # :checked_value and :unchecked_value options are also available.
1255
- def boolean_input(method, options)
1256
- html_options = options.delete(:input_html) || {}
1257
- checked_value = options.delete(:checked_value) || '1'
1258
- unchecked_value = options.delete(:unchecked_value) || '0'
1259
- checked = @object && ActionView::Helpers::InstanceTag.check_box_checked?(@object.send(:"#{method}"), checked_value)
1260
-
1261
- html_options[:id] = html_options[:id] || generate_html_id(method, "")
1262
- input = template.check_box_tag(
1263
- "#{@object_name}[#{method}]",
1264
- checked_value,
1265
- checked,
1266
- html_options
1267
- )
1268
-
1269
- options = options_for_label(options)
1270
- options[:for] ||= html_options[:id]
1271
-
1272
- # the label() method will insert this nested input into the label at the last minute
1273
- options[:label_prefix_for_nested_input] = input
1274
-
1275
- template.hidden_field_tag((html_options[:name] || "#{@object_name}[#{method}]"), unchecked_value, :id => nil, :disabled => html_options[:disabled]) << label(method, options)
1276
- end
1277
-
1278
- # Generates an input for the given method using the type supplied with :as.
1279
- def inline_input_for(method, options)
1280
- send(:"#{options.delete(:as)}_input", method, options)
1281
- end
1282
-
1283
- # Generates hints for the given method using the text supplied in :hint.
1284
- #
1285
- def inline_hints_for(method, options) #:nodoc:
1286
- options[:hint] = localized_string(method, options[:hint], :hint)
1287
- return if options[:hint].blank? or options[:hint].kind_of? Hash
1288
- hint_class = options[:hint_class] || default_hint_class
1289
- template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => hint_class)
1290
- end
1291
-
1292
- # Creates an error sentence by calling to_sentence on the errors array.
1293
- #
1294
- def error_sentence(errors, options = {}) #:nodoc:
1295
- error_class = options[:error_class] || default_inline_error_class
1296
- template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => error_class)
1297
- end
1298
-
1299
- # Creates an error li list.
1300
- #
1301
- def error_list(errors, options = {}) #:nodoc:
1302
- error_class = options[:error_class] || default_error_list_class
1303
- list_elements = []
1304
- errors.each do |error|
1305
- list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
1306
- end
1307
- template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
1308
- end
1309
-
1310
- # Creates an error sentence containing only the first error
1311
- #
1312
- def error_first(errors, options = {}) #:nodoc:
1313
- error_class = options[:error_class] || default_inline_error_class
1314
- template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
1315
- end
1316
-
1317
- # Generates the required or optional string. If the value set is a proc,
1318
- # it evaluates the proc first.
1319
- #
1320
- def required_or_optional_string(required) #:nodoc:
1321
- string_or_proc = case required
1322
- when true
1323
- required_string
1324
- when false
1325
- optional_string
1326
- else
1327
- required
1328
- end
1329
-
1330
- if string_or_proc.is_a?(Proc)
1331
- string_or_proc.call
1332
- else
1333
- string_or_proc.to_s
1334
- end
1335
- end
1336
-
1337
- # Generates a fieldset and wraps the content in an ordered list. When working
1338
- # with nested attributes (in Rails 2.3), it allows %i as interpolation option
1339
- # in :name. So you can do:
1340
- #
1341
- # f.inputs :name => 'Task #%i', :for => :tasks
1342
- #
1343
- # or the shorter equivalent:
1344
- #
1345
- # f.inputs 'Task #%i', :for => :tasks
1346
- #
1347
- # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
1348
- # 'Task #3' and so on.
1349
- #
1350
- # Note: Special case for the inline inputs (non-block):
1351
- # f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
1352
- # f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
1353
- # f.inputs :title, :body, :author # First argument is a column => (no legend)
1354
- #
1355
- def field_set_and_list_wrapping(*args, &block) #:nodoc:
1356
- contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
1357
- html_options = args.extract_options!
1358
-
1359
- legend = html_options.dup.delete(:name).to_s
1360
- legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
1361
- legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
1362
-
1363
- if block_given?
1364
- contents = if template.respond_to?(:is_haml?) && template.is_haml?
1365
- template.capture_haml(&block)
1366
- else
1367
- template.capture(&block)
1368
- end
1369
- end
1370
-
1371
- # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
1372
- contents = contents.join if contents.respond_to?(:join)
1373
- fieldset = template.content_tag(:fieldset,
1374
- Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
1375
- html_options.except(:builder, :parent)
1376
- )
1377
-
1378
- template.concat(fieldset) if block_given? && !Formtastic::Util.rails3?
1379
- fieldset
1380
- end
1381
-
1382
- def field_set_title_from_args(*args) #:nodoc:
1383
- options = args.extract_options!
1384
- options[:name] ||= options.delete(:title)
1385
- title = options[:name]
1386
-
1387
- if title.blank?
1388
- valid_name_classes = [::String, ::Symbol]
1389
- valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && content_columns.include?(args.first))
1390
- title = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
1391
- end
1392
- title = localized_string(title, title, :title) if title.is_a?(::Symbol)
1393
- title
1394
- end
1395
-
1396
- # Also generates a fieldset and an ordered list but with label based in
1397
- # method. This methods is currently used by radio and datetime inputs.
1398
- #
1399
- def field_set_and_list_wrapping_for_method(method, options, contents) #:nodoc:
1400
- contents = contents.join if contents.respond_to?(:join)
1401
-
1402
- template.content_tag(:fieldset,
1403
- template.content_tag(:legend,
1404
- label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
1405
- ) <<
1406
- template.content_tag(:ol, Formtastic::Util.html_safe(contents))
1407
- )
1408
- end
1409
-
1410
- # Generates the legend for radiobuttons and checkboxes
1411
- def legend_tag(method, options = {})
1412
- if options[:label] == false
1413
- Formtastic::Util.html_safe("")
1414
- else
1415
- text = localized_string(method, options[:label], :label) || humanized_attribute_name(method)
1416
- text += required_or_optional_string(options.delete(:required))
1417
- text = Formtastic::Util.html_safe(text)
1418
- template.content_tag :legend, template.label_tag(nil, text, :for => nil), :class => :label
1419
- end
1420
- end
1421
-
1422
- # For methods that have a database column, take a best guess as to what the input method
1423
- # should be. In most cases, it will just return the column type (eg :string), but for special
1424
- # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
1425
- # something different (like :password and :select).
1426
- #
1427
- # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
1428
- # default is a :string, a similar behaviour to Rails' scaffolding.
1429
- #
1430
- def default_input_type(method, options = {}) #:nodoc:
1431
- if column = column_for(method)
1432
- # Special cases where the column type doesn't map to an input method.
1433
- case column.type
1434
- when :string
1435
- return :password if method.to_s =~ /password/
1436
- return :country if method.to_s =~ /country$/
1437
- return :time_zone if method.to_s =~ /time_zone/
1438
- return :email if method.to_s =~ /email/
1439
- return :url if method.to_s =~ /^url$|^website$|_url$/
1440
- return :phone if method.to_s =~ /(phone|fax)/
1441
- return :search if method.to_s =~ /^search$/
1442
- when :integer
1443
- return :select if reflection_for(method)
1444
- return :numeric
1445
- when :float, :decimal
1446
- return :numeric
1447
- when :timestamp
1448
- return :datetime
1449
- end
1450
-
1451
- # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
1452
- return :select if column.type == :string && options.key?(:collection)
1453
- # Try 3: Assume the input name will be the same as the column type (e.g. string_input).
1454
- return column.type
1455
- else
1456
- if @object
1457
- return :select if reflection_for(method)
1458
-
1459
- return :file if is_file?(method, options)
1460
- end
1461
-
1462
- return :select if options.key?(:collection)
1463
- return :password if method.to_s =~ /password/
1464
- return :string
1465
- end
1466
- end
1467
-
1468
- def is_file?(method, options = {})
1469
- @files ||= {}
1470
- @files[method] ||= (options[:as].present? && options[:as] == :file) || begin
1471
- file = @object.send(method) if @object && @object.respond_to?(method)
1472
- file && file_methods.any?{|m| file.respond_to?(m)}
1473
- end
1474
- end
1475
-
1476
- # Used by select and radio inputs. The collection can be retrieved by
1477
- # three ways:
1478
- #
1479
- # * Explicitly provided through :collection
1480
- # * Retrivied through an association
1481
- # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
1482
- #
1483
- # If the collection is not a hash or an array of strings, fixnums or arrays,
1484
- # we use label_method and value_method to retreive an array with the
1485
- # appropriate label and value.
1486
- #
1487
- def find_collection_for_column(column, options) #:nodoc:
1488
- collection = find_raw_collection_for_column(column, options)
1489
-
1490
- # Return if we have a plain string
1491
- return collection if collection.instance_of?(String) || collection.instance_of?(::Formtastic::Util.rails_safe_buffer_class)
1492
-
1493
- # Return if we have an Array of strings, fixnums or arrays
1494
- return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
1495
- [Array, Fixnum, String, Symbol].include?(collection.first.class) &&
1496
- !(options.include?(:label_method) || options.include?(:value_method))
1497
-
1498
- label, value = detect_label_and_value_method!(collection, options)
1499
- collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
1500
- end
1501
-
1502
- # As #find_collection_for_column but returns the collection without mapping the label and value
1503
- #
1504
- def find_raw_collection_for_column(column, options) #:nodoc:
1505
- collection = if options[:collection]
1506
- options.delete(:collection)
1507
- elsif reflection = reflection_for(column)
1508
- options[:find_options] ||= {}
1509
-
1510
- if conditions = reflection.options[:conditions]
1511
- if reflection.klass.respond_to?(:merge_conditions)
1512
- options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
1513
- reflection.klass.all(options[:find_options])
1514
- else
1515
- reflection.klass.where(conditions).where(options[:find_options][:conditions])
1516
- end
1517
- else
1518
- reflection.klass.all(options[:find_options])
1519
- end
1520
- else
1521
- create_boolean_collection(options)
1522
- end
1523
-
1524
- collection = collection.to_a if collection.is_a?(Hash)
1525
- collection
1526
- end
1527
-
1528
- # Detects the label and value methods from a collection using methods set in
1529
- # collection_label_methods and collection_value_methods. For some ruby core
1530
- # classes sensible defaults have been defined. It will use and delete the options
1531
- # :label_method and :value_methods when present.
1532
- #
1533
- def detect_label_and_value_method!(collection, options = {})
1534
- sample = collection.first || collection.last
1535
-
1536
- case sample
1537
- when Array
1538
- label, value = :first, :last
1539
- when Integer
1540
- label, value = :to_s, :to_i
1541
- when String, NilClass
1542
- label, value = :to_s, :to_s
1543
- end
1544
-
1545
- # Order of preference: user supplied method, class defaults, auto-detect
1546
- label = options[:label_method] || label || collection_label_methods.find { |m| sample.respond_to?(m) }
1547
- value = options[:value_method] || value || collection_value_methods.find { |m| sample.respond_to?(m) }
1548
-
1549
- [label, value]
1550
- end
1551
-
1552
- # Return the label collection method when none is supplied using the
1553
- # values set in collection_label_methods.
1554
- #
1555
- def detect_label_method(collection) #:nodoc:
1556
- detect_label_and_value_method!(collection).first
1557
- end
1558
-
1559
- # Detects the method to call for fetching group members from the groups when grouping select options
1560
- #
1561
- def detect_group_association(method, group_by)
1562
- object_to_method_reflection = reflection_for(method)
1563
- method_class = object_to_method_reflection.klass
1564
-
1565
- method_to_group_association = method_class.reflect_on_association(group_by)
1566
- group_class = method_to_group_association.klass
1567
-
1568
- # This will return in the normal case
1569
- return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
1570
-
1571
- # This is for belongs_to associations named differently than their class
1572
- # form.input :parent, :group_by => :customer
1573
- # eg.
1574
- # class Project
1575
- # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1576
- # belongs_to :customer
1577
- # end
1578
- # class Customer
1579
- # has_many :projects
1580
- # end
1581
- group_method = method_class.to_s.underscore.pluralize.to_sym
1582
- return group_method if group_class.reflect_on_association(group_method) # :projects
1583
-
1584
- # This is for has_many associations named differently than their class
1585
- # eg.
1586
- # class Project
1587
- # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1588
- # belongs_to :customer
1589
- # end
1590
- # class Customer
1591
- # has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
1592
- # end
1593
- possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
1594
- return possible_associations.first.name.to_sym if possible_associations.count == 1
1595
-
1596
- raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
1597
-
1598
- end
1599
-
1600
- # Returns a hash to be used by radio and select inputs when a boolean field
1601
- # is provided.
1602
- #
1603
- def create_boolean_collection(options) #:nodoc:
1604
- options[:true] ||= ::Formtastic::I18n.t(:yes)
1605
- options[:false] ||= ::Formtastic::I18n.t(:no)
1606
- options[:value_as_class] = true unless options.key?(:value_as_class)
1607
-
1608
- [ [ options.delete(:true), true], [ options.delete(:false), false ] ]
1609
- end
1610
-
1611
- # Used by association inputs (select, radio) to generate the name that should
1612
- # be used for the input
1613
- #
1614
- # belongs_to :author; f.input :author; will generate 'author_id'
1615
- # belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
1616
- # has_many :authors; f.input :authors; will generate 'author_ids'
1617
- # has_and_belongs_to_many will act like has_many
1618
- #
1619
- def generate_association_input_name(method) #:nodoc:
1620
- if reflection = reflection_for(method)
1621
- if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1622
- "#{method.to_s.singularize}_ids"
1623
- elsif reflection.respond_to? :foreign_key
1624
- reflection.foreign_key
1625
- else
1626
- reflection.options[:foreign_key] || "#{method}_id"
1627
- end
1628
- else
1629
- method
1630
- end.to_sym
1631
- end
1632
-
1633
- # If an association method is passed in (f.input :author) try to find the
1634
- # reflection object.
1635
- #
1636
- def reflection_for(method) #:nodoc:
1637
- @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
1638
- end
1639
-
1640
- # Get a column object for a specified attribute method - if possible.
1641
- #
1642
- def column_for(method) #:nodoc:
1643
- @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1644
- end
1645
-
1646
- # Returns the active validations for the given method or an empty Array if no validations are
1647
- # found for the method.
1648
- #
1649
- # By default, the if/unless options of the validations are evaluated and only the validations
1650
- # that should be run for the current object state are returned. Pass :all to the last
1651
- # parameter to return :all validations regardless of if/unless options.
1652
- #
1653
- # Requires the ValidationReflection plugin to be present or an ActiveModel. Returns an epmty
1654
- # Array if neither is the case.
1655
- #
1656
- def validations_for(method, mode = :active)
1657
- # ActiveModel?
1658
- validations = if @object && @object.class.respond_to?(:validators_on)
1659
- @object.class.validators_on(method)
1660
- else
1661
- # ValidationReflection plugin?
1662
- if @object && @object.class.respond_to?(:reflect_on_validations_for)
1663
- @object.class.reflect_on_validations_for(method)
1664
- else
1665
- []
1666
- end
1667
- end
1668
-
1669
- validations = validations.select do |validation|
1670
- (validation.options.present? ? options_require_validation?(validation.options) : true)
1671
- end unless mode == :all
1672
-
1673
- return validations
1674
- end
1675
-
1676
- # Generates default_string_options by retrieving column information from
1677
- # the database.
1678
- #
1679
- def default_string_options(method, type) #:nodoc:
1680
- def get_maxlength_for(method)
1681
- validation = validations_for(method).find do |validation|
1682
- (validation.respond_to?(:macro) && validation.macro == :validates_length_of) || # Rails 2 validation
1683
- (validation.respond_to?(:kind) && validation.kind == :length) # Rails 3 validator
1684
- end
1685
-
1686
- if validation
1687
- validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
1688
- else
1689
- nil
1690
- end
1691
- end
1692
-
1693
- validation_max_limit = get_maxlength_for(method)
1694
- column = column_for(method)
1695
-
1696
- if type == :text
1697
- { :rows => default_text_area_height,
1698
- :cols => default_text_area_width }
1699
- elsif type == :numeric || column.nil? || !column.respond_to?(:limit) || column.limit.nil?
1700
- { :maxlength => validation_max_limit,
1701
- :size => default_text_field_size }
1702
- else
1703
- { :maxlength => validation_max_limit || column.limit,
1704
- :size => default_text_field_size }
1705
- end
1706
- end
1707
-
1708
- # Generate the html id for the li tag.
1709
- # It takes into account options[:index] and @auto_index to generate li
1710
- # elements with appropriate index scope. It also sanitizes the object
1711
- # and method names.
1712
- #
1713
- def generate_html_id(method_name, value='input') #:nodoc:
1714
- index = if options.has_key?(:index)
1715
- options[:index]
1716
- elsif defined?(@auto_index)
1717
- @auto_index
1718
- else
1719
- ""
1720
- end
1721
- sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
1722
-
1723
- [@@custom_namespace, sanitized_object_name, index, sanitized_method_name, value].reject{|x|x.blank?}.join('_')
1724
- end
1725
-
1726
- # Gets the nested_child_index value from the parent builder. In Rails 2.3
1727
- # it always returns a fixnum. In next versions it returns a hash with each
1728
- # association that the parent builds.
1729
- #
1730
- def parent_child_index(parent) #:nodoc:
1731
- duck = parent[:builder].instance_variable_get('@nested_child_index')
1732
-
1733
- if duck.is_a?(Hash)
1734
- child = parent[:for]
1735
- child = child.first if child.respond_to?(:first)
1736
- duck[child].to_i + 1
1737
- else
1738
- duck.to_i + 1
1739
- end
1740
- end
1741
-
1742
- def sanitized_object_name #:nodoc:
1743
- @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1744
- end
1745
-
1746
- def humanized_attribute_name(method) #:nodoc:
1747
- if @object && @object.class.respond_to?(:human_attribute_name)
1748
- humanized_name = @object.class.human_attribute_name(method.to_s)
1749
- if humanized_name == method.to_s.send(:humanize)
1750
- method.to_s.send(label_str_method)
1751
- else
1752
- humanized_name
1753
- end
1754
- else
1755
- method.to_s.send(label_str_method)
1756
- end
1757
- end
1758
-
1759
- # Internal generic method for looking up localized values within Formtastic
1760
- # using I18n, if no explicit value is set and I18n-lookups are enabled.
1761
- #
1762
- # Enabled/Disable this by setting:
1763
- #
1764
- # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
1765
- #
1766
- # Lookup priority:
1767
- #
1768
- # 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
1769
- # 'formtastic.%{type}.%{model}.%{attribute}'
1770
- # 'formtastic.%{type}.%{attribute}'
1771
- #
1772
- # Example:
1773
- #
1774
- # 'formtastic.labels.post.edit.title'
1775
- # 'formtastic.labels.post.title'
1776
- # 'formtastic.labels.title'
1777
- #
1778
- # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
1779
- #
1780
- def localized_string(key, value, type, options = {}) #:nodoc:
1781
- key = value if value.is_a?(::Symbol)
1782
-
1783
- if value.is_a?(::String)
1784
- escape_html_entities(value)
1785
- else
1786
- use_i18n = value.nil? ? i18n_lookups_by_default : (value != false)
1787
-
1788
- if use_i18n
1789
- model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
1790
- action_name = template.params[:action].to_s rescue ''
1791
- attribute_name = key.to_s
1792
-
1793
- defaults = ::Formtastic::I18n::SCOPES.reject do |i18n_scope|
1794
- nested_model_name.nil? && i18n_scope.match(/nested_model/)
1795
- end.collect do |i18n_scope|
1796
- i18n_path = i18n_scope.dup
1797
- i18n_path.gsub!('%{action}', action_name)
1798
- i18n_path.gsub!('%{model}', model_name)
1799
- i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
1800
- i18n_path.gsub!('%{attribute}', attribute_name)
1801
- i18n_path.gsub!('..', '.')
1802
- i18n_path.to_sym
1803
- end
1804
- defaults << ''
1805
-
1806
- defaults.uniq!
1807
-
1808
- default_key = defaults.shift
1809
- i18n_value = ::Formtastic::I18n.t(default_key,
1810
- options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
1811
- if i18n_value.blank? && type == :label
1812
- # This is effectively what Rails label helper does for i18n lookup
1813
- options[:scope] = [:helpers, type]
1814
- options[:default] = defaults
1815
- i18n_value = ::I18n.t(default_key, options)
1816
- end
1817
- i18n_value = escape_html_entities(i18n_value) if i18n_value.is_a?(::String)
1818
- i18n_value.blank? ? nil : i18n_value
1819
- end
1820
- end
1821
- end
1822
-
1823
- def model_name
1824
- @object.present? ? @object.class.name : @object_name.to_s.classify
1825
- end
1826
-
1827
- def normalize_model_name(name)
1828
- if name =~ /(.+)\[(.+)\]/
1829
- [$1, $2]
1830
- else
1831
- [name]
1832
- end
1833
- end
1834
-
1835
- def send_or_call(duck, object)
1836
- if duck.is_a?(Proc)
1837
- duck.call(object)
1838
- else
1839
- object.send(duck)
1840
- end
1841
- end
1842
-
1843
- def set_include_blank(options)
1844
- unless options.key?(:include_blank) || options.key?(:prompt)
1845
- options[:include_blank] = include_blank_for_select_by_default
1846
- end
1847
- options
1848
- end
1849
-
1850
- def escape_html_entities(string) #:nodoc:
1851
- if escape_html_entities_in_hints_and_labels
1852
- # Acceppt html_safe flag as indicator to skip escaping
1853
- string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true
1854
- end
1855
- string
1856
- end
1857
-
2
+ require 'formtastic/engine' if defined?(::Rails)
3
+
4
+ module Formtastic
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :FormBuilder
8
+ autoload :Helpers
9
+ autoload :HtmlAttributes
10
+ autoload :I18n
11
+ autoload :Inputs
12
+ autoload :Actions
13
+ autoload :LocalizedString
14
+ autoload :Localizer
15
+ autoload :Util
16
+ autoload :NamespacedClassFinder
17
+ autoload :InputClassFinder
18
+ autoload :ActionClassFinder
19
+ autoload :Deprecation
20
+
21
+ # @private
22
+ mattr_accessor :deprecation
23
+ self.deprecation = Formtastic::Deprecation.new('4.0', 'Formtastic')
24
+
25
+ if defined?(::Rails) && Util.deprecated_version_of_rails?
26
+ deprecation.warn("Support for Rails < #{Util.minimum_version_of_rails} will be dropped")
1858
27
  end
1859
28
 
1860
- # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
1861
- #
1862
- # * semantic_form_for(@post)
1863
- # * semantic_fields_for(@post)
1864
- # * semantic_form_remote_for(@post)
1865
- # * semantic_remote_form_for(@post)
1866
- #
1867
- # Each of which are the equivalent of:
1868
- #
1869
- # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1870
- # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
1871
- # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
1872
- # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1873
- #
1874
- # Example Usage:
1875
- #
1876
- # <% semantic_form_for @post do |f| %>
1877
- # <%= f.input :title %>
1878
- # <%= f.input :body %>
1879
- # <% end %>
1880
- #
1881
- # The above examples use a resource-oriented style of form_for() helper where only the @post
1882
- # object is given as an argument, but the generic style is also supported, as are forms with
1883
- # inline objects (Post.new) rather than objects with instance variables (@post):
1884
- #
1885
- # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
1886
- # ...
1887
- # <% end %>
1888
- #
1889
- # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
1890
- # ...
1891
- # <% end %>
1892
- module SemanticFormHelper
1893
- @@builder = ::Formtastic::SemanticFormBuilder
1894
- @@default_form_class = 'formtastic'
1895
- mattr_accessor :builder, :default_form_class
1896
-
1897
- # Override the default ActiveRecordHelper behaviour of wrapping the input.
1898
- # This gets taken care of semantically by adding an error class to the LI tag
1899
- # containing the input.
1900
- #
1901
- FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
1902
- html_tag
1903
- end
1904
-
1905
- def with_custom_field_error_proc(&block)
1906
- default_field_error_proc = ::ActionView::Base.field_error_proc
1907
- ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
1908
- yield
1909
- ensure
1910
- ::ActionView::Base.field_error_proc = default_field_error_proc
1911
- end
1912
-
1913
- def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
1914
- options = args.extract_options!
1915
- if respond_to? :remote_form_for
1916
- semantic_remote_form_for_real(record_or_name_or_array, *(args << options), &proc)
1917
- else
1918
- options[:remote] = true
1919
- semantic_form_for(record_or_name_or_array, *(args << options), &proc)
1920
- end
1921
- end
1922
-
1923
- [:form_for, :fields_for, :remote_form_for].each do |meth|
1924
- module_eval <<-END_SRC, __FILE__, __LINE__ + 1
1925
- def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1926
- options = args.extract_options!
1927
- options[:builder] ||= @@builder
1928
- options[:html] ||= {}
1929
- @@builder.custom_namespace = options[:namespace].to_s
29
+ # @public
30
+ class UnknownInputError < NameError
31
+ end
1930
32
 
1931
- singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
33
+ # @private
34
+ class UnknownActionError < NameError
35
+ end
1932
36
 
1933
- class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1934
- class_names << @@default_form_class
1935
- class_names << case record_or_name_or_array
1936
- when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1937
- when Array then options[:as] || singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
1938
- else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
1939
- end
1940
- options[:html][:class] = class_names.join(" ")
37
+ # @private
38
+ class PolymorphicInputWithoutCollectionError < ArgumentError
39
+ end
1941
40
 
1942
- with_custom_field_error_proc do
1943
- #{meth}(record_or_name_or_array, *(args << options), &proc)
1944
- end
1945
- end
1946
- END_SRC
1947
- end
1948
- alias :semantic_remote_form_for_real :semantic_remote_form_for
1949
- alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
1950
- alias :semantic_form_remote_for :semantic_remote_form_for
41
+ # @private
42
+ class UnsupportedMethodForAction < ArgumentError
43
+ end
1951
44
 
45
+ # @private
46
+ class UnsupportedEnumCollection < NameError
1952
47
  end
1953
- end
1954
48
 
49
+ end