nofxx-formtastic 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Justin French
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,359 @@
1
+ h1. Formtastic 0.1.3
2
+
3
+ Formtastic is a Rails FormBuilder DSL (with some other goodies) to make it far easier to create beautiful, semantically rich, syntactically awesome, readily stylable and wonderfully accessible HTML forms in your Rails applications.
4
+
5
+ h2. The Story
6
+
7
+ One day, I finally had enough, so I opened up my text editor, and wrote a DSL for how I'd like to author forms:
8
+
9
+ <pre>
10
+ <% semantic_form_for @article do |form| %>
11
+
12
+ <% form.inputs :name => "Basic" do %>
13
+ <%= form.input :title %>
14
+ <%= form.input :body %>
15
+ <%= form.input :section %>
16
+ <%= form.input :publication_state, :as => :radio %>
17
+ <%= form.input :category %>
18
+ <%= form.input :allow_comments, :label => "Allow commenting on this article" %>
19
+ <% end %>
20
+
21
+ <% form.inputs :name => "Advanced" do %>
22
+ <%= form.input :keywords, :required => false, :hint => "Example: ruby, rails, forms" %>
23
+ <%= form.input :extract, :required => false %>
24
+ <%= form.input :description, :required => false %>
25
+ <%= form.input :url_title, :required => false %>
26
+ <% end %>
27
+
28
+ <% form.inputs :name => "Author", :for => :author do |author_form| %>
29
+ <%= author_form.input :first_name %>
30
+ <%= author_form.input :last_name %>
31
+ <% end %>
32
+
33
+ <% form.buttons do %>
34
+ <%= form.commit_button %>
35
+ <% end %>
36
+
37
+ <% end %>
38
+ </pre>
39
+
40
+ I also wrote the accompanying HTML output I expected, favoring something very similar to the fieldsets, lists and other semantic elements Aaron Gustafson presented in "Learning to Love Forms":http://www.slideshare.net/AaronGustafson/learning-to-love-forms-web-directions-south-07, hacking together enough Ruby to prove it could be done.
41
+
42
+
43
+ h2. It's better than _SomeOtherFormBuilder_ because...
44
+
45
+ * it can handle @belongs_to@ associations (like Post belongs_to :author), rendering a select or set of radio inputs with choices from the parent model
46
+ * it can handle @has_many@ and @has_and_belongs_to_many@ associations (like Post has_many :tags), rendering a multi-select with choices from the child models
47
+ * it's Rails 2.3-ready (including nested forms)
48
+ * it has internationalization (I18n)!
49
+ * it's _really_ quick to get started with a basic form in place (4 lines), then go back to add in more detail if you need it
50
+ * there's heaps of elements, id and class attributes for you to hook in your CSS and JS
51
+ * it handles real world stuff like inline hints, inline error messages & help text
52
+ * it doesn't hijack or change any of the standard Rails form inputs, so you can still use them as expected (even mix and match)
53
+ * it's got absolutely awesome spec coverage
54
+ * there's a bunch of people using and working on it (it's not just one developer building half a solution)
55
+
56
+
57
+ h2. Why?
58
+
59
+ * web apps = lots of forms
60
+ * forms are so friggin' boring to code
61
+ * semantically rich & accessible forms really are possible
62
+ * the "V" is way behind the "M" and "C" in Rails' MVC – it's the ugly sibling
63
+ * best practices and common patterns have to start somewhere
64
+ * i need a challenge
65
+
66
+
67
+ h2. Opinions
68
+
69
+ * it should be easier to do things the right way than the wrong way
70
+ * sometimes _more mark-up_ is better
71
+ * elements and attribute hooks are _gold_ for stylesheet authors
72
+ * make the common things we do easy, yet still ensure uncommon things are still possible
73
+
74
+
75
+ h2. Installation
76
+
77
+ You can (and should) get it as a gem:
78
+
79
+ <pre>
80
+ gem install justinfrench-formtastic
81
+ </pre>
82
+
83
+ And then add it as a dependency in your environment.rb file:
84
+
85
+ <pre>
86
+ config.gem "justinfrench-formtastic",
87
+ :lib => 'formtastic',
88
+ :source => 'http://gems.github.com',
89
+ :version => '0.1.3'
90
+ </pre>
91
+
92
+ If you're a little more old school, install it as a plugin:
93
+
94
+ <pre>
95
+ ./script/plugin install git://github.com/justinfrench/formtastic.git
96
+ </pre>
97
+
98
+
99
+ h2. Usage
100
+
101
+ Forms are really boring to code... you want to get onto the good stuff as fast as possible.
102
+
103
+ This renders a set of inputs (one for _most_ columns in the database table, and one for each ActiveRecord belongs_to, has_many or has_and_belongs_to_many association) and a submit button:
104
+
105
+ <pre>
106
+ <% semantic_form_for @user do |form| %>
107
+ <%= form.inputs %>
108
+ <%= form.buttons %>
109
+ <% end %>
110
+ </pre>
111
+
112
+ If you want to specify the order of the fields, skip some of the fields or even add in fields that Formtastic couldn't detect, you can pass in a list of field names to @inputs@ and list of button names to @buttons@:
113
+
114
+ <pre>
115
+ <% semantic_form_for @user do |form| %>
116
+ <%= form.inputs :title, :body, :section, :categories, :created_at %>
117
+ <%= form.buttons :commit %>
118
+ <% end %>
119
+ </pre>
120
+
121
+ If you want control over the input type Formtastic uses for each field, you can expand the @inputs@ and @buttons@ blocks. This specifies the :section input should be a set of radio buttons (rather than the default select box), and that the :created_at field should be a string (rather than the default datetime selects):
122
+
123
+ <pre>
124
+ <% semantic_form_for @post do |form| %>
125
+ <% form.inputs do %>
126
+ <%= form.input :title %>
127
+ <%= form.input :body %>
128
+ <%= form.input :section, :as => :radio %>
129
+ <%= form.input :categories %>
130
+ <%= form.input :created_at, :as => :string %>
131
+ <% end %>
132
+ <% form.buttons do %>
133
+ <%= form.commit_button %>
134
+ <% end %>
135
+ <% end %>
136
+ </pre>
137
+
138
+ If you want to customize the label text, or render some hint text below the field, specify which fields are required/option, or break the form into two fieldsets, the DSL is pretty comprehensive:
139
+
140
+ <pre>
141
+ <% semantic_form_for @post do |form| %>
142
+ <% form.inputs :name => "Basic", :id => "basic" do %>
143
+ <%= form.input :title %>
144
+ <%= form.input :body %>
145
+ <% end %>
146
+ <% form.inputs :name => "Advanced Options", :id => "advanced" do %>
147
+ <%= form.input :slug, :label => "URL Title", :hint => "Created automatically if left blank", :required => false %>
148
+ <%= form.input :section, :as => :radio %>
149
+ <%= form.input :user, :label => "Author", :label_method => :full_name, %>
150
+ <%= form.input :categories, :required => false %>
151
+ <%= form.input :created_at, :as => :string, :label => "Publication Date", :required => false %>
152
+ <% end %>
153
+ <% form.buttons do %>
154
+ <%= form.commit_button %>
155
+ <% end %>
156
+ <% end %>
157
+ </pre>
158
+
159
+ If you want to customize html elements for any non button inputs you just need
160
+ to specify the :input_html options hash.
161
+
162
+ <pre>
163
+ <% semantic_form_for @post do |form| %>
164
+ <%= form.input :title, :input_html => {:size => 60} %>
165
+ <%= form.input :body %>
166
+ <%= form.input :created_at, :input_html => {:disabled => true} %>
167
+ <%= form.buttons %>
168
+ <% end %>
169
+ </pre>
170
+
171
+ Nested forms (Rails 2.3) are also supported. You can do it in the Rails way:
172
+
173
+ <pre>
174
+ <% semantic_form_for @post do |form| %>
175
+ <%= form.inputs :title, :body, :created_at %>
176
+
177
+ <% form.semantic_fields_for :author do |author| %>
178
+ <%= author.inputs :first_name, :last_name, :name => 'Author' %>
179
+ <% end %>
180
+
181
+ <%= form.buttons %>
182
+ <% end %>
183
+ </pre>
184
+
185
+ Or in the formtastic way:
186
+
187
+ <pre>
188
+ <% semantic_form_for @post do |form| %>
189
+ <%= form.inputs :title, :body, :created_at %>
190
+
191
+ <%= form.inputs :first_name, :last_name, :for => :author, :name => "Author" %>
192
+
193
+ <%= form.buttons %>
194
+ <% end %>
195
+ </pre>
196
+
197
+ When working in has many association, you can even supply "%i" in your fieldset
198
+ name that it will be properly interpolated with the child index. For example:
199
+
200
+ <pre>
201
+ <% semantic_form_for @post do |form| %>
202
+ <%= form.inputs %>
203
+ <%= form.inputs :name => 'Category #%i', :for => :categories %>
204
+ <%= form.buttons %>
205
+ <% end %>
206
+ </pre>
207
+
208
+ Each category will be wrapped in a fieldset with legend "Category #1",
209
+ "Category #2" and so on. But please notice that this works only with Rails 2.3.
210
+
211
+ h2. The Available Inputs
212
+
213
+ * :select (a select menu) - default for ActiveRecord associations (belongs_to, has_many, has_and_belongs_to_many)
214
+ * :radio (a set of radio inputs) - alternative to :select for ActiveRecord belongs_to associations
215
+ * :password (a password input) - default for :string column types with 'password' in the method name
216
+ * :text (a textarea) - default for :text column types
217
+ * :date (a date select) - default for :date column types
218
+ * :datetime (a date and time select) - default for :datetime and :timestamp column types
219
+ * :time (a time select) - default for :time column types
220
+ * :boolean (a checkbox) - default for :boolean column types
221
+ * :boolean_select (a yes/no select box)
222
+ * :string (a text field) - default for :string column types
223
+ * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
224
+ * :file (a file field) - default for paperclip or attachment_fu attributes
225
+
226
+ The documentation is pretty good for each of these (what it does, what the output is, what the options are, etc) so go check it out.
227
+
228
+
229
+ h2. Configuration
230
+
231
+ If you wish, put something like this in config/initializers/formtastic_config.rb:
232
+
233
+ <pre>
234
+ # Set the default text field size when input is a string. Default is 50
235
+ Formtastic::SemanticFormBuilder.default_text_field_size = 30
236
+
237
+ # Should all fields be considered "required" by default
238
+ # Defaults to true, see ValidationReflection notes below
239
+ Formtastic::SemanticFormBuilder.all_fields_required_by_default = false
240
+
241
+ # Set the string that will be appended to the labels/fieldsets which are required
242
+ # It accepts string or procs and the default is a localized version of
243
+ # '<abbr title="required">*</abbr>'. In other words, if you configure formtastic.required
244
+ # in your locale, it will replace the abbr title properly. But if you don't want to use
245
+ # abbr tag, you can simply give a string as below
246
+ Formtastic::SemanticFormBuilder.required_string = "(required)"
247
+
248
+ # Set the string that will be appended to the labels/fieldsets which are optional
249
+ # Defaults to an empty string ("") and also accepts procs (see required_string above)
250
+ Formtastic::SemanticFormBuilder.optional_string = "(optional)"
251
+
252
+ # Set the way inline errors will be displayed.
253
+ # Defaults to :sentence, valid options are :sentence, :list and :none
254
+ Formtastic::SemanticFormBuilder.inline_errors = :list
255
+
256
+ # Set the method to call on label text to transform or format it for human-friendly
257
+ # reading when formtastic is user without object. Defaults to :humanize.
258
+ Formtastic::SemanticFormBuilder.label_str_method = :titleize
259
+
260
+ # Set the array of methods to try calling on parent objects in :select and :radio inputs
261
+ # for the text inside each @<option>@ tag or alongside each radio @<input>@. The first method
262
+ # that is found on the object will be used.
263
+ # Defaults to ["to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
264
+ Formtastic::SemanticFormBuilder.collection_label_methods = ["title_and_author", "display_name", "login", "to_s"]
265
+
266
+ # Formtastic by default renders inside li tags the input, hints and then
267
+ # errors messages. Sometimes you want the hints to be rendered first than
268
+ # the input, in the following order: hints, input and errors. You can
269
+ # customize it doing just as below:
270
+ Formtastic::SemanticFormBuilder.inline_order = [:hints, :input, :errors]
271
+ </pre>
272
+
273
+
274
+ h2. Internationalization (I18n)
275
+
276
+ Supports I18n! ActiveRecord object names and attributes are, by default, taken from calling @object.human_name and @object.human_attribute_name(attr) respectively. There are a few words specific to Formtastic that can be translated. See lib/locale/en.yml for more information.
277
+
278
+
279
+ h2. ValidationReflection plugin
280
+
281
+ If you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin installed, you won't have to specify the :required option (it checks the validations on the model instead).
282
+
283
+
284
+ h2. Status
285
+
286
+ *THINGS ARE GOING TO CHANGE A BIT BEFORE WE HIT 1.0.*
287
+
288
+ It's a work in progress and a bit rough around the edges still, but I hope you try it and offer some suggestions and improvements anyway.
289
+
290
+ On the plus side, it has a comprehensive spec suite and contributions from at least ten independent developers.
291
+
292
+ "Wishlist":http://wiki.github.com/justinfrench/formtastic/wishlist on the wiki is serving as pretty good documentation for the roadmap to 1.0 and beyond right now, but I'll work on getting a real tracking system or something happening soon.
293
+
294
+
295
+ h2. Dependencies
296
+
297
+ There are none, but...
298
+
299
+ * if you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin is installed, you won't have to specify the :required option (it checks the validations on the model instead)
300
+ * rspec, rspec_hpricot_matchers and rcov gems (plus any of their own dependencies) are required for the test suite
301
+
302
+
303
+ h2. Compatibility
304
+
305
+ I'm only testing Formtastic with the latest Rails 2.2.x stable release, and it should be fine under Rails 2.3 as well (including nested forms). Patches are welcome to allow backwards compatibility, but I don't have the energy!
306
+
307
+
308
+
309
+ h2. What about Stylesheets?
310
+
311
+ A proof-of-concept (very much a work-in-progress) stylesheet is provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet so that the Formtastic styles can be updated without clobbering your changes.
312
+
313
+ 1. Use the generator to copy the formtastic.css and formtastic_changes.css into your public directory
314
+
315
+ <pre>
316
+ ./script/generate formtastic_stylesheets
317
+ </pre>
318
+
319
+ 2. Add both formtastic.css and formtastic_changes.css to your layout:
320
+
321
+ <pre>
322
+ <%= stylesheet_link_tag "formtastic" %>
323
+ <%= stylesheet_link_tag "formtastic_changes" %>
324
+ </pre>
325
+
326
+
327
+ h2. Contributors
328
+
329
+ Formtastic wouldn't be as awesome as it is today if it weren't for the wonderful contributions of these fine, fine coders. An extra huge thanks goes out to "José Valim":http://github.com/josevalim for nearly 50 patches.
330
+
331
+ * "Justin French":http://justinfrench.com
332
+ * "Xavier Shay":http://rhnh.net
333
+ * "Bin Dong":http://github.com/dongbin
334
+ * "Ben Hamill":http://blog.benhamill.com/
335
+ * "Pat Allan":http://github.com/freelancing-god
336
+ * "negonicrac":http://github.com/negonicrac
337
+ * "Andy Pearson":http://github.com/andypearson
338
+ * "Mark Mansour":http://stateofflux.com
339
+ * "Tien Dung":http://github.com/tiendung
340
+ * "Sascha Hoellger":http://github.com/mitnal
341
+ * "Jeff Smick":http://github.com/sprsquish
342
+ * "José Valim":http://github.com/josevalim
343
+ * "Greg Fitzgerald":http://github.com/gregf/
344
+ * "Gareth Townsend":http://github.com/quamen
345
+ * "Jack Dempsey":http://github.com/jackdempsey/
346
+ * "Simon Chiu":http://github.com/tolatomeow
347
+
348
+
349
+ h2. Hey, join the Google group!
350
+
351
+ Please join the "Formtastic Google Group":http://groups.google.com.au/group/formtastic, especially if you'd like to talk about a new feature, or report a bug.
352
+
353
+
354
+ h2. Project Info
355
+
356
+ Formtastic is hosted on Github: http://github.com/justinfrench/formtastic/, where your contributions, forkings, comments and feedback are greatly welcomed.
357
+
358
+
359
+ Copyright (c) 2007-2008 Justin French, released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'spec/rake/spectask'
4
+
5
+ begin
6
+ GEM = "formtastic"
7
+ AUTHOR = "Justin French"
8
+ EMAIL = "justin@indent.com.au"
9
+ SUMMARY = "A Rails form builder plugin/gem with semantically rich and accessible markup"
10
+ HOMEPAGE = "http://github.com/justinfrench/formtastic/tree/master"
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |s|
14
+ s.name = GEM
15
+ s.summary = SUMMARY
16
+ s.email = EMAIL
17
+ s.homepage = HOMEPAGE
18
+ s.description = SUMMARY
19
+ s.author = AUTHOR
20
+
21
+ s.require_path = 'lib'
22
+ s.autorequire = GEM
23
+ s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{rails,lib,spec}/**/*")
24
+ end
25
+ rescue LoadError
26
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
27
+ end
28
+
29
+ desc 'Default: run unit specs.'
30
+ task :default => :spec
31
+
32
+ desc 'Test the formtastic plugin.'
33
+ Spec::Rake::SpecTask.new('spec') do |t|
34
+ t.spec_files = FileList['spec/**/*_spec.rb']
35
+ t.spec_opts = ["-c"]
36
+ end
37
+
38
+ desc 'Test the formtastic plugin with specdoc formatting and colors'
39
+ Spec::Rake::SpecTask.new('specdoc') do |t|
40
+ t.spec_files = FileList['spec/**/*_spec.rb']
41
+ t.spec_opts = ["--format specdoc", "-c"]
42
+ end
43
+
44
+ desc 'Generate documentation for the formtastic plugin.'
45
+ Rake::RDocTask.new(:rdoc) do |rdoc|
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = 'Formtastic'
48
+ rdoc.options << '--line-numbers' << '--inline-source'
49
+ rdoc.rdoc_files.include('README.textile')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
53
+ desc "Run all examples with RCov"
54
+ Spec::Rake::SpecTask.new('examples_with_rcov') do |t|
55
+ t.spec_files = FileList['spec/**/*_spec.rb']
56
+ t.rcov = true
57
+ t.rcov_opts = ['--exclude', 'spec,Library']
58
+ end
data/lib/formtastic.rb ADDED
@@ -0,0 +1,1098 @@
1
+ # Override the default ActiveRecordHelper behaviour of wrapping the input.
2
+ # This gets taken care of semantically by adding an error class to the LI tag
3
+ # containing the input.
4
+ ActionView::Base.field_error_proc = proc do |html_tag, instance_tag|
5
+ html_tag
6
+ end
7
+
8
+ module Formtastic #:nodoc:
9
+
10
+ class SemanticFormBuilder < ActionView::Helpers::FormBuilder
11
+
12
+ @@default_text_field_size = 50
13
+ @@all_fields_required_by_default = true
14
+ @@required_string = proc { %{<abbr title="#{I18n.t 'formtastic.required', :default => 'required'}">*</abbr>} }
15
+ @@optional_string = ''
16
+ @@inline_errors = :sentence
17
+ @@label_str_method = :humanize
18
+ @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
19
+ @@inline_order = [ :input, :hints, :errors ]
20
+ @@file_methods = [ :file?, :public_filename ]
21
+
22
+ cattr_accessor :default_text_field_size, :all_fields_required_by_default, :required_string,
23
+ :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
24
+ :inline_order, :file_methods
25
+
26
+ # Keeps simple mappings in a hash
27
+ #
28
+ INPUT_MAPPINGS = {
29
+ :string => :text_field,
30
+ :password => :password_field,
31
+ :numeric => :text_field,
32
+ :text => :text_area,
33
+ :file => :file_field
34
+ }
35
+ STRING_MAPPINGS = [ :string, :password, :numeric ]
36
+
37
+ attr_accessor :template
38
+ attr_writer :nested_child_index
39
+
40
+ # Returns a suitable form input for the given +method+, using the database column information
41
+ # and other factors (like the method name) to figure out what you probably want.
42
+ #
43
+ # Options:
44
+ #
45
+ # * :as - override the input type (eg force a :string to render as a :password field)
46
+ # * :label - use something other than the method name as the label (or fieldset legend) text
47
+ # * :required - specify if the column is required (true) or not (false)
48
+ # * :hint - provide some text to hint or help the user provide the correct information for a field
49
+ # * :input_html - provide options that will be passed down to the generated input
50
+ #
51
+ # Input Types:
52
+ #
53
+ # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
54
+ # but there are a few special cases and some simplification (:integer, :float and :decimal
55
+ # columns all map to a single numeric_input, for example).
56
+ #
57
+ # * :select (a select menu for associations) - default to association names
58
+ # * :radio (a set of radio inputs for associations) - default to association names
59
+ # * :password (a password input) - default for :string column types with 'password' in the method name
60
+ # * :text (a textarea) - default for :text column types
61
+ # * :date (a date select) - default for :date column types
62
+ # * :datetime (a date and time select) - default for :datetime and :timestamp column types
63
+ # * :time (a time select) - default for :time column types
64
+ # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
65
+ # * :string (a text field) - default for :string column types
66
+ # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
67
+ #
68
+ # Example:
69
+ #
70
+ # <% semantic_form_for @employee do |form| %>
71
+ # <% form.inputs do -%>
72
+ # <%= form.input :name, :label => "Full Name"%>
73
+ # <%= form.input :manager_id, :as => :radio %>
74
+ # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
75
+ # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
76
+ # <% end %>
77
+ # <% end %>
78
+ #
79
+ def input(method, options = {})
80
+ options[:required] = method_required?(method, options[:required])
81
+ options[:as] ||= default_input_type(method)
82
+
83
+ options[:label] ||= if @object
84
+ @object.class.human_attribute_name(method.to_s)
85
+ else
86
+ method.to_s.send(@@label_str_method)
87
+ end
88
+
89
+ if [:boolean_select, :boolean_radio].include?(options[:as])
90
+ ::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
91
+ end
92
+
93
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ].join(' ')
94
+ html_class << ' error' if @object && @object.errors.on(method.to_s)
95
+
96
+ html_id = generate_html_id(method)
97
+
98
+ list_item_content = @@inline_order.map do |type|
99
+ send(:"inline_#{type}_for", method, options)
100
+ end.compact.join("\n")
101
+
102
+ return template.content_tag(:li, list_item_content, { :id => html_id, :class => html_class })
103
+ end
104
+
105
+ # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
106
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
107
+ # or with a list of fields. These two examples are functionally equivalent:
108
+ #
109
+ # # With a block:
110
+ # <% semantic_form_for @post do |form| %>
111
+ # <% form.inputs do %>
112
+ # <%= form.input :title %>
113
+ # <%= form.input :body %>
114
+ # <% end %>
115
+ # <% end %>
116
+ #
117
+ # # With a list of fields:
118
+ # <% semantic_form_for @post do |form| %>
119
+ # <%= form.inputs :title, :body %>
120
+ # <% end %>
121
+ #
122
+ # # Output:
123
+ # <form ...>
124
+ # <fieldset class="inputs">
125
+ # <ol>
126
+ # <li class="string">...</li>
127
+ # <li class="text">...</li>
128
+ # </ol>
129
+ # </fieldset>
130
+ # </form>
131
+ #
132
+ # === Quick Forms
133
+ #
134
+ # When called without a block or a field list, an input is rendered for each column in the
135
+ # model's database table, just like Rails' scaffolding. You'll obviously want more control
136
+ # than this in a production application, but it's a great way to get started, then come back
137
+ # later to customise the form with a field list or a block of inputs. Example:
138
+ #
139
+ # <% semantic_form_for @post do |form| %>
140
+ # <%= form.inputs %>
141
+ # <% end %>
142
+ #
143
+ # === Options
144
+ #
145
+ # All options (with the exception of :name) are passed down to the fieldset as HTML
146
+ # attributes (id, class, style, etc). If provided, the :name option is passed into a
147
+ # legend tag inside the fieldset (otherwise a legend is not generated).
148
+ #
149
+ # # With a block:
150
+ # <% semantic_form_for @post do |form| %>
151
+ # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
152
+ # ...
153
+ # <% end %>
154
+ # <% end %>
155
+ #
156
+ # # With a list (the options must come after the field list):
157
+ # <% semantic_form_for @post do |form| %>
158
+ # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
159
+ # <% end %>
160
+ #
161
+ # === It's basically a fieldset!
162
+ #
163
+ # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
164
+ # use inputs:
165
+ #
166
+ # <% semantic_form_for @post do |f| %>
167
+ # <% f.inputs do %>
168
+ # <%= f.input :title %>
169
+ # <%= f.input :body %>
170
+ # <% end %>
171
+ # <% f.inputs :name => "Advanced", :id => "advanced" do %>
172
+ # <%= f.input :created_at %>
173
+ # <%= f.input :user_id, :label => "Author" %>
174
+ # <% end %>
175
+ # <% end %>
176
+ #
177
+ # # Output:
178
+ # <form ...>
179
+ # <fieldset class="inputs">
180
+ # <ol>
181
+ # <li class="string">...</li>
182
+ # <li class="text">...</li>
183
+ # </ol>
184
+ # </fieldset>
185
+ # <fieldset class="inputs" id="advanced">
186
+ # <legend><span>Advanced</span></legend>
187
+ # <ol>
188
+ # <li class="datetime">...</li>
189
+ # <li class="select">...</li>
190
+ # </ol>
191
+ # </fieldset>
192
+ # </form>
193
+ #
194
+ # === Nested attributes
195
+ #
196
+ # As in Rails, you can use semantic_fields_for to nest attributes:
197
+ #
198
+ # <% semantic_form_for @post do |form| %>
199
+ # <%= form.inputs :title, :body %>
200
+ #
201
+ # <% form.semantic_fields_for :author, @bob do |author_form| %>
202
+ # <% author_form.inputs do %>
203
+ # <%= author_form.input :first_name, :required => false %>
204
+ # <%= author_form.input :last_name %>
205
+ # <% end %>
206
+ # <% end %>
207
+ # <% end %>
208
+ #
209
+ # But this does not look formtastic! This is equivalent:
210
+ #
211
+ # <% semantic_form_for @post do |form| %>
212
+ # <%= form.inputs :title, :body %>
213
+ # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
214
+ # <%= author_form.input :first_name, :required => false %>
215
+ # <%= author_form.input :last_name %>
216
+ # <% end %>
217
+ # <% end %>
218
+ #
219
+ # And if you don't need to give options to your input call, you could do it
220
+ # in just one line:
221
+ #
222
+ # <% semantic_form_for @post do |form| %>
223
+ # <%= form.inputs :title, :body %>
224
+ # <%= form.inputs :first_name, :last_name, :for => @bob %>
225
+ # <% end %>
226
+ #
227
+ # Just remember that calling inputs generates a new fieldset to wrap your
228
+ # inputs. If you have two separate models, but, semantically, on the page
229
+ # they are part of the same fieldset, you should use semantic_fields_for
230
+ # instead (just as you would do with Rails' form builder).
231
+ #
232
+ def inputs(*args, &block)
233
+ html_options = args.extract_options!
234
+ html_options[:class] ||= "inputs"
235
+
236
+ if fields_for_object = html_options.delete(:for)
237
+ html_options.merge!(:parent_builder => self)
238
+ inputs_for_nested_attributes(fields_for_object, args << html_options,
239
+ html_options.delete(:for_options) || {}, &block)
240
+ elsif block_given?
241
+ field_set_and_list_wrapping(html_options, &block)
242
+ else
243
+ if @object && args.empty?
244
+ # Get all belongs_to association
245
+ args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
246
+
247
+ # Get content columns and remove timestamps columns from it
248
+ args += @object.class.content_columns.map(&:name)
249
+ args -= %w[created_at updated_at created_on updated_on]
250
+
251
+ args.compact!
252
+ end
253
+ contents = args.map { |method| input(method.to_sym) }
254
+
255
+ field_set_and_list_wrapping(html_options, contents)
256
+ end
257
+ end
258
+ alias :input_field_set :inputs
259
+
260
+ # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
261
+ # See inputs documentation for a full example. The fieldset's default class attriute
262
+ # is set to "buttons".
263
+ #
264
+ # See inputs for html attributes and special options.
265
+ def buttons(*args, &block)
266
+ html_options = args.extract_options!
267
+ html_options[:class] ||= "buttons"
268
+
269
+ if block_given?
270
+ field_set_and_list_wrapping(html_options, &block)
271
+ else
272
+ args = [:commit] if args.empty?
273
+ contents = args.map { |button_name| send(:"#{button_name}_button") }
274
+ field_set_and_list_wrapping(html_options, contents)
275
+ end
276
+ end
277
+ alias :button_field_set :buttons
278
+
279
+ # Creates a submit input tag with the value "Save [model name]" (for existing records) or
280
+ # "Create [model name]" (for new records) by default:
281
+ #
282
+ # <%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />
283
+ #
284
+ # The value of the button text can be overridden:
285
+ #
286
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
287
+ #
288
+ def commit_button(value=nil, options = {})
289
+ value ||= save_or_create_button_text
290
+ template.content_tag(:li, template.submit_tag(value), :class => "commit")
291
+ end
292
+
293
+ # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
294
+ # for nesting forms:
295
+ #
296
+ # # Example:
297
+ # <% semantic_form_for @post do |post| %>
298
+ # <% post.semantic_fields_for :author do |author| %>
299
+ # <% author.inputs :name %>
300
+ # <% end %>
301
+ # <% end %>
302
+ #
303
+ # # Output:
304
+ # <form ...>
305
+ # <fieldset class="inputs">
306
+ # <ol>
307
+ # <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
308
+ # </ol>
309
+ # </fieldset>
310
+ # </form>
311
+ #
312
+ def semantic_fields_for(record_or_name_or_array, *args, &block)
313
+ opts = args.extract_options!
314
+ opts.merge!(:builder => Formtastic::SemanticFormBuilder)
315
+ args.push(opts)
316
+ fields_for(record_or_name_or_array, *args, &block)
317
+ end
318
+
319
+ #
320
+ # Stolen from Attribute_fu (http://github.com/giraffesoft/attribute_fu)
321
+ # Rails 2.3 Patches from http://github.com/odadata/attribute_fu
322
+ #
323
+ # Dinamically add and remove nested forms for a has_many relation.
324
+ #
325
+
326
+ # Add a link to remove the associated partial
327
+ def remove_link(name, *args)
328
+ options = args.extract_options!
329
+ css_selector = options.delete(:selector) || ".#{@object.class.name.split("::").last.underscore}"
330
+
331
+ function = options.delete(:function) || ""
332
+ function << "$(this).parents('#{css_selector}').hide(); $(this).prev(':input').val('1');"
333
+
334
+ out = hidden_field(:_delete)
335
+ out += template.link_to_function(name, function, *args.push(options))
336
+ end
337
+
338
+ # Add a link to add more partials
339
+ def add_associated_link(name, association, opts = {})
340
+ object = @object.send(association).build
341
+ associated_name = extract_option_or_class_name(opts, :name, object)
342
+ variable = "formtastic_next_#{associated_name}_id"
343
+
344
+ opts.symbolize_keys!
345
+
346
+ partial = opts.delete(:partial) || associated_name
347
+ container = opts.delete(:expression) || "'#{opts.delete(:container) || '#'+associated_name.pluralize}'"
348
+
349
+ function = "if (typeof #{variable} == 'undefined') #{variable} = 0;
350
+ $(#{container}).append($.template(" +
351
+ [self.render_associated_form(object, :fields_for => { :javascript => true },
352
+ :partial => partial)].flatten.first.to_json+"), { number: --#{variable}});"
353
+
354
+ template.link_to_function(name, function, opts)
355
+ end
356
+
357
+ # Render associated form
358
+ def render_associated_form(associated, opts = {})
359
+ associated = associated.is_a?(Array) ? associated : [associated] # preserve association proxy if this is one
360
+
361
+ opts.symbolize_keys!
362
+ (opts[:new] - associated.select(&:new_record?).length).times { associated.build } if opts[:new]
363
+
364
+
365
+ unless associated.empty?
366
+ name = extract_option_or_class_name(opts, :name, associated.first)
367
+ partial = opts[:partial] || name
368
+ local_assign_name = partial.split('/').last.split('.').first
369
+
370
+ output = associated.map do |element|
371
+ fields_for(association_name(name), element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
372
+ template.render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => f.object, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
373
+ end
374
+ end
375
+ output.join
376
+ end
377
+ end
378
+
379
+ private
380
+
381
+ def association_name(class_name)
382
+ @object.respond_to?("#{class_name}_attributes=") ? class_name : class_name.pluralize
383
+ end
384
+
385
+ def extract_option_or_class_name(hash, option, object)
386
+ (hash.delete(option) || object.class.name.split('::').last.underscore).to_s
387
+ end
388
+ # End attribute_fu magic #
389
+
390
+ protected
391
+
392
+ # Deals with :for option when it's supplied to inputs methods. Additional
393
+ # options to be passed down to :for should be supplied using :for_options
394
+ # key.
395
+ #
396
+ # It should raise an error if a block with arity zero is given.
397
+ #
398
+ def inputs_for_nested_attributes(fields_for_object, inputs, options, &block)
399
+ fields_for_block = if block_given?
400
+ raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
401
+ 'but the block does not accept any argument.' if block.arity <= 0
402
+
403
+ proc { |f| f.inputs(*inputs){ block.call(f) } }
404
+ else
405
+ proc { |f| f.inputs(*inputs) }
406
+ end
407
+
408
+ semantic_fields_for(*(Array(fields_for_object) << options), &fields_for_block)
409
+ end
410
+
411
+ # Remove any Formtastic-specific options before passing the down options.
412
+ #
413
+ def set_options(options)
414
+ options.except(:value_method, :label_method, :collection, :required, :label,
415
+ :as, :hint, :input_html, :label_html, :value_as_class)
416
+ end
417
+
418
+ # Create a default button text. If the form is working with a object, it
419
+ # defaults to "Create model" or "Save model" depending if we are working
420
+ # with a new_record or not.
421
+ #
422
+ # When not working with models, it defaults to "Submit object".
423
+ #
424
+ def save_or_create_button_text(prefix='Submit') #:nodoc:
425
+ if @object
426
+ prefix = @object.new_record? ? 'Create' : 'Save'
427
+ object_name = @object.class.human_name
428
+ else
429
+ object_name = @object_name.to_s.send(@@label_str_method)
430
+ end
431
+
432
+ I18n.t(prefix.downcase, :default => prefix, :scope => [:formtastic]) << ' ' << object_name
433
+ end
434
+
435
+ # Determins if the attribute (eg :title) should be considered required or not.
436
+ #
437
+ # * if the :required option was provided in the options hash, the true/false value will be
438
+ # returned immediately, allowing the view to override any guesswork that follows:
439
+ #
440
+ # * if the :required option isn't provided in the options hash, and the ValidationReflection
441
+ # plugin is installed (http://github.com/redinger/validation_reflection), true is returned
442
+ # if the validates_presence_of macro has been used in the class for this attribute, or false
443
+ # otherwise.
444
+ #
445
+ # * if the :required option isn't provided, and the plugin isn't available, the value of the
446
+ # configuration option @@all_fields_required_by_default is used.
447
+ #
448
+ def method_required?(attribute, required_option) #:nodoc:
449
+ return required_option unless required_option.nil?
450
+
451
+ if @object && @object.class.respond_to?(:reflect_on_all_validations)
452
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
453
+
454
+ @object.class.reflect_on_all_validations.any? do |validation|
455
+ validation.macro == :validates_presence_of && validation.name == attribute_sym
456
+ end
457
+ else
458
+ @@all_fields_required_by_default
459
+ end
460
+ end
461
+
462
+ # A method that deals with most of inputs (:string, :password, :file,
463
+ # :textarea and :numeric). :select, :radio, :boolean and :datetime inputs
464
+ # are not handled by this method, since they need more detailed approach.
465
+ #
466
+ # If input_html is given as option, it's passed down to the input.
467
+ #
468
+ def input_simple(type, method, options)
469
+ html_options = options.delete(:input_html) || {}
470
+ html_options = default_string_options(method).merge(html_options) if STRING_MAPPINGS.include?(type)
471
+
472
+ input_label(method, options.delete(:label), options.slice(:required)) + send(INPUT_MAPPINGS[type], method, html_options)
473
+ end
474
+
475
+ # Outputs a label and a select box containing options from the parent
476
+ # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
477
+ # is has_many or has_and_belongs_to_many the select box will be set as multi-select
478
+ # and size = 5
479
+ #
480
+ # Example (belongs_to):
481
+ #
482
+ # f.input :author
483
+ #
484
+ # <label for="book_author_id">Author</label>
485
+ # <select id="book_author_id" name="book[author_id]">
486
+ # <option value="1">Justin French</option>
487
+ # <option value="2">Jane Doe</option>
488
+ # </select>
489
+ #
490
+ # Example (has_many):
491
+ #
492
+ # f.input :chapters
493
+ #
494
+ # <label for="book_chapter_ids">Chapters</label>
495
+ # <select id="book_chapter_ids" name="book[chapter_ids]">
496
+ # <option value="1">Chapter 1</option>
497
+ # <option value="2">Chapter 2</option>
498
+ # </select>
499
+ #
500
+ # Example (has_and_belongs_to_many):
501
+ #
502
+ # f.input :authors
503
+ #
504
+ # <label for="book_author_ids">Authors</label>
505
+ # <select id="book_author_ids" name="book[author_ids]">
506
+ # <option value="1">Justin French</option>
507
+ # <option value="2">Jane Doe</option>
508
+ # </select>
509
+ #
510
+ #
511
+ # You can customize the options available in the select by passing in a collection (Array) of
512
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
513
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
514
+ # it (VehicleOwner.find(:all) in the example above).
515
+ #
516
+ # Examples:
517
+ #
518
+ # f.input :author, :collection => @authors
519
+ # f.input :author, :collection => Author.find(:all)
520
+ # f.input :author, :collection => [@justin, @kate]
521
+ # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
522
+ #
523
+ # Note: This input looks for a label method in the parent association.
524
+ #
525
+ # You can customize the text label inside each option tag, by naming the correct method
526
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
527
+ # by passing in the :label_method option. By default the :label_method is whichever element of
528
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
529
+ #
530
+ # Examples:
531
+ #
532
+ # f.input :author, :label_method => :full_name
533
+ # f.input :author, :label_method => :display_name
534
+ # f.input :author, :label_method => :to_s
535
+ # f.input :author, :label_method => :label
536
+ #
537
+ # You can also customize the value inside each option tag, by passing in the :value_method option.
538
+ # Usage is the same as the :label_method option
539
+ #
540
+ # Examples:
541
+ #
542
+ # f.input :author, :value_method => :full_name
543
+ # f.input :author, :value_method => :display_name
544
+ # f.input :author, :value_method => :to_s
545
+ # f.input :author, :value_method => :value
546
+ #
547
+ # You can pass html_options to the select tag using :input_html => {}
548
+ #
549
+ # Examples:
550
+ #
551
+ # f.input :authors, :input_html => {:size => 20, :multiple => true}
552
+ #
553
+ def select_input(method, options)
554
+ collection = find_collection_for_column(method, options)
555
+ html_options = options.delete(:input_html) || {}
556
+
557
+ reflection = find_reflection(method)
558
+ if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
559
+ html_options[:multiple] ||= true
560
+ html_options[:size] ||= 5
561
+ end
562
+
563
+ input_name = generate_association_input_name(method)
564
+ input_label(input_name, options.delete(:label), options.slice(:required)) +
565
+ self.select(input_name, collection, set_options(options), html_options)
566
+ end
567
+ alias :boolean_select_input :select_input
568
+
569
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
570
+ # items, one for each possible choice in the belongs_to association. Each li contains a
571
+ # label and a radio input.
572
+ #
573
+ # Example:
574
+ #
575
+ # f.input :author, :as => :radio
576
+ #
577
+ # Output:
578
+ #
579
+ # <fieldset>
580
+ # <legend><span>Author</span></legend>
581
+ # <ol>
582
+ # <li>
583
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
584
+ # </li>
585
+ # <li>
586
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
587
+ # </li>
588
+ # </ol>
589
+ # </fieldset>
590
+ #
591
+ # You can customize the options available in the set by passing in a collection (Array) of
592
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
593
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
594
+ # it (VehicleOwner.find(:all) in the example above).
595
+ #
596
+ # Examples:
597
+ #
598
+ # f.input :author, :as => :radio, :collection => @authors
599
+ # f.input :author, :as => :radio, :collection => Author.find(:all)
600
+ # f.input :author, :as => :radio, :collection => [@justin, @kate]
601
+ #
602
+ # You can also customize the text label inside each option tag, by naming the correct method
603
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
604
+ # by passing in the :label_method option. By default the :label_method is whichever element of
605
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
606
+ #
607
+ # Examples:
608
+ #
609
+ # f.input :author, :as => :radio, :label_method => :full_name
610
+ # f.input :author, :as => :radio, :label_method => :display_name
611
+ # f.input :author, :as => :radio, :label_method => :to_s
612
+ # f.input :author, :as => :radio, :label_method => :label
613
+ #
614
+ # Finally, you can set :value_as_class => true if you want that LI wrappers
615
+ # contains a class with the wrapped radio input value.
616
+ #
617
+ def radio_input(method, options)
618
+ collection = find_collection_for_column(method, options)
619
+ html_options = set_options(options).merge(options.delete(:input_html) || {})
620
+
621
+ input_name = generate_association_input_name(method)
622
+ value_as_class = options.delete(:value_as_class)
623
+
624
+ list_item_content = collection.map do |c|
625
+ label = c.is_a?(Array) ? c.first : c
626
+ value = c.is_a?(Array) ? c.last : c
627
+
628
+ li_content = template.content_tag(:label,
629
+ "#{self.radio_button(input_name, value, html_options)} #{label}",
630
+ :for => generate_html_id(input_name, value.to_s.downcase)
631
+ )
632
+
633
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
634
+ template.content_tag(:li, li_content, li_options)
635
+ end
636
+
637
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
638
+ end
639
+ alias :boolean_radio_input :radio_input
640
+
641
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
642
+ # items (li), one for each fragment for the date (year, month, day). Each li contains a label
643
+ # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
644
+ #
645
+ # Some of Rails' options for select_date are supported, but not everything yet.
646
+ def date_input(method, options)
647
+ date_or_datetime_input(method, options.merge(:discard_hour => true))
648
+ end
649
+
650
+
651
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
652
+ # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
653
+ # contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
654
+ # detailed output example.
655
+ #
656
+ # Some of Rails' options for select_date are supported, but not everything yet.
657
+ def datetime_input(method, options)
658
+ date_or_datetime_input(method, options)
659
+ end
660
+
661
+
662
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
663
+ # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
664
+ # (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
665
+ #
666
+ # Some of Rails' options for select_time are supported, but not everything yet.
667
+ def time_input(method, options)
668
+ date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
669
+ end
670
+
671
+
672
+ # <fieldset>
673
+ # <legend>Created At</legend>
674
+ # <ol>
675
+ # <li>
676
+ # <label for="user_created_at_1i">Year</label>
677
+ # <select id="user_created_at_1i" name="user[created_at(1i)]">
678
+ # <option value="2003">2003</option>
679
+ # ...
680
+ # <option value="2013">2013</option>
681
+ # </select>
682
+ # </li>
683
+ # <li>
684
+ # <label for="user_created_at_2i">Month</label>
685
+ # <select id="user_created_at_2i" name="user[created_at(2i)]">
686
+ # <option value="1">January</option>
687
+ # ...
688
+ # <option value="12">December</option>
689
+ # </select>
690
+ # </li>
691
+ # <li>
692
+ # <label for="user_created_at_3i">Day</label>
693
+ # <select id="user_created_at_3i" name="user[created_at(3i)]">
694
+ # <option value="1">1</option>
695
+ # ...
696
+ # <option value="31">31</option>
697
+ # </select>
698
+ # </li>
699
+ # </ol>
700
+ # </fieldset>
701
+ #
702
+ # This is an absolute abomination, but so is the official Rails select_date().
703
+ #
704
+ def date_or_datetime_input(method, options)
705
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
706
+ inputs = options.delete(:order) || I18n.translate(:'date.order') || [:year, :month, :day]
707
+
708
+ time_inputs = [:hour, :minute]
709
+ time_inputs << [:second] if options[:include_seconds]
710
+
711
+ list_items_capture = ""
712
+
713
+ # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
714
+ datetime = @object ? @object.send(method) : nil
715
+ html_options = options.delete(:input_html) || {}
716
+
717
+ (inputs + time_inputs).each do |input|
718
+ html_id = generate_html_id(method, "#{position[input]}i")
719
+ field_name = "#{method}(#{position[input]}i)"
720
+
721
+ list_items_capture << if options["discard_#{input}".intern]
722
+ break if time_inputs.include?(input)
723
+
724
+ hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
725
+ template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
726
+ else
727
+ opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
728
+ item_label_text = I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
729
+
730
+ template.content_tag(:li,
731
+ template.content_tag(:label, item_label_text, :for => html_id) +
732
+ template.send("select_#{input}".intern, datetime, opts, html_options.merge(:id => html_id))
733
+ )
734
+ end
735
+ end
736
+
737
+ field_set_and_list_wrapping_for_method(method, options, list_items_capture)
738
+ end
739
+
740
+ # Outputs a label containing a checkbox and the label text. The label defaults
741
+ # to the column name (method name) and can be altered with the :label option.
742
+ #
743
+ # Different from other inputs, :required options has no effect here and
744
+ # :checked_value and :unchecked_value options are also available.
745
+ #
746
+ def boolean_input(method, options)
747
+ html_options = options.delete(:input_html) || {}
748
+
749
+ content = self.check_box(method, set_options(options).merge(html_options),
750
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
751
+
752
+ # required does not make sense in check box
753
+ input_label(method, content + options.delete(:label), :skip_required => true)
754
+ end
755
+
756
+ # Generates an input for the given method using the type supplied with :as.
757
+ #
758
+ # If the input is included in INPUT_MAPPINGS, it uses input_simple
759
+ # implementation which maps most of the inputs. All others have specific
760
+ # code and then a proper handler should be called (like radio_input) for
761
+ # :radio types.
762
+ #
763
+ def inline_input_for(method, options)
764
+ input_type = options.delete(:as)
765
+
766
+ if INPUT_MAPPINGS.key?(input_type)
767
+ input_simple(input_type, method, options)
768
+ else
769
+ send("#{input_type}_input", method, options)
770
+ end
771
+ end
772
+
773
+ # Generates error messages for the given method. Errors can be shown as list
774
+ # or as sentence. If :none is set, no error is shown.
775
+ #
776
+ def inline_errors_for(method, options) #:nodoc:
777
+ return nil unless @object && [:sentence, :list].include?(@@inline_errors)
778
+
779
+ errors = @object.errors.on(method.to_s).to_a
780
+ send("error_#{@@inline_errors}", errors) unless errors.empty?
781
+ end
782
+
783
+ # Generates hints for the given method using the text supplied in :hint.
784
+ #
785
+ def inline_hints_for(method, options) #:nodoc:
786
+ options[:hint].blank? ? '' : template.content_tag(:p, options[:hint], :class => 'inline-hints')
787
+ end
788
+
789
+ # Creates an error sentence by calling to_sentence on the errors array.
790
+ #
791
+ def error_sentence(errors) #:nodoc:
792
+ template.content_tag(:p, errors.to_sentence, :class => 'inline-errors')
793
+ end
794
+
795
+ # Creates an error li list.
796
+ #
797
+ def error_list(errors) #:nodoc:
798
+ list_elements = []
799
+ errors.each do |error|
800
+ list_elements << template.content_tag(:li, error)
801
+ end
802
+ template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
803
+ end
804
+
805
+ # Generates the label for the input. Accepts the same options as Rails label
806
+ # method and a fourth option that allows the label to be generated as span
807
+ # with class label.
808
+ #
809
+ def input_label(method, text, options={}, as_span=false) #:nodoc:
810
+ text << required_or_optional_string(options.delete(:required)) unless options.delete(:skip_required)
811
+
812
+ if as_span
813
+ options[:class] ||= 'label'
814
+ template.content_tag(:span, text, options)
815
+ else
816
+ self.label(method, text, options)
817
+ end
818
+ end
819
+
820
+ # Generates the required or optional string. If the value set is a proc,
821
+ # it evaluates the proc first.
822
+ #
823
+ def required_or_optional_string(required) #:nodoc:
824
+ string_or_proc = required ? @@required_string : @@optional_string
825
+
826
+ if string_or_proc.is_a? Proc
827
+ string_or_proc.call
828
+ else
829
+ string_or_proc
830
+ end
831
+ end
832
+
833
+ # Generates a fieldset and wraps the content in an ordered list. When working
834
+ # with nested attributes (in Rails 2.3), it allows %i as interpolation option
835
+ # in :name. So you can do:
836
+ #
837
+ # f.inputs :name => 'Task #%i', :for => :tasks
838
+ #
839
+ # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
840
+ # 'Task #3' and so on.
841
+ #
842
+ # If you are using inputs :for, for more than one association in the same
843
+ # form builder, you might want to set the nested_child_index as well. You
844
+ # can do that doing:
845
+ #
846
+ # f.nested_child_index = -1
847
+ #
848
+ def field_set_and_list_wrapping(html_options, contents = '', &block) #:nodoc:
849
+ # Generates the legend text allowing nested_child_index support for interpolation
850
+ legend_text = html_options.delete(:name).to_s
851
+ legend_text %= html_options[:parent_builder].instance_variable_get('@nested_child_index').to_i + 1
852
+
853
+ legend = legend_text.blank? ? "" : template.content_tag(:legend, template.content_tag(:span, legend_text))
854
+ contents = template.capture(&block) if block_given?
855
+
856
+ fieldset = template.content_tag(:fieldset,
857
+ legend + template.content_tag(:ol, contents),
858
+ html_options.except(:builder, :parent_builder)
859
+ )
860
+
861
+ template.concat(fieldset) if block_given?
862
+ fieldset
863
+ end
864
+
865
+ # Also generates a fieldset and an ordered list but with label based in
866
+ # method. This methods is currently used by radio and datetime inputs.
867
+ #
868
+ def field_set_and_list_wrapping_for_method(method, options, contents)
869
+ template.content_tag(:fieldset,
870
+ %{<legend>#{input_label(method, options.delete(:label), options.slice(:required), true)}</legend>} +
871
+ template.content_tag(:ol, contents)
872
+ )
873
+ end
874
+
875
+ # For methods that have a database column, take a best guess as to what the inout method
876
+ # should be. In most cases, it will just return the column type (eg :string), but for special
877
+ # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
878
+ # something different (like :password and :select).
879
+ #
880
+ # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
881
+ # default is a :string, a similar behaviour to Rails' scaffolding.
882
+ #
883
+ def default_input_type(method) #:nodoc:
884
+ return :string if @object.nil?
885
+
886
+ # Find the column object by attribute
887
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
888
+
889
+ # Associations map by default to a select
890
+ return :select if column.nil? && find_reflection(method)
891
+
892
+ if column
893
+ # handle the special cases where the column type doesn't map to an input method
894
+ return :select if column.type == :integer && method.to_s =~ /_id$/
895
+ return :datetime if column.type == :timestamp
896
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
897
+ return :password if column.type == :string && method.to_s =~ /password/
898
+ # otherwise assume the input name will be the same as the column type (eg string_input)
899
+ return column.type
900
+ else
901
+ obj = @object.send(method) if @object.respond_to?(method)
902
+
903
+ return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
904
+ return :password if method.to_s =~ /password/
905
+ return :string
906
+ end
907
+ end
908
+
909
+ # Used by select and radio inputs. The collection can be retrieved by
910
+ # three ways:
911
+ #
912
+ # * Explicitly provided through :collection
913
+ # * Retrivied through an association
914
+ # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
915
+ #
916
+ # If the collection is not a hash or an array of strings, fixnums or arrays,
917
+ # we use label_method and value_method to retreive an array with the
918
+ # appropriate label and value.
919
+ #
920
+ def find_collection_for_column(column, options)
921
+ reflection = find_reflection(column)
922
+
923
+ collection = if options[:collection]
924
+ options.delete(:collection)
925
+ elsif reflection || column.to_s =~ /_id$/
926
+ parent_class = if reflection
927
+ reflection.klass
928
+ else
929
+ ::ActiveSupport::Deprecation.warn("The _id way of doing things is deprecated. Please use the association method (#{column.to_s.sub(/_id$/,'')})", caller[3..-1])
930
+ column.to_s.sub(/_id$/,'').camelize.constantize
931
+ end
932
+
933
+ parent_class.find(:all)
934
+ else
935
+ create_boolean_collection(options)
936
+ end
937
+
938
+ collection = collection.to_a if collection.instance_of?(Hash)
939
+
940
+ # Return if we have an Array of strings, fixnums or arrays
941
+ return collection if collection.instance_of?(Array) &&
942
+ [Array, Fixnum, String, Symbol].include?(collection.first.class)
943
+
944
+ label = options.delete(:label_method) || detect_label_method(collection)
945
+ value = options.delete(:value_method) || :id
946
+
947
+ collection.map { |o| [o.send(label), o.send(value)] }
948
+ end
949
+
950
+ # Detected the label collection method when none is supplied using the
951
+ # values set in @@collection_label_methods.
952
+ #
953
+ def detect_label_method(collection) #:nodoc:
954
+ @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
955
+ end
956
+
957
+ # Returns a hash to be used by radio and select inputs when a boolean field
958
+ # is provided.
959
+ #
960
+ def create_boolean_collection(options)
961
+ options[:true] ||= I18n.t('yes', :default => 'Yes', :scope => [:formtastic])
962
+ options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
963
+ options[:value_as_class] = true unless options.key?(:value_as_class)
964
+
965
+ { options.delete(:true) => true, options.delete(:false) => false }
966
+ end
967
+
968
+ # Used by association inputs (select, radio) to generate the name that should
969
+ # be used for the input
970
+ #
971
+ # belongs_to :author; f.input :author; will generate 'author_id'
972
+ # has_many :authors; f.input :authors; will generate 'author_ids'
973
+ # has_and_belongs_to_many will act like has_many
974
+ #
975
+ def generate_association_input_name(method)
976
+ if reflection = find_reflection(method)
977
+ method = "#{method.to_s.singularize}_id"
978
+ method = method.pluralize if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
979
+ end
980
+ method
981
+ end
982
+
983
+ # If an association method is passed in (f.input :author) try to find the
984
+ # reflection object.
985
+ #
986
+ def find_reflection(method)
987
+ object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
988
+ end
989
+
990
+ # Generates default_string_options by retrieving column information from
991
+ # the database.
992
+ #
993
+ def default_string_options(method) #:nodoc:
994
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
995
+
996
+ if column.nil? || column.limit.nil?
997
+ { :size => @@default_text_field_size }
998
+ else
999
+ { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
1000
+ end
1001
+ end
1002
+
1003
+ # Generate the html id for the li tag.
1004
+ # It takes into account options[:index] and @auto_index to generate li
1005
+ # elements with appropriate index scope. It also sanitizes the object
1006
+ # and method names.
1007
+ #
1008
+ def generate_html_id(method_name, value='input')
1009
+ if options.has_key?(:index)
1010
+ index = "_#{options[:index]}"
1011
+ elsif defined?(@auto_index)
1012
+ index = "_#{@auto_index}"
1013
+ else
1014
+ index = ""
1015
+ end
1016
+ sanitized_method_name = method_name.to_s.sub(/\?$/,"")
1017
+
1018
+ "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1019
+ end
1020
+
1021
+ def sanitized_object_name
1022
+ @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1023
+ end
1024
+
1025
+ end
1026
+
1027
+
1028
+ # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
1029
+ #
1030
+ # * semantic_form_for(@post)
1031
+ # * semantic_fields_for(@post)
1032
+ # * semantic_form_remote_for(@post)
1033
+ # * semantic_remote_form_for(@post)
1034
+ #
1035
+ # Each of which are the equivalent of:
1036
+ #
1037
+ # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1038
+ # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
1039
+ # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
1040
+ # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1041
+ #
1042
+ # Example Usage:
1043
+ #
1044
+ # <% semantic_form_for @post do |f| %>
1045
+ # <%= f.input :title %>
1046
+ # <%= f.input :body %>
1047
+ # <% end %>
1048
+ #
1049
+ # The above examples use a resource-oriented style of form_for() helper where only the @post
1050
+ # object is given as an argument, but the generic style is also supported if you really want it,
1051
+ # as is forms with inline objects (Post.new) rather than objects with instance variables (@post):
1052
+ #
1053
+ # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
1054
+ # ...
1055
+ # <% end %>
1056
+ #
1057
+ # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
1058
+ # ...
1059
+ # <% end %>
1060
+ #
1061
+ # The shorter, resource-oriented style is most definitely preferred, and has recieved the most
1062
+ # testing to date.
1063
+ #
1064
+ # Please note: Although it's possible to call Rails' built-in form_for() helper without an
1065
+ # object, all semantic forms *must* have an object (either Post.new or @post), as Formtastic
1066
+ # has too many dependencies on an ActiveRecord object being present.
1067
+ #
1068
+ module SemanticFormHelper
1069
+ @@builder = Formtastic::SemanticFormBuilder
1070
+
1071
+ # cattr_accessor :builder
1072
+ def self.builder=(val)
1073
+ @@builder = val
1074
+ end
1075
+
1076
+ [:form_for, :fields_for, :form_remote_for, :remote_form_for].each do |meth|
1077
+ src = <<-END_SRC
1078
+ def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1079
+ options = args.extract_options!
1080
+ options[:builder] = @@builder
1081
+ options[:html] ||= {}
1082
+
1083
+ class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1084
+ class_names << "formtastic"
1085
+ class_names << case record_or_name_or_array
1086
+ when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1087
+ when Array then record_or_name_or_array.last.class.to_s.underscore # [@post, @comment] # => "comment"
1088
+ else record_or_name_or_array.class.to_s.underscore # @post => "post"
1089
+ end
1090
+ options[:html][:class] = class_names.join(" ")
1091
+
1092
+ #{meth}(record_or_name_or_array, *(args << options), &proc)
1093
+ end
1094
+ END_SRC
1095
+ module_eval src, __FILE__, __LINE__
1096
+ end
1097
+ end
1098
+ end