lperichon-formtastic 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ /* -------------------------------------------------------------------------------------------------
2
+
3
+ Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs.
4
+ This will allow you to update formtastic.css with new releases without clobbering your own changes.
5
+
6
+ For example, to make the inline hint paragraphs a little darker in color than the standard #666:
7
+
8
+ form.formtastic fieldset ol li p.inline-hints { color:#333; }
9
+
10
+ --------------------------------------------------------------------------------------------------*/
@@ -0,0 +1,16 @@
1
+ class FormtasticStylesheetsGenerator < Rails::Generator::Base
2
+
3
+ def initialize(*runtime_args)
4
+ puts %q{
5
+ ===================================================
6
+ Please run `./script/generate formtastic` instead.
7
+ ===================================================
8
+ }
9
+ end
10
+
11
+ def manifest
12
+ record do |m|
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,1408 @@
1
+ # coding: utf-8
2
+
3
+ module Formtastic #:nodoc:
4
+
5
+ class SemanticFormBuilder < ActionView::Helpers::FormBuilder
6
+
7
+ @@default_text_field_size = 50
8
+ @@all_fields_required_by_default = true
9
+ @@include_blank_for_select_by_default = true
10
+ @@required_string = proc { %{<abbr title="#{I18n.t 'formtastic.required', :default => 'required'}">*</abbr>} }
11
+ @@optional_string = ''
12
+ @@inline_errors = :sentence
13
+ @@label_str_method = :humanize
14
+ @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
15
+ @@inline_order = [ :input, :hints, :errors ]
16
+ @@file_methods = [ :file?, :public_filename ]
17
+ @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
18
+ @@i18n_lookups_by_default = false
19
+ @@default_commit_button_accesskey = nil
20
+
21
+ cattr_accessor :default_text_field_size, :all_fields_required_by_default, :include_blank_for_select_by_default,
22
+ :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
23
+ :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
24
+
25
+ I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
26
+ '{{model}}.{{attribute}}',
27
+ '{{attribute}}']
28
+
29
+ # Keeps simple mappings in a hash
30
+ INPUT_MAPPINGS = {
31
+ :string => :text_field,
32
+ :password => :password_field,
33
+ :numeric => :text_field,
34
+ :text => :text_area,
35
+ :file => :file_field
36
+ }
37
+ STRING_MAPPINGS = [ :string, :password, :numeric ]
38
+
39
+ attr_accessor :template
40
+
41
+ # Returns a suitable form input for the given +method+, using the database column information
42
+ # and other factors (like the method name) to figure out what you probably want.
43
+ #
44
+ # Options:
45
+ #
46
+ # * :as - override the input type (eg force a :string to render as a :password field)
47
+ # * :label - use something other than the method name as the label text, when false no label is printed
48
+ # * :required - specify if the column is required (true) or not (false)
49
+ # * :hint - provide some text to hint or help the user provide the correct information for a field
50
+ # * :input_html - provide options that will be passed down to the generated input
51
+ # * :wrapper_html - provide options that will be passed down to the li wrapper
52
+ #
53
+ # Input Types:
54
+ #
55
+ # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
56
+ # but there are a few special cases and some simplification (:integer, :float and :decimal
57
+ # columns all map to a single numeric_input, for example).
58
+ #
59
+ # * :select (a select menu for associations) - default to association names
60
+ # * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
61
+ # * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
62
+ # * :time_zone (a select menu with time zones)
63
+ # * :password (a password input) - default for :string column types with 'password' in the method name
64
+ # * :text (a textarea) - default for :text column types
65
+ # * :date (a date select) - default for :date column types
66
+ # * :datetime (a date and time select) - default for :datetime and :timestamp column types
67
+ # * :time (a time select) - default for :time column types
68
+ # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
69
+ # * :string (a text field) - default for :string column types
70
+ # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
71
+ # * :country (a select menu of country names) - requires a country_select plugin to be installed
72
+ # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
73
+ #
74
+ # Example:
75
+ #
76
+ # <% semantic_form_for @employee do |form| %>
77
+ # <% form.inputs do -%>
78
+ # <%= form.input :name, :label => "Full Name"%>
79
+ # <%= form.input :manager_id, :as => :radio %>
80
+ # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
81
+ # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
82
+ # <% end %>
83
+ # <% end %>
84
+ #
85
+ def input(method, options = {})
86
+ options[:required] = method_required?(method) unless options.key?(:required)
87
+ options[:as] ||= default_input_type(method)
88
+
89
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ]
90
+ html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
91
+
92
+ wrapper_html = options.delete(:wrapper_html) || {}
93
+ wrapper_html[:id] ||= generate_html_id(method)
94
+ wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
95
+
96
+ if options[:input_html] && options[:input_html][:id]
97
+ options[:label_html] ||= {}
98
+ options[:label_html][:for] ||= options[:input_html][:id]
99
+ end
100
+
101
+ input_parts = @@inline_order.dup
102
+ input_parts.delete(:errors) if options[:as] == :hidden
103
+
104
+ list_item_content = input_parts.map do |type|
105
+ send(:"inline_#{type}_for", method, options)
106
+ end.compact.join("\n")
107
+
108
+ return template.content_tag(:li, list_item_content, wrapper_html)
109
+ end
110
+
111
+ # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
112
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
113
+ # or with a list of fields. These two examples are functionally equivalent:
114
+ #
115
+ # # With a block:
116
+ # <% semantic_form_for @post do |form| %>
117
+ # <% form.inputs do %>
118
+ # <%= form.input :title %>
119
+ # <%= form.input :body %>
120
+ # <% end %>
121
+ # <% end %>
122
+ #
123
+ # # With a list of fields:
124
+ # <% semantic_form_for @post do |form| %>
125
+ # <%= form.inputs :title, :body %>
126
+ # <% end %>
127
+ #
128
+ # # Output:
129
+ # <form ...>
130
+ # <fieldset class="inputs">
131
+ # <ol>
132
+ # <li class="string">...</li>
133
+ # <li class="text">...</li>
134
+ # </ol>
135
+ # </fieldset>
136
+ # </form>
137
+ #
138
+ # === Quick Forms
139
+ #
140
+ # When called without a block or a field list, an input is rendered for each column in the
141
+ # model's database table, just like Rails' scaffolding. You'll obviously want more control
142
+ # than this in a production application, but it's a great way to get started, then come back
143
+ # later to customise the form with a field list or a block of inputs. Example:
144
+ #
145
+ # <% semantic_form_for @post do |form| %>
146
+ # <%= form.inputs %>
147
+ # <% end %>
148
+ #
149
+ # === Options
150
+ #
151
+ # All options (with the exception of :name) are passed down to the fieldset as HTML
152
+ # attributes (id, class, style, etc). If provided, the :name option is passed into a
153
+ # legend tag inside the fieldset (otherwise a legend is not generated).
154
+ #
155
+ # # With a block:
156
+ # <% semantic_form_for @post do |form| %>
157
+ # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
158
+ # ...
159
+ # <% end %>
160
+ # <% end %>
161
+ #
162
+ # # With a list (the options must come after the field list):
163
+ # <% semantic_form_for @post do |form| %>
164
+ # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
165
+ # <% end %>
166
+ #
167
+ # === It's basically a fieldset!
168
+ #
169
+ # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
170
+ # use inputs:
171
+ #
172
+ # <% semantic_form_for @post do |f| %>
173
+ # <% f.inputs do %>
174
+ # <%= f.input :title %>
175
+ # <%= f.input :body %>
176
+ # <% end %>
177
+ # <% f.inputs :name => "Advanced", :id => "advanced" do %>
178
+ # <%= f.input :created_at %>
179
+ # <%= f.input :user_id, :label => "Author" %>
180
+ # <% end %>
181
+ # <% end %>
182
+ #
183
+ # # Output:
184
+ # <form ...>
185
+ # <fieldset class="inputs">
186
+ # <ol>
187
+ # <li class="string">...</li>
188
+ # <li class="text">...</li>
189
+ # </ol>
190
+ # </fieldset>
191
+ # <fieldset class="inputs" id="advanced">
192
+ # <legend><span>Advanced</span></legend>
193
+ # <ol>
194
+ # <li class="datetime">...</li>
195
+ # <li class="select">...</li>
196
+ # </ol>
197
+ # </fieldset>
198
+ # </form>
199
+ #
200
+ # === Nested attributes
201
+ #
202
+ # As in Rails, you can use semantic_fields_for to nest attributes:
203
+ #
204
+ # <% semantic_form_for @post do |form| %>
205
+ # <%= form.inputs :title, :body %>
206
+ #
207
+ # <% form.semantic_fields_for :author, @bob do |author_form| %>
208
+ # <% author_form.inputs do %>
209
+ # <%= author_form.input :first_name, :required => false %>
210
+ # <%= author_form.input :last_name %>
211
+ # <% end %>
212
+ # <% end %>
213
+ # <% end %>
214
+ #
215
+ # But this does not look formtastic! This is equivalent:
216
+ #
217
+ # <% semantic_form_for @post do |form| %>
218
+ # <%= form.inputs :title, :body %>
219
+ # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
220
+ # <%= author_form.input :first_name, :required => false %>
221
+ # <%= author_form.input :last_name %>
222
+ # <% end %>
223
+ # <% end %>
224
+ #
225
+ # And if you don't need to give options to your input call, you could do it
226
+ # in just one line:
227
+ #
228
+ # <% semantic_form_for @post do |form| %>
229
+ # <%= form.inputs :title, :body %>
230
+ # <%= form.inputs :first_name, :last_name, :for => @bob %>
231
+ # <% end %>
232
+ #
233
+ # Just remember that calling inputs generates a new fieldset to wrap your
234
+ # inputs. If you have two separate models, but, semantically, on the page
235
+ # they are part of the same fieldset, you should use semantic_fields_for
236
+ # instead (just as you would do with Rails' form builder).
237
+ #
238
+ def inputs(*args, &block)
239
+ html_options = args.extract_options!
240
+ html_options[:class] ||= "inputs"
241
+
242
+ if html_options[:for]
243
+ inputs_for_nested_attributes(args, html_options, &block)
244
+ elsif block_given?
245
+ field_set_and_list_wrapping(html_options, &block)
246
+ else
247
+ if @object && args.empty?
248
+ args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
249
+ args += @object.class.content_columns.map(&:name)
250
+ args -= %w[created_at updated_at created_on updated_on lock_version version]
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" class="{create|update|submit}" />
287
+ # <%= form.commit_button :label => "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
288
+ #
289
+ # And you can pass html atributes down to the input, with or without the button text:
290
+ #
291
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
292
+ # <%= form.commit_button :class => "pretty" %> => <input name="commit" type="submit" value="Save Post" class="pretty {create|update|submit}" />
293
+ #
294
+ def commit_button(*args)
295
+ options = args.extract_options!
296
+ text = options.delete(:label) || args.shift
297
+
298
+ if @object
299
+ key = @object.new_record? ? :create : :update
300
+ object_name = @object.class.human_name
301
+
302
+ if key == :update
303
+ # Note: Fallback on :save-key (deprecated), :update makes more sense in the REST-world.
304
+ fallback_text = ::I18n.t(:save, :model => object_name, :default => "Save {{model}}", :scope => [:formtastic])
305
+ ::ActiveSupport::Deprecation.warn "Formtastic I18n: Key 'formtastic.save' is now deprecated in favor 'formtastic.update'."
306
+ end
307
+ else
308
+ key = :submit
309
+ object_name = @object_name.to_s.send(@@label_str_method)
310
+ end
311
+ fallback_text ||= "#{key.to_s.humanize} {{model}}"
312
+
313
+ text = (self.localized_string(key, text, :action, :model => object_name) ||
314
+ ::I18n.t(key, :model => object_name, :default => fallback_text, :scope => [:formtastic])) unless text.is_a?(::String)
315
+
316
+ button_html = options.delete(:button_html) || {}
317
+ button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
318
+ element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
319
+ accesskey = (options.delete(:accesskey) || @@default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
320
+ button_html = button_html.merge(:accesskey => accesskey) if accesskey
321
+ template.content_tag(:li, self.submit(text, button_html), :class => element_class)
322
+ end
323
+
324
+ # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
325
+ # for nesting forms:
326
+ #
327
+ # # Example:
328
+ # <% semantic_form_for @post do |post| %>
329
+ # <% post.semantic_fields_for :author do |author| %>
330
+ # <% author.inputs :name %>
331
+ # <% end %>
332
+ # <% end %>
333
+ #
334
+ # # Output:
335
+ # <form ...>
336
+ # <fieldset class="inputs">
337
+ # <ol>
338
+ # <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
339
+ # </ol>
340
+ # </fieldset>
341
+ # </form>
342
+ #
343
+ def semantic_fields_for(record_or_name_or_array, *args, &block)
344
+ opts = args.extract_options!
345
+ opts.merge!(:builder => Formtastic::SemanticFormHelper.builder)
346
+ args.push(opts)
347
+ fields_for(record_or_name_or_array, *args, &block)
348
+ end
349
+
350
+ # Generates the label for the input. It also accepts the same arguments as
351
+ # Rails label method. It has three options that are not supported by Rails
352
+ # label method:
353
+ #
354
+ # * :required - Appends an abbr tag if :required is true
355
+ # * :label - An alternative form to give the label content. Whenever label
356
+ # is false, a blank string is returned.
357
+ # * :as_span - When true returns a span tag with class label instead of a label element
358
+ # * :input_name - Gives the input to match for. This is needed when you want to
359
+ # to call f.label :authors but it should match :author_ids.
360
+ #
361
+ # == Examples
362
+ #
363
+ # f.label :title # like in rails, except that it searches the label on I18n API too
364
+ #
365
+ # f.label :title, "Your post title"
366
+ # f.label :title, :label => "Your post title" # Added for formtastic API
367
+ #
368
+ # f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
369
+ #
370
+ def label(method, options_or_text=nil, options=nil)
371
+ if options_or_text.is_a?(Hash)
372
+ return "" if options_or_text[:label] == false
373
+ options = options_or_text
374
+ text = options.delete(:label)
375
+ else
376
+ text = options_or_text
377
+ options ||= {}
378
+ end
379
+ text = localized_string(method, text, :label) || humanized_attribute_name(method)
380
+ text += required_or_optional_string(options.delete(:required))
381
+
382
+ # special case for boolean (checkbox) labels, which have a nested input
383
+ text = (options.delete(:label_prefix_for_nested_input) || "") + text
384
+
385
+ input_name = options.delete(:input_name) || method
386
+ if options.delete(:as_span)
387
+ options[:class] ||= 'label'
388
+ template.content_tag(:span, text, options)
389
+ else
390
+ super(input_name, text, options)
391
+ end
392
+ end
393
+
394
+ # Generates error messages for the given method. Errors can be shown as list,
395
+ # as sentence or just the first error can be displayed. If :none is set, no error is shown.
396
+ #
397
+ # This method is also aliased as errors_on, so you can call on your custom
398
+ # inputs as well:
399
+ #
400
+ # semantic_form_for :post do |f|
401
+ # f.text_field(:body)
402
+ # f.errors_on(:body)
403
+ # end
404
+ #
405
+ def inline_errors_for(method, options=nil) #:nodoc:
406
+ return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list, :first].include?(@@inline_errors)
407
+
408
+ errors = @object.errors[method.to_sym]
409
+ send("error_#{@@inline_errors}", Array(errors)) unless errors.blank?
410
+ end
411
+ alias :errors_on :inline_errors_for
412
+
413
+ protected
414
+
415
+ # Prepare options to be sent to label
416
+ #
417
+ def options_for_label(options)
418
+ options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
419
+ end
420
+
421
+ # Deals with :for option when it's supplied to inputs methods. Additional
422
+ # options to be passed down to :for should be supplied using :for_options
423
+ # key.
424
+ #
425
+ # It should raise an error if a block with arity zero is given.
426
+ #
427
+ def inputs_for_nested_attributes(args, options, &block)
428
+ args << options.merge!(:parent => { :builder => self, :for => options[:for] })
429
+
430
+ fields_for_block = if block_given?
431
+ raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
432
+ 'but the block does not accept any argument.' if block.arity <= 0
433
+
434
+ proc { |f| f.inputs(*args){ block.call(f) } }
435
+ else
436
+ proc { |f| f.inputs(*args) }
437
+ end
438
+
439
+ fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
440
+ semantic_fields_for(*fields_for_args, &fields_for_block)
441
+ end
442
+
443
+ # Remove any Formtastic-specific options before passing the down options.
444
+ #
445
+ def set_options(options)
446
+ options.except(:value_method, :label_method, :collection, :required, :label,
447
+ :as, :hint, :input_html, :label_html, :value_as_class)
448
+ end
449
+
450
+ # Determins if the attribute (eg :title) should be considered required or not.
451
+ #
452
+ # * if the :required option was provided in the options hash, the true/false value will be
453
+ # returned immediately, allowing the view to override any guesswork that follows:
454
+ #
455
+ # * if the :required option isn't provided in the options hash, and the ValidationReflection
456
+ # plugin is installed (http://github.com/redinger/validation_reflection), true is returned
457
+ # if the validates_presence_of macro has been used in the class for this attribute, or false
458
+ # otherwise.
459
+ #
460
+ # * if the :required option isn't provided, and the plugin isn't available, the value of the
461
+ # configuration option @@all_fields_required_by_default is used.
462
+ #
463
+ def method_required?(attribute) #:nodoc:
464
+ if @object && @object.class.respond_to?(:reflect_on_validations_for)
465
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
466
+
467
+ @object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
468
+ validation.macro == :validates_presence_of &&
469
+ validation.name == attribute_sym &&
470
+ (validation.options.present? ? options_require_validation?(validation.options) : true)
471
+ end
472
+ else
473
+ @@all_fields_required_by_default
474
+ end
475
+ end
476
+
477
+ # Determines whether the given options evaluate to true
478
+ def options_require_validation?(options) #nodoc
479
+ if_condition = !options[:if].nil?
480
+ condition = if_condition ? options[:if] : options[:unless]
481
+
482
+ # ignores any other option (for example :on)
483
+ return true if condition.blank?
484
+
485
+ condition = if condition.respond_to?(:call)
486
+ condition.call(@object)
487
+ elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
488
+ @object.send(condition)
489
+ else
490
+ condition
491
+ end
492
+
493
+ if_condition ? !!condition : !condition
494
+ end
495
+
496
+ # A method that deals with most of inputs (:string, :password, :file,
497
+ # :textarea and :numeric). :select, :radio, :boolean and :datetime inputs
498
+ # are not handled by this method, since they need more detailed approach.
499
+ #
500
+ # If input_html is given as option, it's passed down to the input.
501
+ #
502
+ def input_simple(type, method, options)
503
+ html_options = options.delete(:input_html) || {}
504
+ html_options = default_string_options(method, type).merge(html_options) if STRING_MAPPINGS.include?(type)
505
+
506
+ self.label(method, options_for_label(options)) +
507
+ self.send(INPUT_MAPPINGS[type], method, html_options)
508
+ end
509
+
510
+ # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
511
+ # Additionals options can be given and will be sent straight to hidden input
512
+ # element.
513
+ #
514
+ def hidden_input(method, options)
515
+ self.hidden_field(method, set_options(options))
516
+ end
517
+
518
+ # Outputs a label and a select box containing options from the parent
519
+ # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
520
+ # is has_many or has_and_belongs_to_many the select box will be set as multi-select
521
+ # and size = 5
522
+ #
523
+ # Example (belongs_to):
524
+ #
525
+ # f.input :author
526
+ #
527
+ # <label for="book_author_id">Author</label>
528
+ # <select id="book_author_id" name="book[author_id]">
529
+ # <option value=""></option>
530
+ # <option value="1">Justin French</option>
531
+ # <option value="2">Jane Doe</option>
532
+ # </select>
533
+ #
534
+ # Example (has_many):
535
+ #
536
+ # f.input :chapters
537
+ #
538
+ # <label for="book_chapter_ids">Chapters</label>
539
+ # <select id="book_chapter_ids" name="book[chapter_ids]">
540
+ # <option value=""></option>
541
+ # <option value="1">Chapter 1</option>
542
+ # <option value="2">Chapter 2</option>
543
+ # </select>
544
+ #
545
+ # Example (has_and_belongs_to_many):
546
+ #
547
+ # f.input :authors
548
+ #
549
+ # <label for="book_author_ids">Authors</label>
550
+ # <select id="book_author_ids" name="book[author_ids]">
551
+ # <option value=""></option>
552
+ # <option value="1">Justin French</option>
553
+ # <option value="2">Jane Doe</option>
554
+ # </select>
555
+ #
556
+ #
557
+ # You can customize the options available in the select by passing in a collection (an Array or
558
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
559
+ # parent's class name from the method name and simply calling find(:all) on it
560
+ # (VehicleOwner.find(:all) in the example above).
561
+ #
562
+ # Examples:
563
+ #
564
+ # f.input :author, :collection => @authors
565
+ # f.input :author, :collection => Author.find(:all)
566
+ # f.input :author, :collection => [@justin, @kate]
567
+ # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
568
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
569
+ #
570
+ # The :label_method option allows you to customize the text label inside each option tag two ways:
571
+ #
572
+ # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
573
+ # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
574
+ #
575
+ # Examples:
576
+ #
577
+ # f.input :author, :label_method => :full_name
578
+ # f.input :author, :label_method => :login
579
+ # f.input :author, :label_method => :full_name_with_post_count
580
+ # f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
581
+ #
582
+ # The :value_method option provides the same customization of the value attribute of each option tag.
583
+ #
584
+ # Examples:
585
+ #
586
+ # f.input :author, :value_method => :full_name
587
+ # f.input :author, :value_method => :login
588
+ # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
589
+ #
590
+ # You can pre-select a specific option value by passing in the :select option.
591
+ #
592
+ # Examples:
593
+ #
594
+ # f.input :author, :selected => current_user.id
595
+ # f.input :author, :value_method => :login, :selected => current_user.login
596
+ #
597
+ # You can pass html_options to the select tag using :input_html => {}
598
+ #
599
+ # Examples:
600
+ #
601
+ # f.input :authors, :input_html => {:size => 20, :multiple => true}
602
+ #
603
+ # By default, all select inputs will have a blank option at the top of the list. You can add
604
+ # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
605
+ #
606
+ def select_input(method, options)
607
+ collection = find_collection_for_column(method, options)
608
+ html_options = options.delete(:input_html) || {}
609
+ options = set_include_blank(options)
610
+
611
+ reflection = find_reflection(method)
612
+ if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
613
+ options[:include_blank] = false
614
+ html_options[:multiple] ||= true
615
+ html_options[:size] ||= 5
616
+ end
617
+
618
+ input_name = generate_association_input_name(method)
619
+ self.label(method, options_for_label(options).merge(:input_name => input_name)) +
620
+ self.select(input_name, collection, set_options(options), html_options)
621
+ end
622
+ alias :boolean_select_input :select_input
623
+
624
+ # Outputs a timezone select input as Rails' time_zone_select helper. You
625
+ # can give priority zones as option.
626
+ #
627
+ # Examples:
628
+ #
629
+ # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
630
+ #
631
+ def time_zone_input(method, options)
632
+ html_options = options.delete(:input_html) || {}
633
+
634
+ self.label(method, options_for_label(options)) +
635
+ self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
636
+ end
637
+
638
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
639
+ # items, one for each possible choice in the belongs_to association. Each li contains a
640
+ # label and a radio input.
641
+ #
642
+ # Example:
643
+ #
644
+ # f.input :author, :as => :radio
645
+ #
646
+ # Output:
647
+ #
648
+ # <fieldset>
649
+ # <legend><span>Author</span></legend>
650
+ # <ol>
651
+ # <li>
652
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
653
+ # </li>
654
+ # <li>
655
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
656
+ # </li>
657
+ # </ol>
658
+ # </fieldset>
659
+ #
660
+ # You can customize the choices available in the radio button set by passing in a collection (an Array or
661
+ # Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
662
+ # (Author.find(:all) in the example above).
663
+ #
664
+ # Examples:
665
+ #
666
+ # f.input :author, :as => :radio, :collection => @authors
667
+ # f.input :author, :as => :radio, :collection => Author.find(:all)
668
+ # f.input :author, :as => :radio, :collection => [@justin, @kate]
669
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
670
+ #
671
+ # The :label_method option allows you to customize the label for each radio button two ways:
672
+ #
673
+ # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
674
+ # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
675
+ #
676
+ # Examples:
677
+ #
678
+ # f.input :author, :as => :radio, :label_method => :full_name
679
+ # f.input :author, :as => :radio, :label_method => :login
680
+ # f.input :author, :as => :radio, :label_method => :full_name_with_post_count
681
+ # f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
682
+ #
683
+ # The :value_method option provides the same customization of the value attribute of each option tag.
684
+ #
685
+ # Examples:
686
+ #
687
+ # f.input :author, :as => :radio, :value_method => :full_name
688
+ # f.input :author, :as => :radio, :value_method => :login
689
+ # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
690
+ #
691
+ # You can force a particular radio button in the collection to be checked with the :selected option. Example:
692
+ #
693
+ # f.input :subscribe_to_newsletter, :as => :radio, :selected => true
694
+ # f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
695
+ #
696
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
697
+ # button / label combination to contain a class with the value of the radio button (useful for
698
+ # applying specific CSS or Javascript to a particular radio button).
699
+ def radio_input(method, options)
700
+ collection = find_collection_for_column(method, options)
701
+ html_options = set_options(options).merge(options.delete(:input_html) || {})
702
+
703
+ input_name = generate_association_input_name(method)
704
+ value_as_class = options.delete(:value_as_class)
705
+
706
+ list_item_content = collection.map do |c|
707
+ label = c.is_a?(Array) ? c.first : c
708
+ value = c.is_a?(Array) ? c.last : c
709
+ html_options[:checked] = options.delete(:selected) unless options[:selected].blank?
710
+
711
+ li_content = template.content_tag(:label,
712
+ "#{self.radio_button(input_name, value, html_options)} #{label}",
713
+ :for => generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
714
+ )
715
+
716
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
717
+ template.content_tag(:li, li_content, li_options)
718
+ end
719
+
720
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
721
+ end
722
+ alias :boolean_radio_input :radio_input
723
+
724
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
725
+ # items (li), one for each fragment for the date (year, month, day). Each li contains a label
726
+ # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
727
+ #
728
+ # Some of Rails' options for select_date are supported, but not everything yet.
729
+ def date_input(method, options)
730
+ options = set_include_blank(options)
731
+ date_or_datetime_input(method, options.merge(:discard_hour => true))
732
+ end
733
+
734
+
735
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
736
+ # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
737
+ # contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
738
+ # detailed output example.
739
+ #
740
+ # Some of Rails' options for select_date are supported, but not everything yet.
741
+ def datetime_input(method, options)
742
+ options = set_include_blank(options)
743
+ date_or_datetime_input(method, options)
744
+ end
745
+
746
+
747
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
748
+ # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
749
+ # (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
750
+ #
751
+ # Some of Rails' options for select_time are supported, but not everything yet.
752
+ def time_input(method, options)
753
+ options = set_include_blank(options)
754
+ date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
755
+ end
756
+
757
+
758
+ # <fieldset>
759
+ # <legend>Created At</legend>
760
+ # <ol>
761
+ # <li>
762
+ # <label for="user_created_at_1i">Year</label>
763
+ # <select id="user_created_at_1i" name="user[created_at(1i)]">
764
+ # <option value="2003">2003</option>
765
+ # ...
766
+ # <option value="2013">2013</option>
767
+ # </select>
768
+ # </li>
769
+ # <li>
770
+ # <label for="user_created_at_2i">Month</label>
771
+ # <select id="user_created_at_2i" name="user[created_at(2i)]">
772
+ # <option value="1">January</option>
773
+ # ...
774
+ # <option value="12">December</option>
775
+ # </select>
776
+ # </li>
777
+ # <li>
778
+ # <label for="user_created_at_3i">Day</label>
779
+ # <select id="user_created_at_3i" name="user[created_at(3i)]">
780
+ # <option value="1">1</option>
781
+ # ...
782
+ # <option value="31">31</option>
783
+ # </select>
784
+ # </li>
785
+ # </ol>
786
+ # </fieldset>
787
+ #
788
+ # This is an absolute abomination, but so is the official Rails select_date().
789
+ #
790
+ def date_or_datetime_input(method, options)
791
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
792
+ i18n_date_order = I18n.translate(:'date.order').is_a?(Array) ? I18n.translate(:'date.order') : nil
793
+ inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
794
+
795
+ time_inputs = [:hour, :minute]
796
+ time_inputs << [:second] if options[:include_seconds]
797
+
798
+ list_items_capture = ""
799
+ hidden_fields_capture = ""
800
+
801
+ # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
802
+ datetime = @object ? @object.send(method) : nil
803
+ html_options = options.delete(:input_html) || {}
804
+
805
+ (inputs + time_inputs).each do |input|
806
+ html_id = generate_html_id(method, "#{position[input]}i")
807
+ field_name = "#{method}(#{position[input]}i)"
808
+ if options["discard_#{input}".intern]
809
+ break if time_inputs.include?(input)
810
+
811
+ hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
812
+ hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
813
+ else
814
+ opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
815
+ item_label_text = I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
816
+
817
+ list_items_capture << template.content_tag(:li,
818
+ template.content_tag(:label, item_label_text, :for => html_id) +
819
+ template.send("select_#{input}".intern, datetime, opts, html_options.merge(:id => html_id))
820
+ )
821
+ end
822
+ end
823
+
824
+ hidden_fields_capture + field_set_and_list_wrapping_for_method(method, options, list_items_capture)
825
+ end
826
+
827
+
828
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
829
+ # items, one for each possible choice in the belongs_to association. Each li contains a
830
+ # label and a check_box input.
831
+ #
832
+ # This is an alternative for has many and has and belongs to many associations.
833
+ #
834
+ # Example:
835
+ #
836
+ # f.input :author, :as => :check_boxes
837
+ #
838
+ # Output:
839
+ #
840
+ # <fieldset>
841
+ # <legend><span>Authors</span></legend>
842
+ # <ol>
843
+ # <li>
844
+ # <input type="hidden" name="book[author_id][1]" value="">
845
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
846
+ # </li>
847
+ # <li>
848
+ # <input type="hidden" name="book[author_id][2]" value="">
849
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
850
+ # </li>
851
+ # </ol>
852
+ # </fieldset>
853
+ #
854
+ # Notice that the value of the checkbox is the same as the id and the hidden
855
+ # field has empty value. You can override the hidden field value using the
856
+ # unchecked_value option.
857
+ #
858
+ # You can customize the options available in the set by passing in a collection (Array) of
859
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
860
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
861
+ # it (Author.find(:all) in the example above).
862
+ #
863
+ # Examples:
864
+ #
865
+ # f.input :author, :as => :check_boxes, :collection => @authors
866
+ # f.input :author, :as => :check_boxes, :collection => Author.find(:all)
867
+ # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
868
+ #
869
+ # The :label_method option allows you to customize the label for each checkbox two ways:
870
+ #
871
+ # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
872
+ # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
873
+ #
874
+ # Examples:
875
+ #
876
+ # f.input :author, :as => :check_boxes, :label_method => :full_name
877
+ # f.input :author, :as => :check_boxes, :label_method => :login
878
+ # f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
879
+ # f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
880
+ #
881
+ # The :value_method option provides the same customization of the value attribute of each checkbox input tag.
882
+ #
883
+ # Examples:
884
+ #
885
+ # f.input :author, :as => :check_boxes, :value_method => :full_name
886
+ # f.input :author, :as => :check_boxes, :value_method => :login
887
+ # f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
888
+ #
889
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
890
+ # combination to contain a class with the value of the radio button (useful for applying specific
891
+ # CSS or Javascript to a particular checkbox).
892
+ def check_boxes_input(method, options)
893
+ collection = find_collection_for_column(method, options)
894
+ html_options = options.delete(:input_html) || {}
895
+
896
+ input_name = generate_association_input_name(method)
897
+ value_as_class = options.delete(:value_as_class)
898
+ unchecked_value = options.delete(:unchecked_value) || ''
899
+ html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
900
+
901
+ list_item_content = collection.map do |c|
902
+ label = c.is_a?(Array) ? c.first : c
903
+ value = c.is_a?(Array) ? c.last : c
904
+
905
+ html_options.merge!(:id => generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase))
906
+
907
+ li_content = template.content_tag(:label,
908
+ "#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
909
+ :for => html_options[:id]
910
+ )
911
+
912
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
913
+ template.content_tag(:li, li_content, li_options)
914
+ end
915
+
916
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
917
+ end
918
+
919
+
920
+ # Outputs a country select input, wrapping around a regular country_select helper.
921
+ # Rails doesn't come with a country_select helper by default any more, so you'll need to install
922
+ # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
923
+ # same way.
924
+ #
925
+ # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
926
+ #
927
+ # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
928
+ # which you can change to suit your market and user base (see README for more info on config).
929
+ #
930
+ # Examples:
931
+ # f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
932
+ # f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
933
+ #
934
+ def country_input(method, options)
935
+ raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless self.respond_to?(:country_select)
936
+
937
+ html_options = options.delete(:input_html) || {}
938
+ priority_countries = options.delete(:priority_countries) || @@priority_countries
939
+
940
+ self.label(method, options_for_label(options)) +
941
+ self.country_select(method, priority_countries, set_options(options), html_options)
942
+ end
943
+
944
+
945
+ # Outputs a label containing a checkbox and the label text. The label defaults
946
+ # to the column name (method name) and can be altered with the :label option.
947
+ # :checked_value and :unchecked_value options are also available.
948
+ #
949
+ def boolean_input(method, options)
950
+ html_options = options.delete(:input_html) || {}
951
+
952
+ input = self.check_box(method, set_options(options).merge(html_options),
953
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
954
+ options = options_for_label(options)
955
+
956
+ # the label() method will insert this nested input into the label at the last minute
957
+ options[:label_prefix_for_nested_input] = input
958
+
959
+ self.label(method, options)
960
+ end
961
+
962
+ # Generates an input for the given method using the type supplied with :as.
963
+ #
964
+ # If the input is included in INPUT_MAPPINGS, it uses input_simple
965
+ # implementation which maps most of the inputs. All others have specific
966
+ # code and then a proper handler should be called (like radio_input) for
967
+ # :radio types.
968
+ #
969
+ def inline_input_for(method, options)
970
+ input_type = options.delete(:as)
971
+
972
+ if INPUT_MAPPINGS.key?(input_type)
973
+ input_simple(input_type, method, options)
974
+ else
975
+ send("#{input_type}_input", method, options)
976
+ end
977
+ end
978
+
979
+ # Generates hints for the given method using the text supplied in :hint.
980
+ #
981
+ def inline_hints_for(method, options) #:nodoc:
982
+ options[:hint] = localized_string(method, options[:hint], :hint)
983
+ return if options[:hint].blank?
984
+ template.content_tag(:p, options[:hint], :class => 'inline-hints')
985
+ end
986
+
987
+ # Creates an error sentence by calling to_sentence on the errors array.
988
+ #
989
+ def error_sentence(errors) #:nodoc:
990
+ template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
991
+ end
992
+
993
+ # Creates an error li list.
994
+ #
995
+ def error_list(errors) #:nodoc:
996
+ list_elements = []
997
+ errors.each do |error|
998
+ list_elements << template.content_tag(:li, error.untaint)
999
+ end
1000
+ template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
1001
+ end
1002
+
1003
+ # Creates an error sentence containing only the first error
1004
+ #
1005
+ def error_first(errors) #:nodoc:
1006
+ template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
1007
+ end
1008
+
1009
+ # Generates the required or optional string. If the value set is a proc,
1010
+ # it evaluates the proc first.
1011
+ #
1012
+ def required_or_optional_string(required) #:nodoc:
1013
+ string_or_proc = case required
1014
+ when true
1015
+ @@required_string
1016
+ when false
1017
+ @@optional_string
1018
+ else
1019
+ required
1020
+ end
1021
+
1022
+ if string_or_proc.is_a?(Proc)
1023
+ string_or_proc.call
1024
+ else
1025
+ string_or_proc.to_s
1026
+ end
1027
+ end
1028
+
1029
+ # Generates a fieldset and wraps the content in an ordered list. When working
1030
+ # with nested attributes (in Rails 2.3), it allows %i as interpolation option
1031
+ # in :name. So you can do:
1032
+ #
1033
+ # f.inputs :name => 'Task #%i', :for => :tasks
1034
+ #
1035
+ # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
1036
+ # 'Task #3' and so on.
1037
+ #
1038
+ def field_set_and_list_wrapping(html_options, contents='', &block) #:nodoc:
1039
+ html_options[:name] ||= html_options.delete(:title)
1040
+ html_options[:name] = localized_string(html_options[:name], html_options[:name], :title) if html_options[:name].is_a?(Symbol)
1041
+
1042
+ legend = html_options.delete(:name).to_s
1043
+ legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
1044
+ legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
1045
+
1046
+ if block_given?
1047
+ contents = if template.respond_to?(:is_haml?) && template.is_haml?
1048
+ template.capture_haml(&block)
1049
+ else
1050
+ template.capture(&block)
1051
+ end
1052
+ end
1053
+
1054
+ # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
1055
+ contents = contents.join if contents.respond_to?(:join)
1056
+ fieldset = template.content_tag(:fieldset,
1057
+ legend + template.content_tag(:ol, contents),
1058
+ html_options.except(:builder, :parent)
1059
+ )
1060
+
1061
+ template.concat(fieldset) if block_given?
1062
+ fieldset
1063
+ end
1064
+
1065
+ # Also generates a fieldset and an ordered list but with label based in
1066
+ # method. This methods is currently used by radio and datetime inputs.
1067
+ #
1068
+ def field_set_and_list_wrapping_for_method(method, options, contents)
1069
+ contents = contents.join if contents.respond_to?(:join)
1070
+
1071
+ template.content_tag(:fieldset,
1072
+ %{<legend>#{self.label(method, options_for_label(options).merge!(:as_span => true))}</legend>} +
1073
+ template.content_tag(:ol, contents)
1074
+ )
1075
+ end
1076
+
1077
+ # For methods that have a database column, take a best guess as to what the input method
1078
+ # should be. In most cases, it will just return the column type (eg :string), but for special
1079
+ # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
1080
+ # something different (like :password and :select).
1081
+ #
1082
+ # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
1083
+ # default is a :string, a similar behaviour to Rails' scaffolding.
1084
+ #
1085
+ def default_input_type(method) #:nodoc:
1086
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1087
+
1088
+ if column
1089
+ # handle the special cases where the column type doesn't map to an input method
1090
+ return :time_zone if column.type == :string && method.to_s =~ /time_zone/
1091
+ return :select if column.type == :integer && method.to_s =~ /_id$/
1092
+ return :datetime if column.type == :timestamp
1093
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
1094
+ return :password if column.type == :string && method.to_s =~ /password/
1095
+ return :country if column.type == :string && method.to_s =~ /country/
1096
+
1097
+ # otherwise assume the input name will be the same as the column type (eg string_input)
1098
+ return column.type
1099
+ else
1100
+ if @object
1101
+ return :select if find_reflection(method)
1102
+
1103
+ file = @object.send(method) if @object.respond_to?(method)
1104
+ return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
1105
+ end
1106
+
1107
+ return :password if method.to_s =~ /password/
1108
+ return :string
1109
+ end
1110
+ end
1111
+
1112
+ # Used by select and radio inputs. The collection can be retrieved by
1113
+ # three ways:
1114
+ #
1115
+ # * Explicitly provided through :collection
1116
+ # * Retrivied through an association
1117
+ # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
1118
+ #
1119
+ # If the collection is not a hash or an array of strings, fixnums or arrays,
1120
+ # we use label_method and value_method to retreive an array with the
1121
+ # appropriate label and value.
1122
+ #
1123
+ def find_collection_for_column(column, options)
1124
+ reflection = find_reflection(column)
1125
+
1126
+ collection = if options[:collection]
1127
+ options.delete(:collection)
1128
+ elsif reflection
1129
+ reflection.klass.find(:all)
1130
+ else
1131
+ create_boolean_collection(options)
1132
+ end
1133
+
1134
+ collection = collection.to_a if collection.is_a?(Hash)
1135
+
1136
+ # Return if we have an Array of strings, fixnums or arrays
1137
+ return collection if collection.instance_of?(Array) &&
1138
+ [Array, Fixnum, String, Symbol].include?(collection.first.class)
1139
+
1140
+ label = options.delete(:label_method) || detect_label_method(collection)
1141
+ value = options.delete(:value_method) || :id
1142
+
1143
+ collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
1144
+ end
1145
+
1146
+ # Detected the label collection method when none is supplied using the
1147
+ # values set in @@collection_label_methods.
1148
+ #
1149
+ def detect_label_method(collection) #:nodoc:
1150
+ @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
1151
+ end
1152
+
1153
+ # Returns a hash to be used by radio and select inputs when a boolean field
1154
+ # is provided.
1155
+ #
1156
+ def create_boolean_collection(options)
1157
+ options[:true] ||= I18n.t('yes', :default => 'Yes', :scope => [:formtastic])
1158
+ options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
1159
+ options[:value_as_class] = true unless options.key?(:value_as_class)
1160
+
1161
+ [ [ options.delete(:true), true], [ options.delete(:false), false ] ]
1162
+ end
1163
+
1164
+ # Used by association inputs (select, radio) to generate the name that should
1165
+ # be used for the input
1166
+ #
1167
+ # belongs_to :author; f.input :author; will generate 'author_id'
1168
+ # belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
1169
+ # has_many :authors; f.input :authors; will generate 'author_ids'
1170
+ # has_and_belongs_to_many will act like has_many
1171
+ #
1172
+ def generate_association_input_name(method)
1173
+ if reflection = find_reflection(method)
1174
+ if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1175
+ "#{method.to_s.singularize}_ids"
1176
+ else
1177
+ reflection.options[:foreign_key] || "#{method}_id"
1178
+ end
1179
+ else
1180
+ method
1181
+ end
1182
+ end
1183
+
1184
+ # If an association method is passed in (f.input :author) try to find the
1185
+ # reflection object.
1186
+ #
1187
+ def find_reflection(method)
1188
+ @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
1189
+ end
1190
+
1191
+ # Generates default_string_options by retrieving column information from
1192
+ # the database.
1193
+ #
1194
+ def default_string_options(method, type) #:nodoc:
1195
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1196
+
1197
+ if type == :numeric || column.nil? || column.limit.nil?
1198
+ { :size => @@default_text_field_size }
1199
+ else
1200
+ { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
1201
+ end
1202
+ end
1203
+
1204
+ # Generate the html id for the li tag.
1205
+ # It takes into account options[:index] and @auto_index to generate li
1206
+ # elements with appropriate index scope. It also sanitizes the object
1207
+ # and method names.
1208
+ #
1209
+ def generate_html_id(method_name, value='input')
1210
+ if options.has_key?(:index)
1211
+ index = "_#{options[:index]}"
1212
+ elsif defined?(@auto_index)
1213
+ index = "_#{@auto_index}"
1214
+ else
1215
+ index = ""
1216
+ end
1217
+ sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
1218
+
1219
+ "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1220
+ end
1221
+
1222
+ # Gets the nested_child_index value from the parent builder. In Rails 2.3
1223
+ # it always returns a fixnum. In next versions it returns a hash with each
1224
+ # association that the parent builds.
1225
+ #
1226
+ def parent_child_index(parent)
1227
+ duck = parent[:builder].instance_variable_get('@nested_child_index')
1228
+
1229
+ if duck.is_a?(Hash)
1230
+ child = parent[:for]
1231
+ child = child.first if child.respond_to?(:first)
1232
+ duck[child].to_i + 1
1233
+ else
1234
+ duck.to_i + 1
1235
+ end
1236
+ end
1237
+
1238
+ def sanitized_object_name
1239
+ @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1240
+ end
1241
+
1242
+ def humanized_attribute_name(method)
1243
+ if @object && @object.class.respond_to?(:human_attribute_name)
1244
+ @object.class.human_attribute_name(method.to_s)
1245
+ else
1246
+ method.to_s.send(@@label_str_method)
1247
+ end
1248
+ end
1249
+
1250
+ # Internal generic method for looking up localized values within Formtastic
1251
+ # using I18n, if no explicit value is set and I18n-lookups are enabled.
1252
+ #
1253
+ # Enabled/Disable this by setting:
1254
+ #
1255
+ # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
1256
+ #
1257
+ # Lookup priority:
1258
+ #
1259
+ # 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
1260
+ # 'formtastic.{{type}}.{{model}}.{{attribute}}'
1261
+ # 'formtastic.{{type}}.{{attribute}}'
1262
+ #
1263
+ # Example:
1264
+ #
1265
+ # 'formtastic.labels.post.edit.title'
1266
+ # 'formtastic.labels.post.title'
1267
+ # 'formtastic.labels.title'
1268
+ #
1269
+ # NOTE: Generic, but only used for form input labels/hints.
1270
+ #
1271
+ def localized_string(key, value, type, options = {})
1272
+ key = value if value.is_a?(::Symbol)
1273
+
1274
+ if value.is_a?(::String)
1275
+ value
1276
+ else
1277
+ use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
1278
+
1279
+ if use_i18n
1280
+ model_name = (@object ? @object.class.name : @object_name.to_s.send(@@label_str_method)).underscore
1281
+ action_name = template.params[:action].to_s rescue ''
1282
+ attribute_name = key.to_s
1283
+
1284
+ defaults = I18N_SCOPES.collect do |i18n_scope|
1285
+ i18n_path = i18n_scope.dup
1286
+ i18n_path.gsub!('{{action}}', action_name)
1287
+ i18n_path.gsub!('{{model}}', model_name)
1288
+ i18n_path.gsub!('{{attribute}}', attribute_name)
1289
+ i18n_path.gsub!('..', '.')
1290
+ i18n_path.to_sym
1291
+ end
1292
+ defaults << ''
1293
+
1294
+ i18n_value = ::I18n.t(defaults.shift, options.merge(:default => defaults,
1295
+ :scope => :"formtastic.#{type.to_s.pluralize}"))
1296
+ i18n_value.blank? ? nil : i18n_value
1297
+ end
1298
+ end
1299
+ end
1300
+
1301
+ def send_or_call(duck, object)
1302
+ if duck.is_a?(Proc)
1303
+ duck.call(object)
1304
+ else
1305
+ object.send(duck)
1306
+ end
1307
+ end
1308
+
1309
+ private
1310
+
1311
+ def set_include_blank(options)
1312
+ unless options.key?(:include_blank) || options.key?(:prompt)
1313
+ options[:include_blank] = @@include_blank_for_select_by_default
1314
+ end
1315
+ options
1316
+ end
1317
+
1318
+ end
1319
+
1320
+ # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
1321
+ #
1322
+ # * semantic_form_for(@post)
1323
+ # * semantic_fields_for(@post)
1324
+ # * semantic_form_remote_for(@post)
1325
+ # * semantic_remote_form_for(@post)
1326
+ #
1327
+ # Each of which are the equivalent of:
1328
+ #
1329
+ # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1330
+ # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
1331
+ # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
1332
+ # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
1333
+ #
1334
+ # Example Usage:
1335
+ #
1336
+ # <% semantic_form_for @post do |f| %>
1337
+ # <%= f.input :title %>
1338
+ # <%= f.input :body %>
1339
+ # <% end %>
1340
+ #
1341
+ # The above examples use a resource-oriented style of form_for() helper where only the @post
1342
+ # object is given as an argument, but the generic style is also supported if you really want it,
1343
+ # as is forms with inline objects (Post.new) rather than objects with instance variables (@post):
1344
+ #
1345
+ # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
1346
+ # ...
1347
+ # <% end %>
1348
+ #
1349
+ # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
1350
+ # ...
1351
+ # <% end %>
1352
+ #
1353
+ # The shorter, resource-oriented style is most definitely preferred, and has recieved the most
1354
+ # testing to date.
1355
+ #
1356
+ # Please note: Although it's possible to call Rails' built-in form_for() helper without an
1357
+ # object, all semantic forms *must* have an object (either Post.new or @post), as Formtastic
1358
+ # has too many dependencies on an ActiveRecord object being present.
1359
+ #
1360
+ module SemanticFormHelper
1361
+ @@builder = Formtastic::SemanticFormBuilder
1362
+ mattr_accessor :builder
1363
+
1364
+ @@default_field_error_proc = nil
1365
+
1366
+ # Override the default ActiveRecordHelper behaviour of wrapping the input.
1367
+ # This gets taken care of semantically by adding an error class to the LI tag
1368
+ # containing the input.
1369
+ #
1370
+ FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
1371
+ html_tag
1372
+ end
1373
+
1374
+ def use_custom_field_error_proc(&block)
1375
+ @@default_field_error_proc = ::ActionView::Base.field_error_proc
1376
+ ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
1377
+ result = yield
1378
+ ::ActionView::Base.field_error_proc = @@default_field_error_proc
1379
+ result
1380
+ end
1381
+
1382
+ [:form_for, :fields_for, :remote_form_for].each do |meth|
1383
+ src = <<-END_SRC
1384
+ def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1385
+ options = args.extract_options!
1386
+ options[:builder] = @@builder
1387
+ options[:html] ||= {}
1388
+
1389
+ class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1390
+ class_names << "formtastic"
1391
+ class_names << case record_or_name_or_array
1392
+ when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1393
+ when Array then record_or_name_or_array.last.class.to_s.underscore # [@post, @comment] # => "comment"
1394
+ else record_or_name_or_array.class.to_s.underscore # @post => "post"
1395
+ end
1396
+ options[:html][:class] = class_names.join(" ")
1397
+
1398
+ use_custom_field_error_proc do
1399
+ #{meth}(record_or_name_or_array, *(args << options), &proc)
1400
+ end
1401
+ end
1402
+ END_SRC
1403
+ module_eval src, __FILE__, __LINE__
1404
+ end
1405
+ alias :semantic_form_remote_for :semantic_remote_form_for
1406
+
1407
+ end
1408
+ end