formtastic 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +19 -2
- data/Rakefile +3 -4
- data/generators/form/form_generator.rb +9 -3
- data/lib/formtastic.rb +1042 -913
- data/lib/formtastic/i18n.rb +6 -1
- data/spec/custom_macros.rb +103 -3
- data/spec/form_helper_spec.rb +11 -1
- data/spec/i18n_spec.rb +48 -0
- data/spec/input_spec.rb +8 -8
- data/spec/inputs/boolean_input_spec.rb +34 -1
- data/spec/inputs/check_boxes_input_spec.rb +57 -1
- data/spec/inputs/date_input_spec.rb +3 -1
- data/spec/inputs/datetime_input_spec.rb +8 -8
- data/spec/inputs/radio_input_spec.rb +38 -2
- data/spec/inputs/select_input_spec.rb +124 -12
- data/spec/inputs/time_input_spec.rb +3 -1
- data/spec/inputs/time_zone_input_spec.rb +44 -1
- data/spec/inputs_spec.rb +10 -1
- data/spec/semantic_fields_for_spec.rb +3 -1
- data/spec/spec_helper.rb +13 -2
- metadata +4 -14
data/README.textile
CHANGED
@@ -446,7 +446,22 @@ $ ./script/generate form Post --partial
|
|
446
446
|
create app/views/posts/_form.html.erb
|
447
447
|
</pre>
|
448
448
|
|
449
|
-
To generate *HAML* markup, just add the @--haml@ as argument:
|
449
|
+
To generate *HAML* markup, just add the @--haml@ as argument:
|
450
|
+
|
451
|
+
<pre>
|
452
|
+
$ ./script/generate form Post --haml
|
453
|
+
exists app/views/admin/posts
|
454
|
+
create app/views/admin/posts/_form.html.haml
|
455
|
+
</pre>
|
456
|
+
|
457
|
+
To specify the controller in a namespace (eg admin/posts instead of posts), use the --controller argument:
|
458
|
+
|
459
|
+
<pre>
|
460
|
+
$ ./script/generate form Post --partial --controller admin/posts
|
461
|
+
exists app/views/admin/posts
|
462
|
+
create app/views/admin/posts/_form.html.erb
|
463
|
+
</pre>
|
464
|
+
|
450
465
|
|
451
466
|
h2. Custom Inputs
|
452
467
|
|
@@ -455,6 +470,8 @@ If you want to add your own input types to encapsulate your own logic or interfa
|
|
455
470
|
@Formtastic::SemanticFormHelper.builder = MyCustomBuilder@
|
456
471
|
|
457
472
|
|
473
|
+
|
474
|
+
|
458
475
|
h2. Status
|
459
476
|
|
460
477
|
Formtastic has been in active development for about a year. We've just recently jumped to an 0.9 version number, signaling that we consider this a 1.0 release candidate, and that the API won't change significantly for the 1.x series.
|
@@ -464,7 +481,7 @@ h2. Dependencies
|
|
464
481
|
|
465
482
|
There are none, but...
|
466
483
|
|
467
|
-
* if you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin is installed, you won't have to specify the
|
484
|
+
* 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).
|
468
485
|
* if you want to use the @:country@ input, you'll need to install the "iso-3166-country-select plugin":http://github.com/rails/iso-3166-country-select (or any other country_select plugin with the same API).
|
469
486
|
* "rspec":http://github.com/dchelimsky/rspec/, "rspec_hpricot_matchers":http://rubyforge.org/projects/rspec-hpricot/ and "rcov":http://github.com/relevance/rcov gems (plus any of their own dependencies) are required for the test suite.
|
470
487
|
|
data/Rakefile
CHANGED
@@ -23,7 +23,7 @@ begin
|
|
23
23
|
========================================================================
|
24
24
|
Thanks for installing Formtastic!
|
25
25
|
------------------------------------------------------------------------
|
26
|
-
You can now (optionally) run the
|
26
|
+
You can now (optionally) run the generator to copy some stylesheets and
|
27
27
|
a config initializer into your application:
|
28
28
|
./script/generate formtastic
|
29
29
|
|
@@ -59,13 +59,12 @@ begin
|
|
59
59
|
# Development dependencies. Not installed by default.
|
60
60
|
# Install with: sudo gem install formtastic --development
|
61
61
|
s.add_development_dependency 'rspec-rails', '>= 1.2.6'
|
62
|
-
s.add_development_dependency '
|
63
|
-
s.add_development_dependency 'rspec_hpricot_matchers', '>= 1.0.0'
|
62
|
+
s.add_development_dependency 'rspec_tag_matchers', '>= 1.0.0'
|
64
63
|
end
|
65
64
|
|
66
65
|
Jeweler::GemcutterTasks.new
|
67
66
|
rescue LoadError
|
68
|
-
puts "[formtastic:] Jeweler - or one of
|
67
|
+
puts "[formtastic:] Jeweler - or one of its dependencies - is not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
|
69
68
|
end
|
70
69
|
|
71
70
|
desc 'Default: run unit specs.'
|
@@ -39,10 +39,11 @@ class FormGenerator < Rails::Generator::NamedBase
|
|
39
39
|
def manifest
|
40
40
|
record do |m|
|
41
41
|
if options[:partial]
|
42
|
+
controller_and_view_path = options[:controller] || File.join(controller_class_path, controller_file_name)
|
42
43
|
# Ensure directory exists.
|
43
|
-
m.directory File.join(VIEWS_PATH,
|
44
|
+
m.directory File.join(VIEWS_PATH, controller_and_view_path)
|
44
45
|
# Create a form partial for the model as "_form" in it's views path.
|
45
|
-
m.template "view__form.html.#{template_type}", File.join(VIEWS_PATH,
|
46
|
+
m.template "view__form.html.#{template_type}", File.join(VIEWS_PATH, controller_and_view_path, "_form.html.#{template_type}")
|
46
47
|
else
|
47
48
|
# Load template file, and render without saving to file
|
48
49
|
template = File.read(File.join(source_root, "view__form.html.#{template_type}"))
|
@@ -85,7 +86,7 @@ class FormGenerator < Rails::Generator::NamedBase
|
|
85
86
|
|
86
87
|
# Add additional model attributes if specified in args - probably not that common scenario.
|
87
88
|
def attributes
|
88
|
-
# Get columns for the requested model
|
89
|
+
# Get columns for the requested model.
|
89
90
|
existing_attributes = @class_name.constantize.content_columns.reject { |column| IGNORED_COLUMNS.include?(column.name.to_sym) }
|
90
91
|
@args = super + existing_attributes
|
91
92
|
end
|
@@ -105,6 +106,11 @@ class FormGenerator < Rails::Generator::NamedBase
|
|
105
106
|
"Save generated output directly to a form partial (app/views/{resource}/_form.html.*).") do |v|
|
106
107
|
options[:partial] = v
|
107
108
|
end
|
109
|
+
|
110
|
+
opt.on('--controller CONTROLLER_PATH',
|
111
|
+
"Specify a non-standard controller for the specified model (e.g. admin/posts).") do |v|
|
112
|
+
options[:controller] = v if v.present?
|
113
|
+
end
|
108
114
|
end
|
109
115
|
|
110
116
|
def banner
|
data/lib/formtastic.rb
CHANGED
@@ -27,10 +27,6 @@ module Formtastic #:nodoc:
|
|
27
27
|
|
28
28
|
INLINE_ERROR_TYPES = [:sentence, :list, :first]
|
29
29
|
|
30
|
-
I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
|
31
|
-
'{{model}}.{{attribute}}',
|
32
|
-
'{{attribute}}']
|
33
|
-
|
34
30
|
attr_accessor :template
|
35
31
|
|
36
32
|
# Returns a suitable form input for the given +method+, using the database column information
|
@@ -80,7 +76,7 @@ module Formtastic #:nodoc:
|
|
80
76
|
#
|
81
77
|
def input(method, options = {})
|
82
78
|
options[:required] = method_required?(method) unless options.key?(:required)
|
83
|
-
options[:as] ||= default_input_type(method)
|
79
|
+
options[:as] ||= default_input_type(method, options)
|
84
80
|
|
85
81
|
html_class = [ options[:as], (options[:required] ? :required : :optional) ]
|
86
82
|
html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
|
@@ -96,7 +92,7 @@ module Formtastic #:nodoc:
|
|
96
92
|
|
97
93
|
input_parts = @@inline_order.dup
|
98
94
|
input_parts.delete(:errors) if options[:as] == :hidden
|
99
|
-
|
95
|
+
|
100
96
|
list_item_content = input_parts.map do |type|
|
101
97
|
send(:"inline_#{type}_for", method, options)
|
102
98
|
end.compact.join("\n")
|
@@ -251,11 +247,13 @@ module Formtastic #:nodoc:
|
|
251
247
|
# instead (just as you would do with Rails' form builder).
|
252
248
|
#
|
253
249
|
def inputs(*args, &block)
|
250
|
+
title = field_set_title_from_args(*args)
|
254
251
|
html_options = args.extract_options!
|
255
252
|
html_options[:class] ||= "inputs"
|
256
|
-
|
257
|
-
|
258
|
-
|
253
|
+
html_options[:name] = title
|
254
|
+
|
255
|
+
if html_options[:for] # Nested form
|
256
|
+
inputs_for_nested_attributes(*(args << html_options), &block)
|
259
257
|
elsif block_given?
|
260
258
|
field_set_and_list_wrapping(*(args << html_options), &block)
|
261
259
|
else
|
@@ -319,7 +317,6 @@ module Formtastic #:nodoc:
|
|
319
317
|
key = :submit
|
320
318
|
object_name = @object_name.to_s.send(@@label_str_method)
|
321
319
|
end
|
322
|
-
fallback_text ||= "#{key.to_s.humanize} {{model}}"
|
323
320
|
|
324
321
|
text = (self.localized_string(key, text, :action, :model => object_name) ||
|
325
322
|
::Formtastic::I18n.t(key, :model => object_name)) unless text.is_a?(::String)
|
@@ -353,7 +350,7 @@ module Formtastic #:nodoc:
|
|
353
350
|
#
|
354
351
|
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
355
352
|
opts = args.extract_options!
|
356
|
-
opts
|
353
|
+
opts[:builder] ||= Formtastic::SemanticFormHelper.builder
|
357
354
|
args.push(opts)
|
358
355
|
fields_for(record_or_name_or_array, *args, &block)
|
359
356
|
end
|
@@ -407,1018 +404,1150 @@ module Formtastic #:nodoc:
|
|
407
404
|
# f.errors_on(:body)
|
408
405
|
# end
|
409
406
|
#
|
410
|
-
def inline_errors_for(method, options=nil) #:nodoc:
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
407
|
+
def inline_errors_for(method, options = nil) #:nodoc:
|
408
|
+
if render_inline_errors?
|
409
|
+
errors = @object.errors[method.to_sym]
|
410
|
+
send(:"error_#{@@inline_errors}", [*errors]) if errors.present?
|
411
|
+
else
|
412
|
+
nil
|
413
|
+
end
|
415
414
|
end
|
416
415
|
alias :errors_on :inline_errors_for
|
417
416
|
|
418
417
|
protected
|
419
418
|
|
420
|
-
|
421
|
-
|
422
|
-
def content_columns
|
423
|
-
if @object.present?
|
424
|
-
@object.class.name.constantize.content_columns.collect { |c| c.name.to_sym }.compact
|
425
|
-
else
|
426
|
-
@object_name.to_s.classify.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
|
419
|
+
def render_inline_errors?
|
420
|
+
@object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(@@inline_errors)
|
427
421
|
end
|
428
|
-
end
|
429
422
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
@object.class.reflections.collect do |name, _|
|
435
|
-
if by_associations.present?
|
436
|
-
name if by_associations.include?(_.macro)
|
437
|
-
else
|
438
|
-
name
|
439
|
-
end
|
440
|
-
end.compact
|
441
|
-
else
|
442
|
-
[]
|
423
|
+
# Collects content columns (non-relation columns) for the current form object class.
|
424
|
+
#
|
425
|
+
def content_columns #:nodoc:
|
426
|
+
self.model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
|
443
427
|
end
|
444
|
-
end
|
445
428
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
429
|
+
# Collects association columns (relation columns) for the current form object class.
|
430
|
+
#
|
431
|
+
def association_columns(*by_associations) #:nodoc:
|
432
|
+
if @object.present?
|
433
|
+
@object.class.reflections.collect do |name, _|
|
434
|
+
if by_associations.present?
|
435
|
+
name if by_associations.include?(_.macro)
|
436
|
+
else
|
437
|
+
name
|
438
|
+
end
|
439
|
+
end.compact
|
440
|
+
else
|
441
|
+
[]
|
442
|
+
end
|
443
|
+
end
|
451
444
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
#
|
458
|
-
def inputs_for_nested_attributes(args, options, &block)
|
459
|
-
args << options.merge!(:parent => { :builder => self, :for => options[:for] })
|
445
|
+
# Prepare options to be sent to label
|
446
|
+
#
|
447
|
+
def options_for_label(options) #:nodoc:
|
448
|
+
options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
|
449
|
+
end
|
460
450
|
|
461
|
-
|
462
|
-
|
463
|
-
|
451
|
+
# Deals with :for option when it's supplied to inputs methods. Additional
|
452
|
+
# options to be passed down to :for should be supplied using :for_options
|
453
|
+
# key.
|
454
|
+
#
|
455
|
+
# It should raise an error if a block with arity zero is given.
|
456
|
+
#
|
457
|
+
def inputs_for_nested_attributes(*args, &block) #:nodoc:
|
458
|
+
options = args.extract_options!
|
459
|
+
args << options.merge!(:parent => { :builder => self, :for => options[:for] })
|
460
|
+
|
461
|
+
fields_for_block = if block_given?
|
462
|
+
raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
|
463
|
+
'but the block does not accept any argument.' if block.arity <= 0
|
464
|
+
|
465
|
+
proc { |f| f.inputs(*args){ block.call(f) } }
|
466
|
+
else
|
467
|
+
proc { |f| f.inputs(*args) }
|
468
|
+
end
|
464
469
|
|
465
|
-
|
466
|
-
|
467
|
-
proc { |f| f.inputs(*args) }
|
470
|
+
fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
|
471
|
+
semantic_fields_for(*fields_for_args, &fields_for_block)
|
468
472
|
end
|
469
473
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
def set_options(options)
|
477
|
-
options.except(:value_method, :label_method, :collection, :required, :label,
|
478
|
-
:as, :hint, :input_html, :label_html, :value_as_class)
|
479
|
-
end
|
474
|
+
# Remove any Formtastic-specific options before passing the down options.
|
475
|
+
#
|
476
|
+
def strip_formtastic_options(options) #:nodoc:
|
477
|
+
options.except(:value_method, :label_method, :collection, :required, :label,
|
478
|
+
:as, :hint, :input_html, :label_html, :value_as_class)
|
479
|
+
end
|
480
480
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
481
|
+
# Determins if the attribute (eg :title) should be considered required or not.
|
482
|
+
#
|
483
|
+
# * if the :required option was provided in the options hash, the true/false value will be
|
484
|
+
# returned immediately, allowing the view to override any guesswork that follows:
|
485
|
+
#
|
486
|
+
# * if the :required option isn't provided in the options hash, and the ValidationReflection
|
487
|
+
# plugin is installed (http://github.com/redinger/validation_reflection), true is returned
|
488
|
+
# if the validates_presence_of macro has been used in the class for this attribute, or false
|
489
|
+
# otherwise.
|
490
|
+
#
|
491
|
+
# * if the :required option isn't provided, and the plugin isn't available, the value of the
|
492
|
+
# configuration option @@all_fields_required_by_default is used.
|
493
|
+
#
|
494
|
+
def method_required?(attribute) #:nodoc:
|
495
|
+
if @object && @object.class.respond_to?(:reflect_on_validations_for)
|
496
|
+
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
|
497
|
+
|
498
|
+
@object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
|
499
|
+
validation.macro == :validates_presence_of &&
|
500
|
+
validation.name == attribute_sym &&
|
501
|
+
(validation.options.present? ? options_require_validation?(validation.options) : true)
|
502
|
+
end
|
503
|
+
else
|
504
|
+
@@all_fields_required_by_default
|
502
505
|
end
|
503
|
-
else
|
504
|
-
@@all_fields_required_by_default
|
505
506
|
end
|
506
|
-
end
|
507
507
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
508
|
+
# Determines whether the given options evaluate to true
|
509
|
+
def options_require_validation?(options) #nodoc
|
510
|
+
if_condition = !options[:if].nil?
|
511
|
+
condition = if_condition ? options[:if] : options[:unless]
|
512
512
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
513
|
+
condition = if condition.respond_to?(:call)
|
514
|
+
condition.call(@object)
|
515
|
+
elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
|
516
|
+
@object.send(condition)
|
517
|
+
else
|
518
|
+
condition
|
519
|
+
end
|
520
520
|
|
521
|
-
|
522
|
-
|
521
|
+
if_condition ? !!condition : !condition
|
522
|
+
end
|
523
523
|
|
524
|
-
|
525
|
-
|
526
|
-
|
524
|
+
def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
|
525
|
+
html_options = options.delete(:input_html) || {}
|
526
|
+
html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password].include?(type)
|
527
527
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
# Outputs a label and standard Rails text field inside the wrapper.
|
533
|
-
def string_input(method, options)
|
534
|
-
basic_input_helper(:text_field, :string, method, options)
|
535
|
-
end
|
528
|
+
self.label(method, options_for_label(options)) <<
|
529
|
+
self.send(form_helper_method, method, html_options)
|
530
|
+
end
|
536
531
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
532
|
+
# Outputs a label and standard Rails text field inside the wrapper.
|
533
|
+
def string_input(method, options)
|
534
|
+
basic_input_helper(:text_field, :string, method, options)
|
535
|
+
end
|
541
536
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
537
|
+
# Outputs a label and standard Rails password field inside the wrapper.
|
538
|
+
def password_input(method, options)
|
539
|
+
basic_input_helper(:password_field, :password, method, options)
|
540
|
+
end
|
546
541
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
542
|
+
# Outputs a label and standard Rails text field inside the wrapper.
|
543
|
+
def numeric_input(method, options)
|
544
|
+
basic_input_helper(:text_field, :numeric, method, options)
|
545
|
+
end
|
551
546
|
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
547
|
+
# Ouputs a label and standard Rails text area inside the wrapper.
|
548
|
+
def text_input(method, options)
|
549
|
+
basic_input_helper(:text_area, :text, method, options)
|
550
|
+
end
|
556
551
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
#
|
561
|
-
def hidden_input(method, options)
|
562
|
-
options ||= {}
|
563
|
-
if options[:input_html].present?
|
564
|
-
options[:value] = options[:input_html][:value] if options[:input_html][:value].present?
|
552
|
+
# Outputs a label and a standard Rails file field inside the wrapper.
|
553
|
+
def file_input(method, options)
|
554
|
+
basic_input_helper(:file_field, :file, method, options)
|
565
555
|
end
|
566
|
-
self.hidden_field(method, set_options(options))
|
567
|
-
end
|
568
556
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
# <select id="book_author_id" name="book[author_id]">
|
580
|
-
# <option value=""></option>
|
581
|
-
# <option value="1">Justin French</option>
|
582
|
-
# <option value="2">Jane Doe</option>
|
583
|
-
# </select>
|
584
|
-
#
|
585
|
-
# Example (has_many):
|
586
|
-
#
|
587
|
-
# f.input :chapters
|
588
|
-
#
|
589
|
-
# <label for="book_chapter_ids">Chapters</label>
|
590
|
-
# <select id="book_chapter_ids" name="book[chapter_ids]">
|
591
|
-
# <option value=""></option>
|
592
|
-
# <option value="1">Chapter 1</option>
|
593
|
-
# <option value="2">Chapter 2</option>
|
594
|
-
# </select>
|
595
|
-
#
|
596
|
-
# Example (has_and_belongs_to_many):
|
597
|
-
#
|
598
|
-
# f.input :authors
|
599
|
-
#
|
600
|
-
# <label for="book_author_ids">Authors</label>
|
601
|
-
# <select id="book_author_ids" name="book[author_ids]">
|
602
|
-
# <option value=""></option>
|
603
|
-
# <option value="1">Justin French</option>
|
604
|
-
# <option value="2">Jane Doe</option>
|
605
|
-
# </select>
|
606
|
-
#
|
607
|
-
#
|
608
|
-
# You can customize the options available in the select by passing in a collection (an Array or
|
609
|
-
# Hash) through the :collection option. If not provided, the choices are found by inferring the
|
610
|
-
# parent's class name from the method name and simply calling find(:all) on it
|
611
|
-
# (VehicleOwner.find(:all) in the example above).
|
612
|
-
#
|
613
|
-
# Examples:
|
614
|
-
#
|
615
|
-
# f.input :author, :collection => @authors
|
616
|
-
# f.input :author, :collection => Author.find(:all)
|
617
|
-
# f.input :author, :collection => [@justin, @kate]
|
618
|
-
# f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
|
619
|
-
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
620
|
-
#
|
621
|
-
# The :label_method option allows you to customize the text label inside each option tag two ways:
|
622
|
-
#
|
623
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
624
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
625
|
-
#
|
626
|
-
# Examples:
|
627
|
-
#
|
628
|
-
# f.input :author, :label_method => :full_name
|
629
|
-
# f.input :author, :label_method => :login
|
630
|
-
# f.input :author, :label_method => :full_name_with_post_count
|
631
|
-
# f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
632
|
-
#
|
633
|
-
# The :value_method option provides the same customization of the value attribute of each option tag.
|
634
|
-
#
|
635
|
-
# Examples:
|
636
|
-
#
|
637
|
-
# f.input :author, :value_method => :full_name
|
638
|
-
# f.input :author, :value_method => :login
|
639
|
-
# f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
|
640
|
-
#
|
641
|
-
# You can pre-select a specific option value by passing in the :selected option.
|
642
|
-
#
|
643
|
-
# Examples:
|
644
|
-
#
|
645
|
-
# f.input :author, :selected => current_user.id
|
646
|
-
# f.input :author, :value_method => :login, :selected => current_user.login
|
647
|
-
#
|
648
|
-
# You can pass html_options to the select tag using :input_html => {}
|
649
|
-
#
|
650
|
-
# Examples:
|
651
|
-
#
|
652
|
-
# f.input :authors, :input_html => {:size => 20, :multiple => true}
|
653
|
-
#
|
654
|
-
# By default, all select inputs will have a blank option at the top of the list. You can add
|
655
|
-
# a prompt with the :prompt option, or disable the blank option with :include_blank => false.
|
656
|
-
#
|
657
|
-
#
|
658
|
-
# You can group the options in optgroup elements by passing the :group_by option
|
659
|
-
# (Note: only tested for belongs_to relations)
|
660
|
-
#
|
661
|
-
# Examples:
|
662
|
-
#
|
663
|
-
# f.input :author, :group_by => :continent
|
664
|
-
#
|
665
|
-
# All the other options should work as expected. If you want to call a custom method on the
|
666
|
-
# group item. You can include the option:group_label_method
|
667
|
-
# Examples:
|
668
|
-
#
|
669
|
-
# f.input :author, :group_by => :continents, :group_label_method => :something_different
|
670
|
-
#
|
671
|
-
def select_input(method, options)
|
672
|
-
html_options = options.delete(:input_html) || {}
|
673
|
-
options = set_include_blank(options)
|
674
|
-
|
675
|
-
reflection = find_reflection(method)
|
676
|
-
if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
|
677
|
-
options[:include_blank] = false
|
678
|
-
html_options[:multiple] ||= true
|
679
|
-
html_options[:size] ||= 5
|
557
|
+
# Outputs a hidden field inside the wrapper, which should be hidden with CSS.
|
558
|
+
# Additionals options can be given and will be sent straight to hidden input
|
559
|
+
# element.
|
560
|
+
#
|
561
|
+
def hidden_input(method, options)
|
562
|
+
options ||= {}
|
563
|
+
if options[:input_html].present?
|
564
|
+
options[:value] = options[:input_html][:value] if options[:input_html][:value].present?
|
565
|
+
end
|
566
|
+
self.hidden_field(method, strip_formtastic_options(options))
|
680
567
|
end
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
568
|
+
|
569
|
+
# Outputs a label and a select box containing options from the parent
|
570
|
+
# (belongs_to, has_many, has_and_belongs_to_many) association. If an association
|
571
|
+
# is has_many or has_and_belongs_to_many the select box will be set as multi-select
|
572
|
+
# and size = 5
|
573
|
+
#
|
574
|
+
# Example (belongs_to):
|
575
|
+
#
|
576
|
+
# f.input :author
|
577
|
+
#
|
578
|
+
# <label for="book_author_id">Author</label>
|
579
|
+
# <select id="book_author_id" name="book[author_id]">
|
580
|
+
# <option value=""></option>
|
581
|
+
# <option value="1">Justin French</option>
|
582
|
+
# <option value="2">Jane Doe</option>
|
583
|
+
# </select>
|
584
|
+
#
|
585
|
+
# Example (has_many):
|
586
|
+
#
|
587
|
+
# f.input :chapters
|
588
|
+
#
|
589
|
+
# <label for="book_chapter_ids">Chapters</label>
|
590
|
+
# <select id="book_chapter_ids" name="book[chapter_ids]">
|
591
|
+
# <option value=""></option>
|
592
|
+
# <option value="1">Chapter 1</option>
|
593
|
+
# <option value="2">Chapter 2</option>
|
594
|
+
# </select>
|
595
|
+
#
|
596
|
+
# Example (has_and_belongs_to_many):
|
597
|
+
#
|
598
|
+
# f.input :authors
|
599
|
+
#
|
600
|
+
# <label for="book_author_ids">Authors</label>
|
601
|
+
# <select id="book_author_ids" name="book[author_ids]">
|
602
|
+
# <option value=""></option>
|
603
|
+
# <option value="1">Justin French</option>
|
604
|
+
# <option value="2">Jane Doe</option>
|
605
|
+
# </select>
|
606
|
+
#
|
607
|
+
#
|
608
|
+
# You can customize the options available in the select by passing in a collection (an Array or
|
609
|
+
# Hash) through the :collection option. If not provided, the choices are found by inferring the
|
610
|
+
# parent's class name from the method name and simply calling find(:all) on it
|
611
|
+
# (VehicleOwner.find(:all) in the example above).
|
612
|
+
#
|
613
|
+
# Examples:
|
614
|
+
#
|
615
|
+
# f.input :author, :collection => @authors
|
616
|
+
# f.input :author, :collection => Author.find(:all)
|
617
|
+
# f.input :author, :collection => [@justin, @kate]
|
618
|
+
# f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
|
619
|
+
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
620
|
+
#
|
621
|
+
# The :label_method option allows you to customize the text label inside each option tag two ways:
|
622
|
+
#
|
623
|
+
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
624
|
+
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
625
|
+
#
|
626
|
+
# Examples:
|
627
|
+
#
|
628
|
+
# f.input :author, :label_method => :full_name
|
629
|
+
# f.input :author, :label_method => :login
|
630
|
+
# f.input :author, :label_method => :full_name_with_post_count
|
631
|
+
# f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
632
|
+
#
|
633
|
+
# The :value_method option provides the same customization of the value attribute of each option tag.
|
634
|
+
#
|
635
|
+
# Examples:
|
636
|
+
#
|
637
|
+
# f.input :author, :value_method => :full_name
|
638
|
+
# f.input :author, :value_method => :login
|
639
|
+
# f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
|
640
|
+
#
|
641
|
+
# You can pre-select a specific option value by passing in the :selected option.
|
642
|
+
#
|
643
|
+
# Examples:
|
644
|
+
#
|
645
|
+
# f.input :author, :selected => current_user.id
|
646
|
+
# f.input :author, :value_method => :login, :selected => current_user.login
|
647
|
+
# f.input :authors, :value_method => :login, :selected => Author.most_popular.collect(&:id)
|
648
|
+
# f.input :authors, :value_method => :login, :selected => nil # override any defaults: select none
|
649
|
+
#
|
650
|
+
# You can pass html_options to the select tag using :input_html => {}
|
651
|
+
#
|
652
|
+
# Examples:
|
653
|
+
#
|
654
|
+
# f.input :authors, :input_html => {:size => 20, :multiple => true}
|
655
|
+
#
|
656
|
+
# By default, all select inputs will have a blank option at the top of the list. You can add
|
657
|
+
# a prompt with the :prompt option, or disable the blank option with :include_blank => false.
|
658
|
+
#
|
659
|
+
#
|
660
|
+
# You can group the options in optgroup elements by passing the :group_by option
|
661
|
+
# (Note: only tested for belongs_to relations)
|
662
|
+
#
|
663
|
+
# Examples:
|
664
|
+
#
|
665
|
+
# f.input :author, :group_by => :continent
|
666
|
+
#
|
667
|
+
# All the other options should work as expected. If you want to call a custom method on the
|
668
|
+
# group item. You can include the option:group_label_method
|
669
|
+
# Examples:
|
670
|
+
#
|
671
|
+
# f.input :author, :group_by => :continents, :group_label_method => :something_different
|
672
|
+
#
|
673
|
+
def select_input(method, options)
|
674
|
+
html_options = options.delete(:input_html) || {}
|
675
|
+
options = set_include_blank(options)
|
676
|
+
html_options[:multiple] = options.delete(:multiple) if html_options[:multiple].nil?
|
677
|
+
|
678
|
+
reflection = self.reflection_for(method)
|
679
|
+
if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
|
680
|
+
options[:include_blank] = false
|
681
|
+
html_options[:multiple] = true if html_options[:multiple].nil?
|
682
|
+
html_options[:size] ||= 5
|
683
|
+
end
|
684
|
+
options[:selected] = options[:selected].first if options[:selected].present? && html_options[:multiple] == false
|
685
|
+
input_name = generate_association_input_name(method)
|
686
|
+
|
687
|
+
select_html = if options[:group_by]
|
688
|
+
# The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
|
689
|
+
# The formtastic user however shouldn't notice this too much.
|
690
|
+
raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
|
691
|
+
label, value = detect_label_and_value_method!(raw_collection)
|
692
|
+
group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
|
693
|
+
group_label_method = options[:group_label_method] || detect_label_method(group_collection)
|
694
|
+
group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
|
695
|
+
group_association = options[:group_association] || detect_group_association(method, options[:group_by])
|
696
|
+
|
697
|
+
# Here comes the monster with 8 arguments
|
698
|
+
self.grouped_collection_select(input_name, group_collection,
|
699
|
+
group_association, group_label_method,
|
700
|
+
value, label,
|
701
|
+
strip_formtastic_options(options), html_options)
|
702
|
+
else
|
703
|
+
collection = find_collection_for_column(method, options)
|
704
|
+
self.select(input_name, collection, strip_formtastic_options(options), html_options)
|
705
|
+
end
|
706
|
+
|
707
|
+
self.label(method, options_for_label(options).merge(:input_name => input_name)) << select_html
|
708
|
+
end
|
709
|
+
alias :boolean_select_input :select_input
|
710
|
+
|
711
|
+
# Outputs a timezone select input as Rails' time_zone_select helper. You
|
712
|
+
# can give priority zones as option.
|
713
|
+
#
|
714
|
+
# Examples:
|
715
|
+
#
|
716
|
+
# f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
|
717
|
+
#
|
718
|
+
# You can pre-select a specific option value by passing in the :selected option.
|
719
|
+
# Note: Right now only works if the form object attribute value is not set (nil),
|
720
|
+
# because of how the core helper is implemented.
|
721
|
+
#
|
722
|
+
# Examples:
|
723
|
+
#
|
724
|
+
# f.input :my_favorite_time_zone, :as => :time_zone, :selected => 'Singapore'
|
725
|
+
#
|
726
|
+
def time_zone_input(method, options)
|
727
|
+
html_options = options.delete(:input_html) || {}
|
728
|
+
selected_value = options.delete(:selected)
|
729
|
+
|
730
|
+
self.label(method, options_for_label(options)) <<
|
731
|
+
self.time_zone_select(method, options.delete(:priority_zones),
|
732
|
+
strip_formtastic_options(options).merge(:default => selected_value), html_options)
|
700
733
|
end
|
701
|
-
|
702
|
-
self.label(method, options_for_label(options).merge(:input_name => input_name)) << select_html
|
703
|
-
end
|
704
|
-
alias :boolean_select_input :select_input
|
705
734
|
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
735
|
+
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
736
|
+
# items, one for each possible choice in the belongs_to association. Each li contains a
|
737
|
+
# label and a radio input.
|
738
|
+
#
|
739
|
+
# Example:
|
740
|
+
#
|
741
|
+
# f.input :author, :as => :radio
|
742
|
+
#
|
743
|
+
# Output:
|
744
|
+
#
|
745
|
+
# <fieldset>
|
746
|
+
# <legend><span>Author</span></legend>
|
747
|
+
# <ol>
|
748
|
+
# <li>
|
749
|
+
# <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
|
750
|
+
# </li>
|
751
|
+
# <li>
|
752
|
+
# <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
|
753
|
+
# </li>
|
754
|
+
# </ol>
|
755
|
+
# </fieldset>
|
756
|
+
#
|
757
|
+
# You can customize the choices available in the radio button set by passing in a collection (an Array or
|
758
|
+
# Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
|
759
|
+
# (Author.find(:all) in the example above).
|
760
|
+
#
|
761
|
+
# Examples:
|
762
|
+
#
|
763
|
+
# f.input :author, :as => :radio, :collection => @authors
|
764
|
+
# f.input :author, :as => :radio, :collection => Author.find(:all)
|
765
|
+
# f.input :author, :as => :radio, :collection => [@justin, @kate]
|
766
|
+
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
767
|
+
#
|
768
|
+
# The :label_method option allows you to customize the label for each radio button two ways:
|
769
|
+
#
|
770
|
+
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
771
|
+
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
772
|
+
#
|
773
|
+
# Examples:
|
774
|
+
#
|
775
|
+
# f.input :author, :as => :radio, :label_method => :full_name
|
776
|
+
# f.input :author, :as => :radio, :label_method => :login
|
777
|
+
# f.input :author, :as => :radio, :label_method => :full_name_with_post_count
|
778
|
+
# f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
779
|
+
#
|
780
|
+
# The :value_method option provides the same customization of the value attribute of each option tag.
|
781
|
+
#
|
782
|
+
# Examples:
|
783
|
+
#
|
784
|
+
# f.input :author, :as => :radio, :value_method => :full_name
|
785
|
+
# f.input :author, :as => :radio, :value_method => :login
|
786
|
+
# f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
|
787
|
+
#
|
788
|
+
# You can force a particular radio button in the collection to be checked with the :selected option.
|
789
|
+
#
|
790
|
+
# Examples:
|
791
|
+
#
|
792
|
+
# f.input :subscribe_to_newsletter, :as => :radio, :selected => true
|
793
|
+
# f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
|
794
|
+
#
|
795
|
+
# Finally, you can set :value_as_class => true if you want the li wrapper around each radio
|
796
|
+
# button / label combination to contain a class with the value of the radio button (useful for
|
797
|
+
# applying specific CSS or Javascript to a particular radio button).
|
798
|
+
#
|
799
|
+
def radio_input(method, options)
|
800
|
+
collection = find_collection_for_column(method, options)
|
801
|
+
html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})
|
802
|
+
|
803
|
+
input_name = generate_association_input_name(method)
|
804
|
+
value_as_class = options.delete(:value_as_class)
|
805
|
+
input_ids = []
|
806
|
+
selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
|
807
|
+
selected_value = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
|
808
|
+
|
809
|
+
list_item_content = collection.map do |c|
|
810
|
+
label = c.is_a?(Array) ? c.first : c
|
811
|
+
value = c.is_a?(Array) ? c.last : c
|
812
|
+
input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
813
|
+
input_ids << input_id
|
814
|
+
|
815
|
+
html_options[:checked] = selected_value == value if selected_option_is_present
|
816
|
+
|
817
|
+
li_content = template.content_tag(:label,
|
818
|
+
"#{self.radio_button(input_name, value, html_options)} #{label}",
|
819
|
+
:for => input_id
|
820
|
+
)
|
715
821
|
|
716
|
-
|
717
|
-
|
718
|
-
|
822
|
+
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
823
|
+
template.content_tag(:li, li_content, li_options)
|
824
|
+
end
|
719
825
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
#
|
742
|
-
# You can customize the choices available in the radio button set by passing in a collection (an Array or
|
743
|
-
# Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
|
744
|
-
# (Author.find(:all) in the example above).
|
745
|
-
#
|
746
|
-
# Examples:
|
747
|
-
#
|
748
|
-
# f.input :author, :as => :radio, :collection => @authors
|
749
|
-
# f.input :author, :as => :radio, :collection => Author.find(:all)
|
750
|
-
# f.input :author, :as => :radio, :collection => [@justin, @kate]
|
751
|
-
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
752
|
-
#
|
753
|
-
# The :label_method option allows you to customize the label for each radio button two ways:
|
754
|
-
#
|
755
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
756
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
757
|
-
#
|
758
|
-
# Examples:
|
759
|
-
#
|
760
|
-
# f.input :author, :as => :radio, :label_method => :full_name
|
761
|
-
# f.input :author, :as => :radio, :label_method => :login
|
762
|
-
# f.input :author, :as => :radio, :label_method => :full_name_with_post_count
|
763
|
-
# f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
764
|
-
#
|
765
|
-
# The :value_method option provides the same customization of the value attribute of each option tag.
|
766
|
-
#
|
767
|
-
# Examples:
|
768
|
-
#
|
769
|
-
# f.input :author, :as => :radio, :value_method => :full_name
|
770
|
-
# f.input :author, :as => :radio, :value_method => :login
|
771
|
-
# f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
|
772
|
-
#
|
773
|
-
# You can force a particular radio button in the collection to be checked with the :selected option. Example:
|
774
|
-
#
|
775
|
-
# f.input :subscribe_to_newsletter, :as => :radio, :selected => true
|
776
|
-
# f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
|
777
|
-
#
|
778
|
-
# Finally, you can set :value_as_class => true if you want the li wrapper around each radio
|
779
|
-
# button / label combination to contain a class with the value of the radio button (useful for
|
780
|
-
# applying specific CSS or Javascript to a particular radio button).
|
781
|
-
def radio_input(method, options)
|
782
|
-
collection = find_collection_for_column(method, options)
|
783
|
-
html_options = set_options(options).merge(options.delete(:input_html) || {})
|
784
|
-
|
785
|
-
input_name = generate_association_input_name(method)
|
786
|
-
value_as_class = options.delete(:value_as_class)
|
787
|
-
input_ids = []
|
788
|
-
|
789
|
-
list_item_content = collection.map do |c|
|
790
|
-
label = c.is_a?(Array) ? c.first : c
|
791
|
-
value = c.is_a?(Array) ? c.last : c
|
792
|
-
html_options[:checked] = options.delete(:selected) unless options[:selected].blank?
|
793
|
-
|
794
|
-
input_ids << input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
795
|
-
li_content = template.content_tag(:label,
|
796
|
-
"#{self.radio_button(input_name, value, html_options)} #{label}",
|
797
|
-
:for => input_id
|
798
|
-
)
|
826
|
+
field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_item_content)
|
827
|
+
end
|
828
|
+
alias :boolean_radio_input :radio_input
|
829
|
+
|
830
|
+
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
831
|
+
# items (li), one for each fragment for the date (year, month, day). Each li contains a label
|
832
|
+
# (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
|
833
|
+
#
|
834
|
+
# You can pre-select a specific option value by passing in the :selected option.
|
835
|
+
#
|
836
|
+
# Examples:
|
837
|
+
#
|
838
|
+
# f.input :created_at, :as => :date, :selected => 1.day.ago
|
839
|
+
# f.input :created_at, :as => :date, :selected => nil # override any defaults: select none
|
840
|
+
#
|
841
|
+
# Some of Rails' options for select_date are supported, but not everything yet.
|
842
|
+
#
|
843
|
+
def date_input(method, options)
|
844
|
+
options = set_include_blank(options)
|
845
|
+
date_or_datetime_input(method, options.merge(:discard_hour => true))
|
846
|
+
end
|
799
847
|
|
800
|
-
|
801
|
-
|
848
|
+
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
849
|
+
# items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
|
850
|
+
# contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
|
851
|
+
# detailed output example.
|
852
|
+
#
|
853
|
+
# You can pre-select a specific option value by passing in the :selected option.
|
854
|
+
#
|
855
|
+
# Examples:
|
856
|
+
#
|
857
|
+
# f.input :created_at, :as => :datetime, :selected => 1.day.ago
|
858
|
+
# f.input :created_at, :as => :datetime, :selected => nil # override any defaults: select none
|
859
|
+
#
|
860
|
+
# Some of Rails' options for select_date are supported, but not everything yet.
|
861
|
+
#
|
862
|
+
def datetime_input(method, options)
|
863
|
+
options = set_include_blank(options)
|
864
|
+
date_or_datetime_input(method, options)
|
802
865
|
end
|
803
866
|
|
804
|
-
|
805
|
-
|
806
|
-
|
867
|
+
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
868
|
+
# items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
|
869
|
+
# (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
|
870
|
+
#
|
871
|
+
# You can pre-select a specific option value by passing in the :selected option.
|
872
|
+
#
|
873
|
+
# Examples:
|
874
|
+
#
|
875
|
+
# f.input :created_at, :as => :time, :selected => 1.hour.ago
|
876
|
+
# f.input :created_at, :as => :time, :selected => nil # override any defaults: select none
|
877
|
+
#
|
878
|
+
# Some of Rails' options for select_time are supported, but not everything yet.
|
879
|
+
#
|
880
|
+
def time_input(method, options)
|
881
|
+
options = set_include_blank(options)
|
882
|
+
date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
|
883
|
+
end
|
807
884
|
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
885
|
+
# <fieldset>
|
886
|
+
# <legend>Created At</legend>
|
887
|
+
# <ol>
|
888
|
+
# <li>
|
889
|
+
# <label for="user_created_at_1i">Year</label>
|
890
|
+
# <select id="user_created_at_1i" name="user[created_at(1i)]">
|
891
|
+
# <option value="2003">2003</option>
|
892
|
+
# ...
|
893
|
+
# <option value="2013">2013</option>
|
894
|
+
# </select>
|
895
|
+
# </li>
|
896
|
+
# <li>
|
897
|
+
# <label for="user_created_at_2i">Month</label>
|
898
|
+
# <select id="user_created_at_2i" name="user[created_at(2i)]">
|
899
|
+
# <option value="1">January</option>
|
900
|
+
# ...
|
901
|
+
# <option value="12">December</option>
|
902
|
+
# </select>
|
903
|
+
# </li>
|
904
|
+
# <li>
|
905
|
+
# <label for="user_created_at_3i">Day</label>
|
906
|
+
# <select id="user_created_at_3i" name="user[created_at(3i)]">
|
907
|
+
# <option value="1">1</option>
|
908
|
+
# ...
|
909
|
+
# <option value="31">31</option>
|
910
|
+
# </select>
|
911
|
+
# </li>
|
912
|
+
# </ol>
|
913
|
+
# </fieldset>
|
914
|
+
#
|
915
|
+
# This is an absolute abomination, but so is the official Rails select_date().
|
916
|
+
#
|
917
|
+
def date_or_datetime_input(method, options)
|
918
|
+
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
919
|
+
i18n_date_order = ::I18n.t(:order, :scope => [:date])
|
920
|
+
i18n_date_order = nil unless i18n_date_order.is_a?(Array)
|
921
|
+
inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
|
922
|
+
|
923
|
+
time_inputs = [:hour, :minute]
|
924
|
+
time_inputs << [:second] if options[:include_seconds]
|
925
|
+
|
926
|
+
list_items_capture = ""
|
927
|
+
hidden_fields_capture = ""
|
928
|
+
|
929
|
+
default_time = ::Time.now
|
930
|
+
|
931
|
+
# Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
|
932
|
+
datetime = options[:selected] || (@object ? @object.send(method) : default_time) || default_time
|
933
|
+
|
934
|
+
html_options = options.delete(:input_html) || {}
|
935
|
+
input_ids = []
|
817
936
|
|
818
|
-
|
819
|
-
|
820
|
-
# contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
|
821
|
-
# detailed output example.
|
822
|
-
#
|
823
|
-
# Some of Rails' options for select_date are supported, but not everything yet.
|
824
|
-
def datetime_input(method, options)
|
825
|
-
options = set_include_blank(options)
|
826
|
-
date_or_datetime_input(method, options)
|
827
|
-
end
|
937
|
+
(inputs + time_inputs).each do |input|
|
938
|
+
input_ids << input_id = generate_html_id(method, "#{position[input]}i")
|
828
939
|
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
#
|
833
|
-
# Some of Rails' options for select_time are supported, but not everything yet.
|
834
|
-
def time_input(method, options)
|
835
|
-
options = set_include_blank(options)
|
836
|
-
date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
|
837
|
-
end
|
940
|
+
field_name = "#{method}(#{position[input]}i)"
|
941
|
+
if options[:"discard_#{input}"]
|
942
|
+
break if time_inputs.include?(input)
|
838
943
|
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
# <select id="user_created_at_1i" name="user[created_at(1i)]">
|
845
|
-
# <option value="2003">2003</option>
|
846
|
-
# ...
|
847
|
-
# <option value="2013">2013</option>
|
848
|
-
# </select>
|
849
|
-
# </li>
|
850
|
-
# <li>
|
851
|
-
# <label for="user_created_at_2i">Month</label>
|
852
|
-
# <select id="user_created_at_2i" name="user[created_at(2i)]">
|
853
|
-
# <option value="1">January</option>
|
854
|
-
# ...
|
855
|
-
# <option value="12">December</option>
|
856
|
-
# </select>
|
857
|
-
# </li>
|
858
|
-
# <li>
|
859
|
-
# <label for="user_created_at_3i">Day</label>
|
860
|
-
# <select id="user_created_at_3i" name="user[created_at(3i)]">
|
861
|
-
# <option value="1">1</option>
|
862
|
-
# ...
|
863
|
-
# <option value="31">31</option>
|
864
|
-
# </select>
|
865
|
-
# </li>
|
866
|
-
# </ol>
|
867
|
-
# </fieldset>
|
868
|
-
#
|
869
|
-
# This is an absolute abomination, but so is the official Rails select_date().
|
870
|
-
#
|
871
|
-
def date_or_datetime_input(method, options)
|
872
|
-
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
873
|
-
i18n_date_order = ::I18n.t(:order, :scope => [:date])
|
874
|
-
i18n_date_order = nil unless i18n_date_order.is_a?(Array)
|
875
|
-
inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
|
944
|
+
hidden_value = datetime.respond_to?(input) ? datetime.send(input.to_sym) : datetime
|
945
|
+
hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
|
946
|
+
else
|
947
|
+
opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
|
948
|
+
item_label_text = ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
|
876
949
|
|
877
|
-
|
878
|
-
|
950
|
+
list_items_capture << template.content_tag(:li,
|
951
|
+
template.content_tag(:label, item_label_text, :for => input_id) <<
|
952
|
+
template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
|
953
|
+
)
|
954
|
+
end
|
955
|
+
end
|
879
956
|
|
880
|
-
|
881
|
-
|
957
|
+
hidden_fields_capture << field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_items_capture)
|
958
|
+
end
|
882
959
|
|
883
|
-
#
|
884
|
-
|
885
|
-
|
886
|
-
|
960
|
+
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
961
|
+
# items, one for each possible choice in the belongs_to association. Each li contains a
|
962
|
+
# label and a check_box input.
|
963
|
+
#
|
964
|
+
# This is an alternative for has many and has and belongs to many associations.
|
965
|
+
#
|
966
|
+
# Example:
|
967
|
+
#
|
968
|
+
# f.input :author, :as => :check_boxes
|
969
|
+
#
|
970
|
+
# Output:
|
971
|
+
#
|
972
|
+
# <fieldset>
|
973
|
+
# <legend class="label"><label>Authors</label></legend>
|
974
|
+
# <ol>
|
975
|
+
# <li>
|
976
|
+
# <input type="hidden" name="book[author_id][1]" value="">
|
977
|
+
# <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
|
978
|
+
# </li>
|
979
|
+
# <li>
|
980
|
+
# <input type="hidden" name="book[author_id][2]" value="">
|
981
|
+
# <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
|
982
|
+
# </li>
|
983
|
+
# </ol>
|
984
|
+
# </fieldset>
|
985
|
+
#
|
986
|
+
# Notice that the value of the checkbox is the same as the id and the hidden
|
987
|
+
# field has empty value. You can override the hidden field value using the
|
988
|
+
# unchecked_value option.
|
989
|
+
#
|
990
|
+
# You can customize the options available in the set by passing in a collection (Array) of
|
991
|
+
# ActiveRecord objects through the :collection option. If not provided, the choices are found
|
992
|
+
# by inferring the parent's class name from the method name and simply calling find(:all) on
|
993
|
+
# it (Author.find(:all) in the example above).
|
994
|
+
#
|
995
|
+
# Examples:
|
996
|
+
#
|
997
|
+
# f.input :author, :as => :check_boxes, :collection => @authors
|
998
|
+
# f.input :author, :as => :check_boxes, :collection => Author.find(:all)
|
999
|
+
# f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
|
1000
|
+
#
|
1001
|
+
# The :label_method option allows you to customize the label for each checkbox two ways:
|
1002
|
+
#
|
1003
|
+
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
1004
|
+
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
1005
|
+
#
|
1006
|
+
# Examples:
|
1007
|
+
#
|
1008
|
+
# f.input :author, :as => :check_boxes, :label_method => :full_name
|
1009
|
+
# f.input :author, :as => :check_boxes, :label_method => :login
|
1010
|
+
# f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
|
1011
|
+
# f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
1012
|
+
#
|
1013
|
+
# The :value_method option provides the same customization of the value attribute of each checkbox input tag.
|
1014
|
+
#
|
1015
|
+
# Examples:
|
1016
|
+
#
|
1017
|
+
# f.input :author, :as => :check_boxes, :value_method => :full_name
|
1018
|
+
# f.input :author, :as => :check_boxes, :value_method => :login
|
1019
|
+
# f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
|
1020
|
+
#
|
1021
|
+
# You can pre-select/check a specific checkbox value by passing in the :selected option (alias :checked works as well).
|
1022
|
+
#
|
1023
|
+
# Examples:
|
1024
|
+
#
|
1025
|
+
# f.input :authors, :as => :check_boxes, :selected => @justin
|
1026
|
+
# f.input :authors, :as => :check_boxes, :selected => Author.most_popular.collect(&:id)
|
1027
|
+
# f.input :authors, :as => :check_boxes, :selected => nil # override any defaults: select none
|
1028
|
+
#
|
1029
|
+
# Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
|
1030
|
+
# combination to contain a class with the value of the radio button (useful for applying specific
|
1031
|
+
# CSS or Javascript to a particular checkbox).
|
1032
|
+
#
|
1033
|
+
def check_boxes_input(method, options)
|
1034
|
+
collection = find_collection_for_column(method, options)
|
1035
|
+
html_options = options.delete(:input_html) || {}
|
887
1036
|
|
888
|
-
(
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
break if time_inputs.include?(input)
|
894
|
-
|
895
|
-
hidden_value = datetime.respond_to?(input) ? datetime.send(input.to_sym) : datetime
|
896
|
-
hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
|
897
|
-
else
|
898
|
-
opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
|
899
|
-
item_label_text = ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
|
1037
|
+
input_name = generate_association_input_name(method)
|
1038
|
+
value_as_class = options.delete(:value_as_class)
|
1039
|
+
unchecked_value = options.delete(:unchecked_value) || ''
|
1040
|
+
html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
|
1041
|
+
input_ids = []
|
900
1042
|
|
901
|
-
|
902
|
-
|
903
|
-
|
1043
|
+
selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
|
1044
|
+
selected_values = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
|
1045
|
+
selected_values = [*selected_values].compact
|
1046
|
+
|
1047
|
+
list_item_content = collection.map do |c|
|
1048
|
+
label = c.is_a?(Array) ? c.first : c
|
1049
|
+
value = c.is_a?(Array) ? c.last : c
|
1050
|
+
input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
1051
|
+
input_ids << input_id
|
1052
|
+
|
1053
|
+
html_options[:checked] = selected_values.include?(value) if selected_option_is_present
|
1054
|
+
html_options[:id] = input_id
|
1055
|
+
|
1056
|
+
li_content = template.content_tag(:label,
|
1057
|
+
"#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
|
1058
|
+
:for => input_id
|
904
1059
|
)
|
905
|
-
end
|
906
|
-
end
|
907
1060
|
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
912
|
-
# items, one for each possible choice in the belongs_to association. Each li contains a
|
913
|
-
# label and a check_box input.
|
914
|
-
#
|
915
|
-
# This is an alternative for has many and has and belongs to many associations.
|
916
|
-
#
|
917
|
-
# Example:
|
918
|
-
#
|
919
|
-
# f.input :author, :as => :check_boxes
|
920
|
-
#
|
921
|
-
# Output:
|
922
|
-
#
|
923
|
-
# <fieldset>
|
924
|
-
# <legend class="label"><label>Authors</label></legend>
|
925
|
-
# <ol>
|
926
|
-
# <li>
|
927
|
-
# <input type="hidden" name="book[author_id][1]" value="">
|
928
|
-
# <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
|
929
|
-
# </li>
|
930
|
-
# <li>
|
931
|
-
# <input type="hidden" name="book[author_id][2]" value="">
|
932
|
-
# <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
|
933
|
-
# </li>
|
934
|
-
# </ol>
|
935
|
-
# </fieldset>
|
936
|
-
#
|
937
|
-
# Notice that the value of the checkbox is the same as the id and the hidden
|
938
|
-
# field has empty value. You can override the hidden field value using the
|
939
|
-
# unchecked_value option.
|
940
|
-
#
|
941
|
-
# You can customize the options available in the set by passing in a collection (Array) of
|
942
|
-
# ActiveRecord objects through the :collection option. If not provided, the choices are found
|
943
|
-
# by inferring the parent's class name from the method name and simply calling find(:all) on
|
944
|
-
# it (Author.find(:all) in the example above).
|
945
|
-
#
|
946
|
-
# Examples:
|
947
|
-
#
|
948
|
-
# f.input :author, :as => :check_boxes, :collection => @authors
|
949
|
-
# f.input :author, :as => :check_boxes, :collection => Author.find(:all)
|
950
|
-
# f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
|
951
|
-
#
|
952
|
-
# The :label_method option allows you to customize the label for each checkbox two ways:
|
953
|
-
#
|
954
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
955
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
956
|
-
#
|
957
|
-
# Examples:
|
958
|
-
#
|
959
|
-
# f.input :author, :as => :check_boxes, :label_method => :full_name
|
960
|
-
# f.input :author, :as => :check_boxes, :label_method => :login
|
961
|
-
# f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
|
962
|
-
# f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
963
|
-
#
|
964
|
-
# The :value_method option provides the same customization of the value attribute of each checkbox input tag.
|
965
|
-
#
|
966
|
-
# Examples:
|
967
|
-
#
|
968
|
-
# f.input :author, :as => :check_boxes, :value_method => :full_name
|
969
|
-
# f.input :author, :as => :check_boxes, :value_method => :login
|
970
|
-
# f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
|
971
|
-
#
|
972
|
-
# Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
|
973
|
-
# combination to contain a class with the value of the radio button (useful for applying specific
|
974
|
-
# CSS or Javascript to a particular checkbox).
|
975
|
-
def check_boxes_input(method, options)
|
976
|
-
collection = find_collection_for_column(method, options)
|
977
|
-
html_options = options.delete(:input_html) || {}
|
978
|
-
|
979
|
-
input_name = generate_association_input_name(method)
|
980
|
-
value_as_class = options.delete(:value_as_class)
|
981
|
-
unchecked_value = options.delete(:unchecked_value) || ''
|
982
|
-
html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
|
983
|
-
input_ids = []
|
984
|
-
|
985
|
-
list_item_content = collection.map do |c|
|
986
|
-
label = c.is_a?(Array) ? c.first : c
|
987
|
-
value = c.is_a?(Array) ? c.last : c
|
988
|
-
|
989
|
-
input_ids << input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
990
|
-
html_options.merge!(:id => input_id)
|
991
|
-
|
992
|
-
li_content = template.content_tag(:label,
|
993
|
-
"#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
|
994
|
-
:for => input_id
|
995
|
-
)
|
1061
|
+
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
1062
|
+
template.content_tag(:li, li_content, li_options)
|
1063
|
+
end
|
996
1064
|
|
997
|
-
|
998
|
-
template.content_tag(:li, li_content, li_options)
|
1065
|
+
field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_item_content)
|
999
1066
|
end
|
1000
1067
|
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
#
|
1018
|
-
def country_input(method, options)
|
1019
|
-
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)
|
1068
|
+
# Outputs a country select input, wrapping around a regular country_select helper.
|
1069
|
+
# Rails doesn't come with a country_select helper by default any more, so you'll need to install
|
1070
|
+
# the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
|
1071
|
+
# same way.
|
1072
|
+
#
|
1073
|
+
# The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
|
1074
|
+
#
|
1075
|
+
# By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
|
1076
|
+
# which you can change to suit your market and user base (see README for more info on config).
|
1077
|
+
#
|
1078
|
+
# Examples:
|
1079
|
+
# f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
|
1080
|
+
# f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
|
1081
|
+
#
|
1082
|
+
def country_input(method, options)
|
1083
|
+
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)
|
1020
1084
|
|
1021
|
-
|
1022
|
-
|
1085
|
+
html_options = options.delete(:input_html) || {}
|
1086
|
+
priority_countries = options.delete(:priority_countries) || @@priority_countries
|
1023
1087
|
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1088
|
+
self.label(method, options_for_label(options)) <<
|
1089
|
+
self.country_select(method, priority_countries, strip_formtastic_options(options), html_options)
|
1090
|
+
end
|
1027
1091
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1092
|
+
# Outputs a label containing a checkbox and the label text. The label defaults
|
1093
|
+
# to the column name (method name) and can be altered with the :label option.
|
1094
|
+
# :checked_value and :unchecked_value options are also available.
|
1095
|
+
#
|
1096
|
+
# You can pre-select/check the boolean checkbox by passing in the :selected option (alias :checked works as well).
|
1097
|
+
#
|
1098
|
+
# Examples:
|
1099
|
+
#
|
1100
|
+
# f.input :allow_comments, :as => :boolean, :selected => true # override any default value: selected/checked
|
1101
|
+
#
|
1102
|
+
def boolean_input(method, options)
|
1103
|
+
html_options = options.delete(:input_html) || {}
|
1104
|
+
checked = options.key?(:checked) ? options[:checked] : options[:selected]
|
1105
|
+
html_options[:checked] = checked == true if [:selected, :checked].any? { |k| options.key?(k) }
|
1106
|
+
|
1107
|
+
input = self.check_box(method, strip_formtastic_options(options).merge(html_options),
|
1108
|
+
options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
|
1109
|
+
options = options_for_label(options)
|
1110
|
+
|
1111
|
+
# the label() method will insert this nested input into the label at the last minute
|
1112
|
+
options[:label_prefix_for_nested_input] = input
|
1113
|
+
|
1114
|
+
self.label(method, options)
|
1115
|
+
end
|
1034
1116
|
|
1035
|
-
input
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
# the label() method will insert this nested input into the label at the last minute
|
1040
|
-
options[:label_prefix_for_nested_input] = input
|
1041
|
-
|
1042
|
-
self.label(method, options)
|
1043
|
-
end
|
1117
|
+
# Generates an input for the given method using the type supplied with :as.
|
1118
|
+
def inline_input_for(method, options)
|
1119
|
+
send(:"#{options.delete(:as)}_input", method, options)
|
1120
|
+
end
|
1044
1121
|
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1122
|
+
# Generates hints for the given method using the text supplied in :hint.
|
1123
|
+
#
|
1124
|
+
def inline_hints_for(method, options) #:nodoc:
|
1125
|
+
options[:hint] = localized_string(method, options[:hint], :hint)
|
1126
|
+
return if options[:hint].blank?
|
1127
|
+
template.content_tag(:p, options[:hint], :class => 'inline-hints')
|
1128
|
+
end
|
1049
1129
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
template.content_tag(:p, options[:hint], :class => 'inline-hints')
|
1056
|
-
end
|
1130
|
+
# Creates an error sentence by calling to_sentence on the errors array.
|
1131
|
+
#
|
1132
|
+
def error_sentence(errors) #:nodoc:
|
1133
|
+
template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
|
1134
|
+
end
|
1057
1135
|
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1136
|
+
# Creates an error li list.
|
1137
|
+
#
|
1138
|
+
def error_list(errors) #:nodoc:
|
1139
|
+
list_elements = []
|
1140
|
+
errors.each do |error|
|
1141
|
+
list_elements << template.content_tag(:li, error.untaint)
|
1142
|
+
end
|
1143
|
+
template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
|
1144
|
+
end
|
1063
1145
|
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
errors.each do |error|
|
1069
|
-
list_elements << template.content_tag(:li, error.untaint)
|
1146
|
+
# Creates an error sentence containing only the first error
|
1147
|
+
#
|
1148
|
+
def error_first(errors) #:nodoc:
|
1149
|
+
template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
|
1070
1150
|
end
|
1071
|
-
template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
|
1072
|
-
end
|
1073
1151
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1152
|
+
# Generates the required or optional string. If the value set is a proc,
|
1153
|
+
# it evaluates the proc first.
|
1154
|
+
#
|
1155
|
+
def required_or_optional_string(required) #:nodoc:
|
1156
|
+
string_or_proc = case required
|
1157
|
+
when true
|
1158
|
+
@@required_string
|
1159
|
+
when false
|
1160
|
+
@@optional_string
|
1161
|
+
else
|
1162
|
+
required
|
1163
|
+
end
|
1079
1164
|
|
1080
|
-
|
1081
|
-
|
1082
|
-
#
|
1083
|
-
def required_or_optional_string(required) #:nodoc:
|
1084
|
-
string_or_proc = case required
|
1085
|
-
when true
|
1086
|
-
@@required_string
|
1087
|
-
when false
|
1088
|
-
@@optional_string
|
1165
|
+
if string_or_proc.is_a?(Proc)
|
1166
|
+
string_or_proc.call
|
1089
1167
|
else
|
1090
|
-
|
1168
|
+
string_or_proc.to_s
|
1169
|
+
end
|
1091
1170
|
end
|
1092
1171
|
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1172
|
+
# Generates a fieldset and wraps the content in an ordered list. When working
|
1173
|
+
# with nested attributes (in Rails 2.3), it allows %i as interpolation option
|
1174
|
+
# in :name. So you can do:
|
1175
|
+
#
|
1176
|
+
# f.inputs :name => 'Task #%i', :for => :tasks
|
1177
|
+
#
|
1178
|
+
# or the shorter equivalent:
|
1179
|
+
#
|
1180
|
+
# f.inputs 'Task #%i', :for => :tasks
|
1181
|
+
#
|
1182
|
+
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
|
1183
|
+
# 'Task #3' and so on.
|
1184
|
+
#
|
1185
|
+
# Note: Special case for the inline inputs (non-block):
|
1186
|
+
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
1187
|
+
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
1188
|
+
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
1189
|
+
#
|
1190
|
+
def field_set_and_list_wrapping(*args, &block) #:nodoc:
|
1191
|
+
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
|
1192
|
+
html_options = args.extract_options!
|
1193
|
+
|
1194
|
+
legend = html_options.delete(:name).to_s
|
1195
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
|
1196
|
+
legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
|
1197
|
+
|
1198
|
+
if block_given?
|
1199
|
+
contents = if template.respond_to?(:is_haml?) && template.is_haml?
|
1200
|
+
template.capture_haml(&block)
|
1201
|
+
else
|
1202
|
+
template.capture(&block)
|
1203
|
+
end
|
1204
|
+
end
|
1099
1205
|
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
# or the shorter equivalent:
|
1107
|
-
#
|
1108
|
-
# f.inputs 'Task #%i', :for => :tasks
|
1109
|
-
#
|
1110
|
-
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
|
1111
|
-
# 'Task #3' and so on.
|
1112
|
-
#
|
1113
|
-
# Note: Special case for the inline inputs (non-block):
|
1114
|
-
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
1115
|
-
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
1116
|
-
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
1117
|
-
#
|
1118
|
-
def field_set_and_list_wrapping(*args, &block) #:nodoc:
|
1119
|
-
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
|
1120
|
-
html_options = args.extract_options!
|
1206
|
+
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
1207
|
+
contents = contents.join if contents.respond_to?(:join)
|
1208
|
+
fieldset = template.content_tag(:fieldset,
|
1209
|
+
legend << template.content_tag(:ol, contents),
|
1210
|
+
html_options.except(:builder, :parent)
|
1211
|
+
)
|
1121
1212
|
|
1122
|
-
|
1123
|
-
|
1124
|
-
valid_name_classes = [::String, ::Symbol]
|
1125
|
-
valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && self.content_columns.include?(args.first))
|
1126
|
-
html_options[:name] = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
|
1213
|
+
template.concat(fieldset) if block_given?
|
1214
|
+
fieldset
|
1127
1215
|
end
|
1128
|
-
html_options[:name] = localized_string(html_options[:name], html_options[:name], :title) if html_options[:name].is_a?(::Symbol)
|
1129
1216
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1217
|
+
def field_set_title_from_args(*args) #:nodoc:
|
1218
|
+
options = args.extract_options!
|
1219
|
+
options[:name] ||= options.delete(:title)
|
1220
|
+
title = options[:name]
|
1133
1221
|
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
template.capture(&block)
|
1222
|
+
if title.blank?
|
1223
|
+
valid_name_classes = [::String, ::Symbol]
|
1224
|
+
valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && self.content_columns.include?(args.first))
|
1225
|
+
title = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
|
1139
1226
|
end
|
1227
|
+
title = localized_string(title, title, :title) if title.is_a?(::Symbol)
|
1228
|
+
title
|
1140
1229
|
end
|
1141
1230
|
|
1142
|
-
#
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1231
|
+
# Also generates a fieldset and an ordered list but with label based in
|
1232
|
+
# method. This methods is currently used by radio and datetime inputs.
|
1233
|
+
#
|
1234
|
+
def field_set_and_list_wrapping_for_method(method, options, contents) #:nodoc:
|
1235
|
+
contents = contents.join if contents.respond_to?(:join)
|
1236
|
+
|
1237
|
+
template.content_tag(:fieldset,
|
1238
|
+
template.content_tag(:legend,
|
1239
|
+
self.label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
|
1240
|
+
) <<
|
1241
|
+
template.content_tag(:ol, contents)
|
1242
|
+
)
|
1243
|
+
end
|
1152
1244
|
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1245
|
+
# For methods that have a database column, take a best guess as to what the input method
|
1246
|
+
# should be. In most cases, it will just return the column type (eg :string), but for special
|
1247
|
+
# cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
|
1248
|
+
# something different (like :password and :select).
|
1249
|
+
#
|
1250
|
+
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
1251
|
+
# default is a :string, a similar behaviour to Rails' scaffolding.
|
1252
|
+
#
|
1253
|
+
def default_input_type(method, options = {}) #:nodoc:
|
1254
|
+
if column = self.column_for(method)
|
1255
|
+
# Special cases where the column type doesn't map to an input method.
|
1256
|
+
case column.type
|
1257
|
+
when :string
|
1258
|
+
return :password if method.to_s =~ /password/
|
1259
|
+
return :country if method.to_s =~ /country/
|
1260
|
+
return :time_zone if method.to_s =~ /time_zone/
|
1261
|
+
when :integer
|
1262
|
+
return :select if method.to_s =~ /_id$/
|
1263
|
+
return :numeric
|
1264
|
+
when :float, :decimal
|
1265
|
+
return :numeric
|
1266
|
+
when :timestamp
|
1267
|
+
return :datetime
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
1271
|
+
return :select if column.type == :string && options.key?(:collection)
|
1272
|
+
# Try 3: Assume the input name will be the same as the column type (e.g. string_input).
|
1273
|
+
return column.type
|
1274
|
+
else
|
1275
|
+
if @object
|
1276
|
+
return :select if self.reflection_for(method)
|
1166
1277
|
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
# something different (like :password and :select).
|
1171
|
-
#
|
1172
|
-
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
1173
|
-
# default is a :string, a similar behaviour to Rails' scaffolding.
|
1174
|
-
#
|
1175
|
-
def default_input_type(method) #:nodoc:
|
1176
|
-
column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
1177
|
-
|
1178
|
-
if column
|
1179
|
-
# handle the special cases where the column type doesn't map to an input method
|
1180
|
-
return :time_zone if column.type == :string && method.to_s =~ /time_zone/
|
1181
|
-
return :select if column.type == :integer && method.to_s =~ /_id$/
|
1182
|
-
return :datetime if column.type == :timestamp
|
1183
|
-
return :numeric if [:integer, :float, :decimal].include?(column.type)
|
1184
|
-
return :password if column.type == :string && method.to_s =~ /password/
|
1185
|
-
return :country if column.type == :string && method.to_s =~ /country/
|
1186
|
-
|
1187
|
-
# otherwise assume the input name will be the same as the column type (eg string_input)
|
1188
|
-
return column.type
|
1189
|
-
else
|
1190
|
-
if @object
|
1191
|
-
return :select if find_reflection(method)
|
1278
|
+
file = @object.send(method) if @object.respond_to?(method)
|
1279
|
+
return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
|
1280
|
+
end
|
1192
1281
|
|
1193
|
-
|
1194
|
-
return :
|
1282
|
+
return :select if options.key?(:collection)
|
1283
|
+
return :password if method.to_s =~ /password/
|
1284
|
+
return :string
|
1195
1285
|
end
|
1286
|
+
end
|
1196
1287
|
|
1197
|
-
|
1198
|
-
|
1288
|
+
# Used by select and radio inputs. The collection can be retrieved by
|
1289
|
+
# three ways:
|
1290
|
+
#
|
1291
|
+
# * Explicitly provided through :collection
|
1292
|
+
# * Retrivied through an association
|
1293
|
+
# * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
|
1294
|
+
#
|
1295
|
+
# If the collection is not a hash or an array of strings, fixnums or arrays,
|
1296
|
+
# we use label_method and value_method to retreive an array with the
|
1297
|
+
# appropriate label and value.
|
1298
|
+
#
|
1299
|
+
def find_collection_for_column(column, options) #:nodoc:
|
1300
|
+
collection = find_raw_collection_for_column(column, options)
|
1301
|
+
|
1302
|
+
# Return if we have an Array of strings, fixnums or arrays
|
1303
|
+
return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
|
1304
|
+
[Array, Fixnum, String, Symbol].include?(collection.first.class)
|
1305
|
+
|
1306
|
+
label, value = detect_label_and_value_method!(collection, options)
|
1307
|
+
collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
|
1199
1308
|
end
|
1200
|
-
end
|
1201
1309
|
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
#
|
1213
|
-
def find_collection_for_column(column, options)
|
1214
|
-
collection = find_raw_collection_for_column(column, options)
|
1310
|
+
# As #find_collection_for_column but returns the collection without mapping the label and value
|
1311
|
+
#
|
1312
|
+
def find_raw_collection_for_column(column, options) #:nodoc:
|
1313
|
+
collection = if options[:collection]
|
1314
|
+
options.delete(:collection)
|
1315
|
+
elsif reflection = self.reflection_for(column)
|
1316
|
+
reflection.klass.find(:all, options[:find_options] || {})
|
1317
|
+
else
|
1318
|
+
create_boolean_collection(options)
|
1319
|
+
end
|
1215
1320
|
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1321
|
+
collection = collection.to_a if collection.is_a?(Hash)
|
1322
|
+
collection
|
1323
|
+
end
|
1219
1324
|
|
1220
|
-
label
|
1325
|
+
# Detects the label and value methods from a collection values set in
|
1326
|
+
# @@collection_label_methods. It will use and delete
|
1327
|
+
# the options :label_method and :value_methods when present
|
1328
|
+
#
|
1329
|
+
def detect_label_and_value_method!(collection_or_instance, options = {}) #:nodoc
|
1330
|
+
label = options.delete(:label_method) || detect_label_method(collection_or_instance)
|
1331
|
+
value = options.delete(:value_method) || :id
|
1332
|
+
[label, value]
|
1333
|
+
end
|
1221
1334
|
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
reflection = find_reflection(column)
|
1335
|
+
# Detected the label collection method when none is supplied using the
|
1336
|
+
# values set in @@collection_label_methods.
|
1337
|
+
#
|
1338
|
+
def detect_label_method(collection) #:nodoc:
|
1339
|
+
@@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
|
1340
|
+
end
|
1229
1341
|
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1342
|
+
# Detects the method to call for fetching group members from the groups when grouping select options
|
1343
|
+
#
|
1344
|
+
def detect_group_association(method, group_by)
|
1345
|
+
object_to_method_reflection = self.reflection_for(method)
|
1346
|
+
method_class = object_to_method_reflection.klass
|
1347
|
+
|
1348
|
+
method_to_group_association = method_class.reflect_on_association(group_by)
|
1349
|
+
group_class = method_to_group_association.klass
|
1350
|
+
|
1351
|
+
# This will return in the normal case
|
1352
|
+
return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
|
1353
|
+
|
1354
|
+
# This is for belongs_to associations named differently than their class
|
1355
|
+
# form.input :parent, :group_by => :customer
|
1356
|
+
# eg.
|
1357
|
+
# class Project
|
1358
|
+
# belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
|
1359
|
+
# belongs_to :customer
|
1360
|
+
# end
|
1361
|
+
# class Customer
|
1362
|
+
# has_many :projects
|
1363
|
+
# end
|
1364
|
+
group_method = method_class.to_s.underscore.pluralize.to_sym
|
1365
|
+
return group_method if group_class.reflect_on_association(group_method) # :projects
|
1366
|
+
|
1367
|
+
# This is for has_many associations named differently than their class
|
1368
|
+
# eg.
|
1369
|
+
# class Project
|
1370
|
+
# belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
|
1371
|
+
# belongs_to :customer
|
1372
|
+
# end
|
1373
|
+
# class Customer
|
1374
|
+
# has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
|
1375
|
+
# end
|
1376
|
+
possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
|
1377
|
+
return possible_associations.first.name.to_sym if possible_associations.count == 1
|
1378
|
+
|
1379
|
+
raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
|
1380
|
+
|
1236
1381
|
end
|
1237
1382
|
|
1238
|
-
|
1383
|
+
# Returns a hash to be used by radio and select inputs when a boolean field
|
1384
|
+
# is provided.
|
1385
|
+
#
|
1386
|
+
def create_boolean_collection(options) #:nodoc:
|
1387
|
+
options[:true] ||= ::Formtastic::I18n.t(:yes)
|
1388
|
+
options[:false] ||= ::Formtastic::I18n.t(:no)
|
1389
|
+
options[:value_as_class] = true unless options.key?(:value_as_class)
|
1239
1390
|
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
# Detects the label and value methods from a collection values set in
|
1244
|
-
# @@collection_label_methods. It will use and delete
|
1245
|
-
# the options :label_method and :value_methods when present
|
1246
|
-
#
|
1247
|
-
def detect_label_and_value_method!(collection_or_instance, options = {}) #:nodoc
|
1248
|
-
label = options.delete(:label_method) || detect_label_method(collection_or_instance)
|
1249
|
-
value = options.delete(:value_method) || :id
|
1250
|
-
[label, value]
|
1251
|
-
end
|
1391
|
+
[ [ options.delete(:true), true], [ options.delete(:false), false ] ]
|
1392
|
+
end
|
1252
1393
|
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1394
|
+
# Used by association inputs (select, radio) to generate the name that should
|
1395
|
+
# be used for the input
|
1396
|
+
#
|
1397
|
+
# belongs_to :author; f.input :author; will generate 'author_id'
|
1398
|
+
# belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
|
1399
|
+
# has_many :authors; f.input :authors; will generate 'author_ids'
|
1400
|
+
# has_and_belongs_to_many will act like has_many
|
1401
|
+
#
|
1402
|
+
def generate_association_input_name(method) #:nodoc:
|
1403
|
+
if reflection = self.reflection_for(method)
|
1404
|
+
if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
|
1405
|
+
"#{method.to_s.singularize}_ids"
|
1406
|
+
else
|
1407
|
+
reflection.options[:foreign_key] || "#{method}_id"
|
1408
|
+
end
|
1409
|
+
else
|
1410
|
+
method
|
1411
|
+
end.to_sym
|
1412
|
+
end
|
1259
1413
|
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
options[:value_as_class] = true unless options.key?(:value_as_class)
|
1414
|
+
# If an association method is passed in (f.input :author) try to find the
|
1415
|
+
# reflection object.
|
1416
|
+
#
|
1417
|
+
def reflection_for(method) #:nodoc:
|
1418
|
+
@object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
|
1419
|
+
end
|
1267
1420
|
|
1268
|
-
|
1269
|
-
|
1421
|
+
# Get a column object for a specified attribute method - if possible.
|
1422
|
+
#
|
1423
|
+
def column_for(method) #:nodoc:
|
1424
|
+
@object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
1425
|
+
end
|
1270
1426
|
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
def generate_association_input_name(method)
|
1280
|
-
if reflection = find_reflection(method)
|
1281
|
-
if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
|
1282
|
-
"#{method.to_s.singularize}_ids"
|
1427
|
+
# Generates default_string_options by retrieving column information from
|
1428
|
+
# the database.
|
1429
|
+
#
|
1430
|
+
def default_string_options(method, type) #:nodoc:
|
1431
|
+
column = self.column_for(method)
|
1432
|
+
|
1433
|
+
if type == :numeric || column.nil? || column.limit.nil?
|
1434
|
+
{ :size => @@default_text_field_size }
|
1283
1435
|
else
|
1284
|
-
|
1436
|
+
{ :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
|
1285
1437
|
end
|
1286
|
-
|
1287
|
-
method
|
1288
|
-
end.to_sym
|
1289
|
-
end
|
1290
|
-
|
1291
|
-
# If an association method is passed in (f.input :author) try to find the
|
1292
|
-
# reflection object.
|
1293
|
-
#
|
1294
|
-
def find_reflection(method)
|
1295
|
-
@object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
|
1296
|
-
end
|
1438
|
+
end
|
1297
1439
|
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1440
|
+
# Generate the html id for the li tag.
|
1441
|
+
# It takes into account options[:index] and @auto_index to generate li
|
1442
|
+
# elements with appropriate index scope. It also sanitizes the object
|
1443
|
+
# and method names.
|
1444
|
+
#
|
1445
|
+
def generate_html_id(method_name, value='input') #:nodoc:
|
1446
|
+
if options.has_key?(:index)
|
1447
|
+
index = "_#{options[:index]}"
|
1448
|
+
elsif defined?(@auto_index)
|
1449
|
+
index = "_#{@auto_index}"
|
1450
|
+
else
|
1451
|
+
index = ""
|
1452
|
+
end
|
1453
|
+
sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
|
1303
1454
|
|
1304
|
-
|
1305
|
-
{ :size => @@default_text_field_size }
|
1306
|
-
else
|
1307
|
-
{ :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
|
1455
|
+
"#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
|
1308
1456
|
end
|
1309
|
-
end
|
1310
1457
|
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1458
|
+
# Gets the nested_child_index value from the parent builder. In Rails 2.3
|
1459
|
+
# it always returns a fixnum. In next versions it returns a hash with each
|
1460
|
+
# association that the parent builds.
|
1461
|
+
#
|
1462
|
+
def parent_child_index(parent) #:nodoc:
|
1463
|
+
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
1464
|
+
|
1465
|
+
if duck.is_a?(Hash)
|
1466
|
+
child = parent[:for]
|
1467
|
+
child = child.first if child.respond_to?(:first)
|
1468
|
+
duck[child].to_i + 1
|
1469
|
+
else
|
1470
|
+
duck.to_i + 1
|
1471
|
+
end
|
1323
1472
|
end
|
1324
|
-
sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
|
1325
|
-
|
1326
|
-
"#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
|
1327
|
-
end
|
1328
1473
|
|
1329
|
-
|
1330
|
-
|
1331
|
-
# association that the parent builds.
|
1332
|
-
#
|
1333
|
-
def parent_child_index(parent)
|
1334
|
-
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
1335
|
-
|
1336
|
-
if duck.is_a?(Hash)
|
1337
|
-
child = parent[:for]
|
1338
|
-
child = child.first if child.respond_to?(:first)
|
1339
|
-
duck[child].to_i + 1
|
1340
|
-
else
|
1341
|
-
duck.to_i + 1
|
1474
|
+
def sanitized_object_name #:nodoc:
|
1475
|
+
@sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
1342
1476
|
end
|
1343
|
-
end
|
1344
|
-
|
1345
|
-
def sanitized_object_name
|
1346
|
-
@sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
1347
|
-
end
|
1348
1477
|
|
1349
|
-
|
1350
|
-
if @object && @object.class.respond_to?(:human_attribute_name)
|
1351
|
-
@object.class.human_attribute_name(method.to_s)
|
1352
|
-
else
|
1478
|
+
def humanized_attribute_name(method) #:nodoc:
|
1353
1479
|
method.to_s.send(@@label_str_method)
|
1354
1480
|
end
|
1355
|
-
end
|
1356
|
-
|
1357
|
-
# Internal generic method for looking up localized values within Formtastic
|
1358
|
-
# using I18n, if no explicit value is set and I18n-lookups are enabled.
|
1359
|
-
#
|
1360
|
-
# Enabled/Disable this by setting:
|
1361
|
-
#
|
1362
|
-
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
|
1363
|
-
#
|
1364
|
-
# Lookup priority:
|
1365
|
-
#
|
1366
|
-
# 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
|
1367
|
-
# 'formtastic.{{type}}.{{model}}.{{attribute}}'
|
1368
|
-
# 'formtastic.{{type}}.{{attribute}}'
|
1369
|
-
#
|
1370
|
-
# Example:
|
1371
|
-
#
|
1372
|
-
# 'formtastic.labels.post.edit.title'
|
1373
|
-
# 'formtastic.labels.post.title'
|
1374
|
-
# 'formtastic.labels.title'
|
1375
|
-
#
|
1376
|
-
# NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
|
1377
|
-
#
|
1378
|
-
def localized_string(key, value, type, options = {})
|
1379
|
-
key = value if value.is_a?(::Symbol)
|
1380
1481
|
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1482
|
+
# Internal generic method for looking up localized values within Formtastic
|
1483
|
+
# using I18n, if no explicit value is set and I18n-lookups are enabled.
|
1484
|
+
#
|
1485
|
+
# Enabled/Disable this by setting:
|
1486
|
+
#
|
1487
|
+
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
|
1488
|
+
#
|
1489
|
+
# Lookup priority:
|
1490
|
+
#
|
1491
|
+
# 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
|
1492
|
+
# 'formtastic.{{type}}.{{model}}.{{attribute}}'
|
1493
|
+
# 'formtastic.{{type}}.{{attribute}}'
|
1494
|
+
#
|
1495
|
+
# Example:
|
1496
|
+
#
|
1497
|
+
# 'formtastic.labels.post.edit.title'
|
1498
|
+
# 'formtastic.labels.post.title'
|
1499
|
+
# 'formtastic.labels.title'
|
1500
|
+
#
|
1501
|
+
# NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
|
1502
|
+
#
|
1503
|
+
def localized_string(key, value, type, options = {}) #:nodoc:
|
1504
|
+
key = value if value.is_a?(::Symbol)
|
1505
|
+
|
1506
|
+
if value.is_a?(::String)
|
1507
|
+
value
|
1508
|
+
else
|
1509
|
+
use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
|
1510
|
+
|
1511
|
+
if use_i18n
|
1512
|
+
model_name = self.model_name.underscore
|
1513
|
+
action_name = template.params[:action].to_s rescue ''
|
1514
|
+
attribute_name = key.to_s
|
1515
|
+
|
1516
|
+
defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
|
1517
|
+
i18n_path = i18n_scope.dup
|
1518
|
+
i18n_path.gsub!('{{action}}', action_name)
|
1519
|
+
i18n_path.gsub!('{{model}}', model_name)
|
1520
|
+
i18n_path.gsub!('{{attribute}}', attribute_name)
|
1521
|
+
i18n_path.gsub!('..', '.')
|
1522
|
+
i18n_path.to_sym
|
1523
|
+
end
|
1524
|
+
defaults << ''
|
1525
|
+
|
1526
|
+
i18n_value = ::Formtastic::I18n.t(defaults.shift,
|
1527
|
+
options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
|
1528
|
+
i18n_value.blank? ? nil : i18n_value
|
1398
1529
|
end
|
1399
|
-
defaults << ''
|
1400
|
-
|
1401
|
-
i18n_value = ::Formtastic::I18n.t(defaults.shift,
|
1402
|
-
options.merge(:default => defaults,:scope => type.to_s.pluralize.to_sym))
|
1403
|
-
i18n_value.blank? ? nil : i18n_value
|
1404
1530
|
end
|
1405
1531
|
end
|
1406
|
-
end
|
1407
1532
|
|
1408
|
-
|
1409
|
-
|
1410
|
-
duck.call(object)
|
1411
|
-
else
|
1412
|
-
object.send(duck)
|
1533
|
+
def model_name
|
1534
|
+
@object.present? ? @object.class.name : @object_name.to_s.classify
|
1413
1535
|
end
|
1414
|
-
end
|
1415
1536
|
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1537
|
+
def send_or_call(duck, object)
|
1538
|
+
if duck.is_a?(Proc)
|
1539
|
+
duck.call(object)
|
1540
|
+
else
|
1541
|
+
object.send(duck)
|
1542
|
+
end
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
def set_include_blank(options)
|
1546
|
+
unless options.key?(:include_blank) || options.key?(:prompt)
|
1547
|
+
options[:include_blank] = @@include_blank_for_select_by_default
|
1548
|
+
end
|
1549
|
+
options
|
1419
1550
|
end
|
1420
|
-
options
|
1421
|
-
end
|
1422
1551
|
|
1423
1552
|
end
|
1424
1553
|
|
@@ -1480,7 +1609,7 @@ module Formtastic #:nodoc:
|
|
1480
1609
|
src = <<-END_SRC
|
1481
1610
|
def semantic_#{meth}(record_or_name_or_array, *args, &proc)
|
1482
1611
|
options = args.extract_options!
|
1483
|
-
options[:builder]
|
1612
|
+
options[:builder] ||= @@builder
|
1484
1613
|
options[:html] ||= {}
|
1485
1614
|
|
1486
1615
|
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|