formtastic 1.1.0 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.textile +80 -66
  3. data/generators/form/form_generator.rb +37 -46
  4. data/generators/formtastic/formtastic_generator.rb +10 -8
  5. data/lib/formtastic.rb +318 -227
  6. data/lib/formtastic/i18n.rb +8 -6
  7. data/lib/formtastic/layout_helper.rb +6 -4
  8. data/lib/formtastic/railtie.rb +3 -1
  9. data/lib/formtastic/util.rb +2 -0
  10. data/lib/generators/formtastic/form/form_generator.rb +5 -7
  11. data/lib/generators/formtastic/install/install_generator.rb +2 -9
  12. data/lib/generators/templates/_form.html.erb +5 -0
  13. data/lib/generators/templates/_form.html.haml +4 -0
  14. data/{generators/formtastic → lib/generators}/templates/formtastic.css +25 -11
  15. data/{generators/formtastic → lib/generators}/templates/formtastic.rb +19 -2
  16. data/{generators/formtastic → lib/generators}/templates/formtastic_changes.css +0 -0
  17. metadata +58 -98
  18. data/Rakefile +0 -127
  19. data/generators/form/templates/view__form.html.erb +0 -5
  20. data/generators/form/templates/view__form.html.haml +0 -4
  21. data/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +0 -16
  22. data/init.rb +0 -5
  23. data/rails/init.rb +0 -2
  24. data/spec/buttons_spec.rb +0 -166
  25. data/spec/commit_button_spec.rb +0 -401
  26. data/spec/custom_builder_spec.rb +0 -98
  27. data/spec/defaults_spec.rb +0 -20
  28. data/spec/error_proc_spec.rb +0 -27
  29. data/spec/errors_spec.rb +0 -105
  30. data/spec/form_helper_spec.rb +0 -142
  31. data/spec/helpers/layout_helper_spec.rb +0 -21
  32. data/spec/i18n_spec.rb +0 -152
  33. data/spec/include_blank_spec.rb +0 -74
  34. data/spec/input_spec.rb +0 -786
  35. data/spec/inputs/boolean_input_spec.rb +0 -104
  36. data/spec/inputs/check_boxes_input_spec.rb +0 -426
  37. data/spec/inputs/country_input_spec.rb +0 -118
  38. data/spec/inputs/date_input_spec.rb +0 -168
  39. data/spec/inputs/datetime_input_spec.rb +0 -310
  40. data/spec/inputs/file_input_spec.rb +0 -34
  41. data/spec/inputs/hidden_input_spec.rb +0 -78
  42. data/spec/inputs/numeric_input_spec.rb +0 -44
  43. data/spec/inputs/password_input_spec.rb +0 -46
  44. data/spec/inputs/radio_input_spec.rb +0 -243
  45. data/spec/inputs/select_input_spec.rb +0 -546
  46. data/spec/inputs/string_input_spec.rb +0 -64
  47. data/spec/inputs/text_input_spec.rb +0 -34
  48. data/spec/inputs/time_input_spec.rb +0 -206
  49. data/spec/inputs/time_zone_input_spec.rb +0 -110
  50. data/spec/inputs_spec.rb +0 -476
  51. data/spec/label_spec.rb +0 -89
  52. data/spec/semantic_errors_spec.rb +0 -98
  53. data/spec/semantic_fields_for_spec.rb +0 -45
  54. data/spec/spec.opts +0 -2
  55. data/spec/spec_helper.rb +0 -289
  56. data/spec/support/custom_macros.rb +0 -494
  57. data/spec/support/output_buffer.rb +0 -4
  58. data/spec/support/test_environment.rb +0 -45
data/lib/formtastic.rb CHANGED
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+ # encoding: utf-8
2
2
  require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
3
3
  require File.join(File.dirname(__FILE__), *%w[formtastic util])
4
4
  require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(::Rails::Railtie)
@@ -6,13 +6,16 @@ require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(:
6
6
  module Formtastic #:nodoc:
7
7
 
8
8
  class SemanticFormBuilder < ActionView::Helpers::FormBuilder
9
- class_inheritable_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
10
- :required_string, :optional_string, :inline_errors, :label_str_method, :collection_value_methods, :collection_label_methods,
11
- :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels, :default_commit_button_accesskey,
12
- :instance_reader => false
9
+ class_inheritable_accessor :default_text_field_size, :default_text_area_height, :default_text_area_width, :all_fields_required_by_default, :include_blank_for_select_by_default,
10
+ :required_string, :optional_string, :inline_errors, :label_str_method, :collection_value_methods, :collection_label_methods, :file_metadata_suffixes,
11
+ :inline_order, :custom_inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels,
12
+ :default_commit_button_accesskey, :default_inline_error_class, :default_hint_class, :default_error_list_class, :instance_reader => false
13
13
 
14
- self.default_text_field_size = 50
14
+ cattr_accessor :custom_namespace
15
+
16
+ self.default_text_field_size = nil
15
17
  self.default_text_area_height = 20
18
+ self.default_text_area_width = 50
16
19
  self.all_fields_required_by_default = true
17
20
  self.include_blank_for_select_by_default = true
18
21
  self.required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
@@ -22,11 +25,16 @@ module Formtastic #:nodoc:
22
25
  self.collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
23
26
  self.collection_value_methods = %w[id to_s]
24
27
  self.inline_order = [ :input, :hints, :errors ]
28
+ self.custom_inline_order = {}
25
29
  self.file_methods = [ :file?, :public_filename, :filename ]
30
+ self.file_metadata_suffixes = ['content_type', 'file_name', 'file_size']
26
31
  self.priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
27
32
  self.i18n_lookups_by_default = false
28
33
  self.escape_html_entities_in_hints_and_labels = true
29
34
  self.default_commit_button_accesskey = nil
35
+ self.default_inline_error_class = 'inline-errors'
36
+ self.default_error_list_class = 'errors'
37
+ self.default_hint_class = 'inline-hints'
30
38
 
31
39
  RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
32
40
 
@@ -64,6 +72,15 @@ module Formtastic #:nodoc:
64
72
  # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
65
73
  # * :string (a text field) - default for :string column types
66
74
  # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
75
+ # * :email (an email input) - default for :string column types with 'email' as the method name.
76
+ # * :url (a url input) - default for :string column types with 'url' as the method name.
77
+ # * :phone (a tel input) - default for :string column types with 'phone' or 'fax' in the method name.
78
+ # * :search (a search input) - default for :string column types with 'search' as the method name.
79
+ # * :country (a select menu of country names) - requires a country_select plugin to be installed
80
+ # * :email (an email input) - New in HTML5 - needs to be explicitly provided with :as => :email
81
+ # * :url (a url input) - New in HTML5 - needs to be explicitly provided with :as => :url
82
+ # * :phone (a tel input) - New in HTML5 - needs to be explicitly provided with :as => :phone
83
+ # * :search (a search input) - New in HTML5 - needs to be explicity provided with :as => :search
67
84
  # * :country (a select menu of country names) - requires a country_select plugin to be installed
68
85
  # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
69
86
  #
@@ -76,22 +93,17 @@ module Formtastic #:nodoc:
76
93
  # <%= form.input :manager_id, :as => :radio %>
77
94
  # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
78
95
  # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
96
+ # <%= form.input :email %>
97
+ # <%= form.input :website, :as => :url, :hint => "You may wish to omit the http://" %>
79
98
  # <% end %>
80
99
  # <% end %>
81
100
  #
82
101
  def input(method, options = {})
83
- if options.key?(:selected) || options.key?(:checked) || options.key?(:default)
84
- ::ActiveSupport::Deprecation.warn(
85
- "The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " <<
86
- "Please set default values in your models (using an after_initialize callback) or in your controller set-up. " <<
87
- "See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller)
88
- end
89
-
90
102
  options[:required] = method_required?(method) unless options.key?(:required)
91
103
  options[:as] ||= default_input_type(method, options)
92
104
 
93
105
  html_class = [ options[:as], (options[:required] ? :required : :optional) ]
94
- html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
106
+ html_class << 'error' if has_errors?(method, options)
95
107
 
96
108
  wrapper_html = options.delete(:wrapper_html) || {}
97
109
  wrapper_html[:id] ||= generate_html_id(method)
@@ -102,7 +114,7 @@ module Formtastic #:nodoc:
102
114
  options[:label_html][:for] ||= options[:input_html][:id]
103
115
  end
104
116
 
105
- input_parts = self.class.inline_order.dup
117
+ input_parts = (self.class.custom_inline_order[options[:as]] || self.class.inline_order).dup
106
118
  input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
107
119
 
108
120
  list_item_content = input_parts.map do |type|
@@ -263,7 +275,7 @@ module Formtastic #:nodoc:
263
275
  html_options = args.extract_options!
264
276
  html_options[:class] ||= "inputs"
265
277
  html_options[:name] = title
266
-
278
+
267
279
  if html_options[:for] # Nested form
268
280
  inputs_for_nested_attributes(*(args << html_options), &block)
269
281
  elsif block_given?
@@ -278,11 +290,15 @@ module Formtastic #:nodoc:
278
290
  legend = args.shift if args.first.is_a?(::String)
279
291
  contents = args.collect { |method| input(method.to_sym) }
280
292
  args.unshift(legend) if legend.present?
281
-
293
+
282
294
  field_set_and_list_wrapping(*((args << html_options) << contents))
283
295
  end
284
296
  end
285
- alias :input_field_set :inputs
297
+
298
+ def input_field_set(*args, &block)
299
+ ::ActiveSupport::Deprecation.warn("input_field_set() is deprecated and will be removed in Formtastic 1.3 or later, use inputs() instead.", caller)
300
+ inputs(args, &block)
301
+ end
286
302
 
287
303
  # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
288
304
  # See inputs documentation for a full example. The fieldset's default class attriute
@@ -301,7 +317,11 @@ module Formtastic #:nodoc:
301
317
  field_set_and_list_wrapping(html_options, contents)
302
318
  end
303
319
  end
304
- alias :button_field_set :buttons
320
+
321
+ def button_field_set(*args, &block)
322
+ ::ActiveSupport::Deprecation.warn("button_field_set() is deprecated and will be removed in Formtastic 1.3 or later, use inputs() instead.", caller)
323
+ buttons(args, &block)
324
+ end
305
325
 
306
326
  # Creates a submit input tag with the value "Save [model name]" (for existing records) or
307
327
  # "Create [model name]" (for new records) by default:
@@ -322,11 +342,15 @@ module Formtastic #:nodoc:
322
342
  options = args.extract_options!
323
343
  text = options.delete(:label) || args.shift
324
344
 
325
- if @object && @object.respond_to?(:new_record?)
326
- key = @object.new_record? ? :create : :update
327
-
345
+ if @object && (@object.respond_to?(:persisted?) || @object.respond_to?(:new_record?))
346
+ if @object.respond_to?(:persisted?) # ActiveModel
347
+ key = @object.persisted? ? :update : :create
348
+ else # Rails 2
349
+ key = @object.new_record? ? :create : :update
350
+ end
351
+
328
352
  # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
329
- # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
353
+ # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
330
354
  # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
331
355
  # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
332
356
  if @object.class.model_name.respond_to?(:human)
@@ -432,12 +456,10 @@ module Formtastic #:nodoc:
432
456
  # f.errors_on(:body)
433
457
  # end
434
458
  #
435
- def inline_errors_for(method, options = nil) #:nodoc:
459
+ def inline_errors_for(method, options = {}) #:nodoc:
436
460
  if render_inline_errors?
437
- errors = [@object.errors[method.to_sym]]
438
- errors << [@object.errors[association_primary_key(method)]] if association_macro_for_method(method) == :belongs_to
439
- errors = errors.flatten.compact.uniq
440
- send(:"error_#{self.class.inline_errors}", [*errors]) if errors.any?
461
+ errors = error_keys(method, options).map{|x| @object.errors[x] }.flatten.compact.uniq
462
+ send(:"error_#{self.class.inline_errors}", [*errors], options) if errors.any?
441
463
  else
442
464
  nil
443
465
  end
@@ -472,6 +494,21 @@ module Formtastic #:nodoc:
472
494
 
473
495
  protected
474
496
 
497
+ def error_keys(method, options)
498
+ @methods_for_error ||= {}
499
+ @methods_for_error[method] ||= begin
500
+ methods_for_error = [method.to_sym]
501
+ methods_for_error << self.class.file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
502
+ methods_for_error << [association_primary_key(method)] if association_macro_for_method(method) == :belongs_to
503
+ methods_for_error.flatten.compact.uniq
504
+ end
505
+ end
506
+
507
+ def has_errors?(method, options)
508
+ methods_for_error = error_keys(method,options)
509
+ @object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
510
+ end
511
+
475
512
  def render_inline_errors?
476
513
  @object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(self.class.inline_errors)
477
514
  end
@@ -486,9 +523,9 @@ module Formtastic #:nodoc:
486
523
  #
487
524
  def association_columns(*by_associations) #:nodoc:
488
525
  if @object.present? && @object.class.respond_to?(:reflections)
489
- @object.class.reflections.collect do |name, _|
526
+ @object.class.reflections.collect do |name, association_reflection|
490
527
  if by_associations.present?
491
- name if by_associations.include?(_.macro)
528
+ name if by_associations.include?(association_reflection.macro)
492
529
  else
493
530
  name
494
531
  end
@@ -497,13 +534,13 @@ module Formtastic #:nodoc:
497
534
  []
498
535
  end
499
536
  end
500
-
537
+
501
538
  # Returns nil, or a symbol like :belongs_to or :has_many
502
539
  def association_macro_for_method(method) #:nodoc:
503
540
  reflection = self.reflection_for(method)
504
541
  reflection.macro if reflection
505
542
  end
506
-
543
+
507
544
  def association_primary_key(method)
508
545
  reflection = self.reflection_for(method)
509
546
  reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
@@ -569,18 +606,17 @@ module Formtastic #:nodoc:
569
606
  # configuration option all_fields_required_by_default is used.
570
607
  #
571
608
  def method_required?(attribute) #:nodoc:
572
- if @object && @object.class.respond_to?(:reflect_on_validations_for)
573
- attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
609
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
574
610
 
611
+ if @object && @object.class.respond_to?(:reflect_on_validations_for)
575
612
  @object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
576
- validation.macro == :validates_presence_of &&
613
+ (validation.macro == :validates_presence_of || validation.macro == :validates_inclusion_of) &&
577
614
  validation.name == attribute_sym &&
578
615
  (validation.options.present? ? options_require_validation?(validation.options) : true)
579
616
  end
580
617
  else
581
618
  if @object && @object.class.respond_to?(:validators_on)
582
- attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
583
- !@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
619
+ !@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence || validator.kind == :inclusion) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
584
620
  else
585
621
  self.class.all_fields_required_by_default
586
622
  end
@@ -605,10 +641,13 @@ module Formtastic #:nodoc:
605
641
 
606
642
  def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
607
643
  html_options = options.delete(:input_html) || {}
608
- html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text].include?(type)
609
-
610
- self.label(method, options_for_label(options)) <<
611
- self.send(form_helper_method, method, html_options)
644
+ html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text, :phone, :search, :url, :email].include?(type)
645
+ field_id = generate_html_id(method, "")
646
+ html_options[:id] ||= field_id
647
+ label_options = options_for_label(options)
648
+ label_options[:for] ||= html_options[:id]
649
+ self.label(method, label_options) <<
650
+ self.send(self.respond_to?(form_helper_method) ? form_helper_method : :text_field, method, html_options)
612
651
  end
613
652
 
614
653
  # Outputs a label and standard Rails text field inside the wrapper.
@@ -636,6 +675,26 @@ module Formtastic #:nodoc:
636
675
  basic_input_helper(:file_field, :file, method, options)
637
676
  end
638
677
 
678
+ # Outputs a label and a standard Rails email field inside the wrapper.
679
+ def email_input(method, options)
680
+ basic_input_helper(:email_field, :email, method, options)
681
+ end
682
+
683
+ # Outputs a label and a standard Rails phone field inside the wrapper.
684
+ def phone_input(method, options)
685
+ basic_input_helper(:phone_field, :phone, method, options)
686
+ end
687
+
688
+ # Outputs a label and a standard Rails url field inside the wrapper.
689
+ def url_input(method, options)
690
+ basic_input_helper(:url_field, :url, method, options)
691
+ end
692
+
693
+ # Outputs a label and a standard Rails search field inside the wrapper.
694
+ def search_input(method, options)
695
+ basic_input_helper(:search_field, :search, method, options)
696
+ end
697
+
639
698
  # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
640
699
  # Additionals options can be given using :input_hml. Should :input_html not be
641
700
  # specified every option except for formtastic options will be sent straight
@@ -644,6 +703,7 @@ module Formtastic #:nodoc:
644
703
  def hidden_input(method, options)
645
704
  options ||= {}
646
705
  html_options = options.delete(:input_html) || strip_formtastic_options(options)
706
+ html_options[:id] ||= generate_html_id(method, "")
647
707
  self.hidden_field(method, html_options)
648
708
  end
649
709
 
@@ -686,18 +746,19 @@ module Formtastic #:nodoc:
686
746
  # </select>
687
747
  #
688
748
  #
689
- # You can customize the options available in the select by passing in a collection (an Array or
690
- # Hash) through the :collection option. If not provided, the choices are found by inferring the
691
- # parent's class name from the method name and simply calling find(:all) on it
692
- # (VehicleOwner.find(:all) in the example above).
749
+ # You can customize the options available in the select by passing in a collection. A collection can be given
750
+ # as an Array, a Hash or as a String (containing pre-rendered HTML options). If not provided, the choices are
751
+ # found by inferring the parent's class name from the method name and simply calling all on it
752
+ # (VehicleOwner.all in the example above).
693
753
  #
694
754
  # Examples:
695
755
  #
696
756
  # f.input :author, :collection => @authors
697
- # f.input :author, :collection => Author.find(:all)
757
+ # f.input :author, :collection => Author.all
698
758
  # f.input :author, :collection => [@justin, @kate]
699
759
  # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
700
760
  # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
761
+ # f.input :author, :collection => grouped_options_for_select(["North America",[["United States","US"],["Canada","CA"]]])
701
762
  #
702
763
  # The :label_method option allows you to customize the text label inside each option tag two ways:
703
764
  #
@@ -719,15 +780,6 @@ module Formtastic #:nodoc:
719
780
  # f.input :author, :value_method => :login
720
781
  # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
721
782
  #
722
- # You can pre-select a specific option value by passing in the :selected option.
723
- #
724
- # Examples:
725
- #
726
- # f.input :author, :selected => current_user.id
727
- # f.input :author, :value_method => :login, :selected => current_user.login
728
- # f.input :authors, :value_method => :login, :selected => Author.most_popular.collect(&:id)
729
- # f.input :authors, :value_method => :login, :selected => nil # override any defaults: select none
730
- #
731
783
  # You can pass html_options to the select tag using :input_html => {}
732
784
  #
733
785
  # Examples:
@@ -738,14 +790,14 @@ module Formtastic #:nodoc:
738
790
  # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
739
791
  #
740
792
  #
741
- # You can group the options in optgroup elements by passing the :group_by option
793
+ # You can group the options in optgroup elements by passing the :group_by option
742
794
  # (Note: only tested for belongs_to relations)
743
- #
795
+ #
744
796
  # Examples:
745
797
  #
746
798
  # f.input :author, :group_by => :continent
747
- #
748
- # All the other options should work as expected. If you want to call a custom method on the
799
+ #
800
+ # All the other options should work as expected. If you want to call a custom method on the
749
801
  # group item. You can include the option:group_label_method
750
802
  # Examples:
751
803
  #
@@ -753,24 +805,29 @@ module Formtastic #:nodoc:
753
805
  #
754
806
  def select_input(method, options)
755
807
  html_options = options.delete(:input_html) || {}
756
- options = set_include_blank(options)
757
808
  html_options[:multiple] = html_options[:multiple] || options.delete(:multiple)
758
809
  html_options.delete(:multiple) if html_options[:multiple].nil?
759
810
 
760
811
  reflection = self.reflection_for(method)
761
812
  if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
762
- options[:include_blank] = false
763
813
  html_options[:multiple] = true if html_options[:multiple].nil?
764
814
  html_options[:size] ||= 5
815
+
816
+ if html_options[:multiple]
817
+ options[:include_blank] = false
818
+ else
819
+ options[:include_blank] ||= false
820
+ end
765
821
  end
766
- options[:selected] = options[:selected].first if options[:selected].present? && html_options[:multiple] == false
822
+ options = set_include_blank(options)
767
823
  input_name = generate_association_input_name(method)
824
+ html_options[:id] ||= generate_html_id(input_name, "")
768
825
 
769
826
  select_html = if options[:group_by]
770
- # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
827
+ # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
771
828
  # The formtastic user however shouldn't notice this too much.
772
829
  raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
773
- label, value = detect_label_and_value_method!(raw_collection)
830
+ label, value = detect_label_and_value_method!(raw_collection, options)
774
831
  group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
775
832
  group_label_method = options[:group_label_method] || detect_label_method(group_collection)
776
833
  group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
@@ -779,7 +836,7 @@ module Formtastic #:nodoc:
779
836
  # Here comes the monster with 8 arguments
780
837
  self.grouped_collection_select(input_name, group_collection,
781
838
  group_association, group_label_method,
782
- value, label,
839
+ value, label,
783
840
  strip_formtastic_options(options), html_options)
784
841
  else
785
842
  collection = find_collection_for_column(method, options)
@@ -787,9 +844,15 @@ module Formtastic #:nodoc:
787
844
  self.select(input_name, collection, strip_formtastic_options(options), html_options)
788
845
  end
789
846
 
790
- self.label(method, options_for_label(options).merge(:input_name => input_name)) << select_html
847
+ label_options = options_for_label(options).merge(:input_name => input_name)
848
+ label_options[:for] ||= html_options[:id]
849
+ self.label(method, label_options) << select_html
850
+ end
851
+
852
+ def boolean_select_input(method, options)
853
+ ::ActiveSupport::Deprecation.warn(":as => :boolean_select is deprecated and will be removed in Formtastic 1.3 or later. Use :as => :select instead.", caller)
854
+ select_input(method, options)
791
855
  end
792
- alias :boolean_select_input :select_input
793
856
 
794
857
  # Outputs a timezone select input as Rails' time_zone_select helper. You
795
858
  # can give priority zones as option.
@@ -797,22 +860,15 @@ module Formtastic #:nodoc:
797
860
  # Examples:
798
861
  #
799
862
  # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
800
- #
801
- # You can pre-select a specific option value by passing in the :selected option.
802
- # Note: Right now only works if the form object attribute value is not set (nil),
803
- # because of how the core helper is implemented.
804
- #
805
- # Examples:
806
- #
807
- # f.input :my_favorite_time_zone, :as => :time_zone, :selected => 'Singapore'
808
- #
809
863
  def time_zone_input(method, options)
810
864
  html_options = options.delete(:input_html) || {}
811
- selected_value = options.delete(:selected)
812
-
813
- self.label(method, options_for_label(options)) <<
865
+ field_id = generate_html_id(method, "")
866
+ html_options[:id] ||= field_id
867
+ label_options = options_for_label(options)
868
+ label_options[:for] ||= html_options[:id]
869
+ self.label(method, label_options) <<
814
870
  self.time_zone_select(method, options.delete(:priority_zones),
815
- strip_formtastic_options(options).merge(:default => selected_value), html_options)
871
+ strip_formtastic_options(options), html_options)
816
872
  end
817
873
 
818
874
  # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
@@ -837,14 +893,14 @@ module Formtastic #:nodoc:
837
893
  # </ol>
838
894
  # </fieldset>
839
895
  #
840
- # You can customize the choices available in the radio button set by passing in a collection (an Array or
896
+ # You can customize the choices available in the radio button set by passing in a collection (an Array or
841
897
  # Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
842
- # (Author.find(:all) in the example above).
898
+ # (Author.all in the example above).
843
899
  #
844
900
  # Examples:
845
901
  #
846
902
  # f.input :author, :as => :radio, :collection => @authors
847
- # f.input :author, :as => :radio, :collection => Author.find(:all)
903
+ # f.input :author, :as => :radio, :collection => Author.all
848
904
  # f.input :author, :as => :radio, :collection => [@justin, @kate]
849
905
  # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
850
906
  #
@@ -867,18 +923,10 @@ module Formtastic #:nodoc:
867
923
  # f.input :author, :as => :radio, :value_method => :full_name
868
924
  # f.input :author, :as => :radio, :value_method => :login
869
925
  # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
870
- #
871
- # You can force a particular radio button in the collection to be checked with the :selected option.
872
926
  #
873
- # Examples:
874
- #
875
- # f.input :subscribe_to_newsletter, :as => :radio, :selected => true
876
- # f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
877
- #
878
- # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
927
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
879
928
  # button / label combination to contain a class with the value of the radio button (useful for
880
929
  # applying specific CSS or Javascript to a particular radio button).
881
- #
882
930
  def radio_input(method, options)
883
931
  collection = find_collection_for_column(method, options)
884
932
  html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})
@@ -886,8 +934,6 @@ module Formtastic #:nodoc:
886
934
  input_name = generate_association_input_name(method)
887
935
  value_as_class = options.delete(:value_as_class)
888
936
  input_ids = []
889
- selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
890
- selected_value = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
891
937
 
892
938
  list_item_content = collection.map do |c|
893
939
  label = c.is_a?(Array) ? c.first : c
@@ -895,7 +941,7 @@ module Formtastic #:nodoc:
895
941
  input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
896
942
  input_ids << input_id
897
943
 
898
- html_options[:checked] = selected_value == value if selected_option_is_present
944
+ html_options[:id] = input_id
899
945
 
900
946
  li_content = template.content_tag(:label,
901
947
  Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
@@ -905,12 +951,16 @@ module Formtastic #:nodoc:
905
951
  li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
906
952
  template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
907
953
  end
908
-
954
+
909
955
  template.content_tag(:fieldset,
910
956
  legend_tag(method, options) << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
911
957
  )
912
958
  end
913
- alias :boolean_radio_input :radio_input
959
+
960
+ def boolean_radio_input(method, options)
961
+ ::ActiveSupport::Deprecation.warn(":as => :boolean_radio is deprecated and will be removed in Formtastic 1.3 or later. Use :as => :radio instead.", caller)
962
+ radio_input(method, options)
963
+ end
914
964
 
915
965
  # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
916
966
  # items (li), one for each fragment for the date (year, month, day). Each li contains a label
@@ -918,15 +968,7 @@ module Formtastic #:nodoc:
918
968
  # :labels should be a hash with the field (e.g. day) as key and the label text as value.
919
969
  # See date_or_datetime_input for a more detailed output example.
920
970
  #
921
- # You can pre-select a specific option value by passing in the :selected option.
922
- #
923
- # Examples:
924
- #
925
- # f.input :created_at, :as => :date, :selected => 1.day.ago
926
- # f.input :created_at, :as => :date, :selected => nil # override any defaults: select none
927
- # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day" }
928
- #
929
- # Some of Rails' options for select_date are supported, but not everything yet, see
971
+ # Some of Rails' options for select_date are supported, but not everything yet, see
930
972
  # documentation of date_or_datetime_input() for more information.
931
973
  def date_input(method, options)
932
974
  options = set_include_blank(options)
@@ -939,16 +981,7 @@ module Formtastic #:nodoc:
939
981
  # the :labels option. :labels should be a hash with the field (e.g. day) as key and the label
940
982
  # text as value. See date_or_datetime_input for a more detailed output example.
941
983
  #
942
- # You can pre-select a specific option value by passing in the :selected option.
943
- #
944
- # Examples:
945
- #
946
- # f.input :created_at, :as => :datetime, :selected => 1.day.ago
947
- # f.input :created_at, :as => :datetime, :selected => nil # override any defaults: select none
948
- # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day",
949
- # :hour => "Hour", :minute => "Minute" }
950
- #
951
- # Some of Rails' options for select_date are supported, but not everything yet, see
984
+ # Some of Rails' options for select_date are supported, but not everything yet, see
952
985
  # documentation of date_or_datetime_input() for more information.
953
986
  def datetime_input(method, options)
954
987
  options = set_include_blank(options)
@@ -961,23 +994,15 @@ module Formtastic #:nodoc:
961
994
  # :labels should be a hash with the field (e.g. day) as key and the label text as value.
962
995
  # See date_or_datetime_input for a more detailed output example.
963
996
  #
964
- # You can pre-select a specific option value by passing in the :selected option.
965
- #
966
- # Examples:
967
- #
968
- # f.input :created_at, :as => :time, :selected => 1.hour.ago
969
- # f.input :created_at, :as => :time, :selected => nil # override any defaults: select none
970
- # f.input :created_at, :as => :date, :labels => { :hour => "Hour", :minute => "Minute" }
971
- #
972
- # Some of Rails' options for select_time are supported, but not everything yet, see
997
+ # Some of Rails' options for select_time are supported, but not everything yet, see
973
998
  # documentation of date_or_datetime_input() for more information.
974
999
  def time_input(method, options)
975
1000
  options = set_include_blank(options)
976
1001
  date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
977
1002
  end
978
-
979
- # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
980
- # legend (for what would normally be considered the label), and an ordered list of list items
1003
+
1004
+ # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
1005
+ # legend (for what would normally be considered the label), and an ordered list of list items
981
1006
  # for year, month, day, hour, etc, each containing a label and a select. Example:
982
1007
  #
983
1008
  # <fieldset>
@@ -1016,9 +1041,6 @@ module Formtastic #:nodoc:
1016
1041
  #
1017
1042
  # * @:order => [:month, :day, :year]@
1018
1043
  # * @:include_seconds@ => true@
1019
- # * @:selected => Time.mktime(2008)@
1020
- # * @:selected => Date.new(2008)@
1021
- # * @:selected => nil@
1022
1044
  # * @:discard_(year|month|day|hour|minute) => true@
1023
1045
  # * @:include_blank => true@
1024
1046
  # * @:labels => {}@
@@ -1036,8 +1058,7 @@ module Formtastic #:nodoc:
1036
1058
  list_items_capture = ""
1037
1059
  hidden_fields_capture = ""
1038
1060
 
1039
- datetime = options[:selected]
1040
- datetime = @object.send(method) if @object && @object.send(method) # object value trumps :selected value
1061
+ datetime = @object.send(method) if @object && @object.send(method)
1041
1062
 
1042
1063
  html_options = options.delete(:input_html) || {}
1043
1064
  input_ids = []
@@ -1098,13 +1119,13 @@ module Formtastic #:nodoc:
1098
1119
  #
1099
1120
  # You can customize the options available in the set by passing in a collection (Array) of
1100
1121
  # ActiveRecord objects through the :collection option. If not provided, the choices are found
1101
- # by inferring the parent's class name from the method name and simply calling find(:all) on
1102
- # it (Author.find(:all) in the example above).
1122
+ # by inferring the parent's class name from the method name and simply calling all on
1123
+ # it (Author.all in the example above).
1103
1124
  #
1104
1125
  # Examples:
1105
1126
  #
1106
1127
  # f.input :author, :as => :check_boxes, :collection => @authors
1107
- # f.input :author, :as => :check_boxes, :collection => Author.find(:all)
1128
+ # f.input :author, :as => :check_boxes, :collection => Author.all
1108
1129
  # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
1109
1130
  #
1110
1131
  # The :label_method option allows you to customize the label for each checkbox two ways:
@@ -1127,25 +1148,16 @@ module Formtastic #:nodoc:
1127
1148
  # f.input :author, :as => :check_boxes, :value_method => :login
1128
1149
  # f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
1129
1150
  #
1130
- # You can pre-select/check a specific checkbox value by passing in the :selected option (alias :checked works as well).
1131
- #
1132
- # Examples:
1133
- #
1134
- # f.input :authors, :as => :check_boxes, :selected => @justin
1135
- # f.input :authors, :as => :check_boxes, :selected => Author.most_popular.collect(&:id)
1136
- # f.input :authors, :as => :check_boxes, :selected => nil # override any defaults: select none
1137
- #
1138
- #
1139
1151
  # Formtastic works around a bug in rails handling of check box collections by
1140
- # not generating the hidden fields for state checking of the checkboxes
1152
+ # not generating the hidden fields for state checking of the checkboxes
1141
1153
  # The :hidden_fields option provides a way to re-enable these hidden inputs by
1142
1154
  # setting it to true.
1143
1155
  #
1144
1156
  # f.input :authors, :as => :check_boxes, :hidden_fields => false
1145
1157
  # f.input :authors, :as => :check_boxes, :hidden_fields => true
1146
1158
  #
1147
- # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
1148
- # combination to contain a class with the value of the radio button (useful for applying specific
1159
+ # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
1160
+ # combination to contain a class with the value of the radio button (useful for applying specific
1149
1161
  # CSS or Javascript to a particular checkbox).
1150
1162
  #
1151
1163
  def check_boxes_input(method, options)
@@ -1190,27 +1202,21 @@ module Formtastic #:nodoc:
1190
1202
  template.content_tag(:fieldset, fieldset_content)
1191
1203
  end
1192
1204
 
1193
- # Used by check_boxes input. The selected values will be set either by:
1194
- #
1195
- # * Explicitly provided through :selected or :checked
1196
- # * Values retrieved through an association
1205
+ # Used by check_boxes input. The selected values will be set by retrieving the value
1206
+ # through the association.
1197
1207
  #
1198
1208
  # If the collection is not a hash or an array of strings, fixnums or symbols,
1199
1209
  # we use value_method to retrieve an array with the values
1200
- #
1201
1210
  def find_selected_values_for_column(method, options)
1202
- selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
1203
- if selected_option_is_present
1204
- selected_values = (options.key?(:checked) ? options[:checked] : options[:selected])
1205
- elsif object.respond_to?(method)
1211
+ if object.respond_to?(method)
1206
1212
  collection = [object.send(method)].compact.flatten
1207
1213
  label, value = detect_label_and_value_method!(collection, options)
1208
- selected_values = collection.map { |o| send_or_call(value, o) }
1214
+ [*collection.map { |o| send_or_call(value, o) }].compact
1215
+ else
1216
+ []
1209
1217
  end
1210
- selected_values = [*selected_values].compact
1211
- selected_values
1212
1218
  end
1213
-
1219
+
1214
1220
  # Outputs a custom hidden field for check_boxes
1215
1221
  def create_hidden_field_for_check_boxes(method, value_as_class) #:nodoc:
1216
1222
  options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
@@ -1225,14 +1231,14 @@ module Formtastic #:nodoc:
1225
1231
  self.check_box(input_name, html_options, checked_value, unchecked_value)
1226
1232
  end
1227
1233
 
1228
- # Outputs a country select input, wrapping around a regular country_select helper.
1234
+ # Outputs a country select input, wrapping around a regular country_select helper.
1229
1235
  # Rails doesn't come with a country_select helper by default any more, so you'll need to install
1230
1236
  # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
1231
1237
  # same way.
1232
1238
  #
1233
1239
  # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
1234
1240
  #
1235
- # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1241
+ # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1236
1242
  # which you can change to suit your market and user base (see README for more info on config).
1237
1243
  #
1238
1244
  # Examples:
@@ -1245,35 +1251,39 @@ module Formtastic #:nodoc:
1245
1251
  html_options = options.delete(:input_html) || {}
1246
1252
  priority_countries = options.delete(:priority_countries) || self.class.priority_countries
1247
1253
 
1248
- self.label(method, options_for_label(options)) <<
1254
+ field_id = generate_html_id(method, "")
1255
+ html_options[:id] ||= field_id
1256
+ label_options = options_for_label(options)
1257
+ label_options[:for] ||= html_options[:id]
1258
+
1259
+ self.label(method, label_options) <<
1249
1260
  self.country_select(method, priority_countries, strip_formtastic_options(options), html_options)
1250
1261
  end
1251
1262
 
1252
1263
  # Outputs a label containing a checkbox and the label text. The label defaults
1253
1264
  # to the column name (method name) and can be altered with the :label option.
1254
1265
  # :checked_value and :unchecked_value options are also available.
1255
- #
1256
- # You can pre-select/check the boolean checkbox by passing in the :selected option (alias :checked works as well).
1257
- #
1258
- # Examples:
1259
- #
1260
- # f.input :allow_comments, :as => :boolean, :selected => true # override any default value: selected/checked
1261
- #
1262
1266
  def boolean_input(method, options)
1263
1267
  html_options = options.delete(:input_html) || {}
1264
- checked = options.key?(:checked) ? options[:checked] : options[:selected]
1265
- html_options[:checked] = checked == true if [:selected, :checked].any? { |k| options.key?(k) }
1266
1268
  checked_value = options.delete(:checked_value) || '1'
1267
1269
  unchecked_value = options.delete(:unchecked_value) || '0'
1268
1270
 
1269
- input = self.check_box(method, strip_formtastic_options(options).merge(html_options),
1270
- checked_value, unchecked_value)
1271
+ #input = self.check_box(method, strip_formtastic_options(options).merge(html_options),
1272
+ # checked_value, unchecked_value)
1273
+ field_id = [@@custom_namespace,@object_name,method].reject{|x|x.blank?}.join("_")
1274
+ input = template.check_box_tag(
1275
+ "#{@object_name}[#{method}]",
1276
+ checked_value,
1277
+ (@object && @object.send(:"#{method}") == checked_value),
1278
+ :id => field_id
1279
+ )
1271
1280
  options = options_for_label(options)
1281
+ options[:for] ||= field_id
1272
1282
 
1273
1283
  # the label() method will insert this nested input into the label at the last minute
1274
1284
  options[:label_prefix_for_nested_input] = input
1275
1285
 
1276
- self.label(method, options)
1286
+ template.hidden_field_tag(method, unchecked_value) << self.label(method, options)
1277
1287
  end
1278
1288
 
1279
1289
  # Generates an input for the given method using the type supplied with :as.
@@ -1286,29 +1296,33 @@ module Formtastic #:nodoc:
1286
1296
  def inline_hints_for(method, options) #:nodoc:
1287
1297
  options[:hint] = localized_string(method, options[:hint], :hint)
1288
1298
  return if options[:hint].blank? or options[:hint].kind_of? Hash
1289
- template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => 'inline-hints')
1299
+ hint_class = options[:hint_class] || self.class.default_hint_class
1300
+ template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => hint_class)
1290
1301
  end
1291
1302
 
1292
1303
  # Creates an error sentence by calling to_sentence on the errors array.
1293
1304
  #
1294
- def error_sentence(errors) #:nodoc:
1295
- template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => 'inline-errors')
1305
+ def error_sentence(errors, options = {}) #:nodoc:
1306
+ error_class = options[:error_class] || self.class.default_inline_error_class
1307
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => error_class)
1296
1308
  end
1297
1309
 
1298
1310
  # Creates an error li list.
1299
1311
  #
1300
- def error_list(errors) #:nodoc:
1312
+ def error_list(errors, options = {}) #:nodoc:
1313
+ error_class = options[:error_class] || self.class.default_error_list_class
1301
1314
  list_elements = []
1302
1315
  errors.each do |error|
1303
1316
  list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
1304
1317
  end
1305
- template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => 'errors')
1318
+ template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
1306
1319
  end
1307
1320
 
1308
1321
  # Creates an error sentence containing only the first error
1309
1322
  #
1310
- def error_first(errors) #:nodoc:
1311
- template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => 'inline-errors')
1323
+ def error_first(errors, options = {}) #:nodoc:
1324
+ error_class = options[:error_class] || self.class.default_inline_error_class
1325
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
1312
1326
  end
1313
1327
 
1314
1328
  # Generates the required or optional string. If the value set is a proc,
@@ -1406,9 +1420,14 @@ module Formtastic #:nodoc:
1406
1420
 
1407
1421
  # Generates the legend for radiobuttons and checkboxes
1408
1422
  def legend_tag(method, options = {})
1409
- (options[:label] == false) ? Formtastic::Util.html_safe("") : template.content_tag(:legend,
1410
- template.label_tag(nil, localized_string(method, options[:label], :label) || humanized_attribute_name(method), :for => nil), :class => :label
1411
- )
1423
+ if options[:label] == false
1424
+ Formtastic::Util.html_safe("")
1425
+ else
1426
+ text = localized_string(method, options[:label], :label) || humanized_attribute_name(method)
1427
+ text += required_or_optional_string(options.delete(:required))
1428
+ text = Formtastic::Util.html_safe(text)
1429
+ template.content_tag :legend, template.label_tag(nil, text, :for => nil), :class => :label
1430
+ end
1412
1431
  end
1413
1432
 
1414
1433
  # For methods that have a database column, take a best guess as to what the input method
@@ -1427,15 +1446,19 @@ module Formtastic #:nodoc:
1427
1446
  return :password if method.to_s =~ /password/
1428
1447
  return :country if method.to_s =~ /country$/
1429
1448
  return :time_zone if method.to_s =~ /time_zone/
1449
+ return :email if method.to_s =~ /email/
1450
+ return :url if method.to_s =~ /^url$|^website$|_url$/
1451
+ return :phone if method.to_s =~ /(phone|fax)/
1452
+ return :search if method.to_s =~ /^search$/
1430
1453
  when :integer
1431
- return :select if method.to_s =~ /_id$/
1454
+ return :select if self.reflection_for(method)
1432
1455
  return :numeric
1433
1456
  when :float, :decimal
1434
1457
  return :numeric
1435
1458
  when :timestamp
1436
1459
  return :datetime
1437
1460
  end
1438
-
1461
+
1439
1462
  # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
1440
1463
  return :select if column.type == :string && options.key?(:collection)
1441
1464
  # Try 3: Assume the input name will be the same as the column type (e.g. string_input).
@@ -1444,8 +1467,7 @@ module Formtastic #:nodoc:
1444
1467
  if @object
1445
1468
  return :select if self.reflection_for(method)
1446
1469
 
1447
- file = @object.send(method) if @object.respond_to?(method)
1448
- return :file if file && self.class.file_methods.any? { |m| file.respond_to?(m) }
1470
+ return :file if is_file?(method, options)
1449
1471
  end
1450
1472
 
1451
1473
  return :select if options.key?(:collection)
@@ -1454,6 +1476,14 @@ module Formtastic #:nodoc:
1454
1476
  end
1455
1477
  end
1456
1478
 
1479
+ def is_file?(method, options = {})
1480
+ @files ||= {}
1481
+ @files[method] ||= (options[:as].present? && options[:as] == :file) || begin
1482
+ file = @object.send(method) if @object && @object.respond_to?(method)
1483
+ file && self.class.file_methods.any?{|m| file.respond_to?(m)}
1484
+ end
1485
+ end
1486
+
1457
1487
  # Used by select and radio inputs. The collection can be retrieved by
1458
1488
  # three ways:
1459
1489
  #
@@ -1468,6 +1498,9 @@ module Formtastic #:nodoc:
1468
1498
  def find_collection_for_column(column, options) #:nodoc:
1469
1499
  collection = find_raw_collection_for_column(column, options)
1470
1500
 
1501
+ # Return if we have a plain string
1502
+ return collection if collection.instance_of?(String) || collection.instance_of?(::Formtastic::Util.rails_safe_buffer_class)
1503
+
1471
1504
  # Return if we have an Array of strings, fixnums or arrays
1472
1505
  return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
1473
1506
  [Array, Fixnum, String, Symbol].include?(collection.first.class) &&
@@ -1539,16 +1572,16 @@ module Formtastic #:nodoc:
1539
1572
  def detect_group_association(method, group_by)
1540
1573
  object_to_method_reflection = self.reflection_for(method)
1541
1574
  method_class = object_to_method_reflection.klass
1542
-
1575
+
1543
1576
  method_to_group_association = method_class.reflect_on_association(group_by)
1544
1577
  group_class = method_to_group_association.klass
1545
-
1578
+
1546
1579
  # This will return in the normal case
1547
1580
  return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
1548
-
1581
+
1549
1582
  # This is for belongs_to associations named differently than their class
1550
1583
  # form.input :parent, :group_by => :customer
1551
- # eg.
1584
+ # eg.
1552
1585
  # class Project
1553
1586
  # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1554
1587
  # belongs_to :customer
@@ -1558,9 +1591,9 @@ module Formtastic #:nodoc:
1558
1591
  # end
1559
1592
  group_method = method_class.to_s.underscore.pluralize.to_sym
1560
1593
  return group_method if group_class.reflect_on_association(group_method) # :projects
1561
-
1594
+
1562
1595
  # This is for has_many associations named differently than their class
1563
- # eg.
1596
+ # eg.
1564
1597
  # class Project
1565
1598
  # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
1566
1599
  # belongs_to :customer
@@ -1570,9 +1603,9 @@ module Formtastic #:nodoc:
1570
1603
  # end
1571
1604
  possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
1572
1605
  return possible_associations.first.name.to_sym if possible_associations.count == 1
1573
-
1606
+
1574
1607
  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"
1575
-
1608
+
1576
1609
  end
1577
1610
 
1578
1611
  # Returns a hash to be used by radio and select inputs when a boolean field
@@ -1619,19 +1652,64 @@ module Formtastic #:nodoc:
1619
1652
  @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1620
1653
  end
1621
1654
 
1655
+ # Returns the active validations for the given method or an empty Array if no validations are
1656
+ # found for the method.
1657
+ #
1658
+ # By default, the if/unless options of the validations are evaluated and only the validations
1659
+ # that should be run for the current object state are returned. Pass :all to the last
1660
+ # parameter to return :all validations regardless of if/unless options.
1661
+ #
1662
+ # Requires the ValidationReflection plugin to be present or an ActiveModel. Returns an epmty
1663
+ # Array if neither is the case.
1664
+ #
1665
+ def validations_for(method, mode = :active)
1666
+ # ActiveModel?
1667
+ validations = if @object && @object.class.respond_to?(:validators_on)
1668
+ @object.class.validators_on(method)
1669
+ else
1670
+ # ValidationReflection plugin?
1671
+ if @object && @object.class.respond_to?(:reflect_on_validations_for)
1672
+ @object.class.reflect_on_validations_for(method)
1673
+ else
1674
+ []
1675
+ end
1676
+ end
1677
+
1678
+ validations = validations.select do |validation|
1679
+ (validation.options.present? ? options_require_validation?(validation.options) : true)
1680
+ end unless mode == :all
1681
+
1682
+ return validations
1683
+ end
1684
+
1622
1685
  # Generates default_string_options by retrieving column information from
1623
1686
  # the database.
1624
1687
  #
1625
1688
  def default_string_options(method, type) #:nodoc:
1689
+ def get_maxlength_for(method)
1690
+ validation = validations_for(method).find do |validation|
1691
+ (validation.respond_to?(:macro) && validation.macro == :validates_length_of) || # Rails 2 validation
1692
+ (validation.respond_to?(:kind) && validation.kind == :length) # Rails 3 validator
1693
+ end
1694
+
1695
+ if validation
1696
+ validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
1697
+ else
1698
+ nil
1699
+ end
1700
+ end
1701
+
1702
+ validation_max_limit = get_maxlength_for(method)
1626
1703
  column = self.column_for(method)
1627
1704
 
1628
1705
  if type == :text
1629
- { :cols => self.class.default_text_field_size, :rows => self.class.default_text_area_height }
1706
+ { :rows => self.class.default_text_area_height, :cols => self.class.default_text_area_width }
1630
1707
  elsif type == :numeric || column.nil? || column.limit.nil?
1631
- { :size => self.class.default_text_field_size }
1708
+ { :maxlength => validation_max_limit,
1709
+ :size => self.class.default_text_field_size }
1632
1710
  else
1633
- { :maxlength => column.limit,
1634
- :size => self.class.default_text_field_size && [column.limit, self.class.default_text_field_size].min }
1711
+ { :maxlength => validation_max_limit || column.limit,
1712
+ :size => self.class.default_text_field_size }
1635
1713
  end
1636
1714
  end
1637
1715
 
@@ -1641,16 +1719,16 @@ module Formtastic #:nodoc:
1641
1719
  # and method names.
1642
1720
  #
1643
1721
  def generate_html_id(method_name, value='input') #:nodoc:
1644
- if options.has_key?(:index)
1645
- index = "_#{options[:index]}"
1646
- elsif defined?(@auto_index)
1647
- index = "_#{@auto_index}"
1648
- else
1649
- index = ""
1650
- end
1722
+ index = if options.has_key?(:index)
1723
+ options[:index]
1724
+ elsif defined?(@auto_index)
1725
+ @auto_index
1726
+ else
1727
+ ""
1728
+ end
1651
1729
  sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
1652
1730
 
1653
- "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1731
+ [@@custom_namespace, sanitized_object_name, index, sanitized_method_name, value].reject{|x|x.blank?}.join('_')
1654
1732
  end
1655
1733
 
1656
1734
  # Gets the nested_child_index value from the parent builder. In Rails 2.3
@@ -1698,13 +1776,13 @@ module Formtastic #:nodoc:
1698
1776
  # 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
1699
1777
  # 'formtastic.%{type}.%{model}.%{attribute}'
1700
1778
  # 'formtastic.%{type}.%{attribute}'
1701
- #
1779
+ #
1702
1780
  # Example:
1703
- #
1781
+ #
1704
1782
  # 'formtastic.labels.post.edit.title'
1705
1783
  # 'formtastic.labels.post.title'
1706
1784
  # 'formtastic.labels.title'
1707
- #
1785
+ #
1708
1786
  # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
1709
1787
  #
1710
1788
  def localized_string(key, value, type, options = {}) #:nodoc:
@@ -1720,7 +1798,9 @@ module Formtastic #:nodoc:
1720
1798
  action_name = template.params[:action].to_s rescue ''
1721
1799
  attribute_name = key.to_s
1722
1800
 
1723
- defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
1801
+ defaults = ::Formtastic::I18n::SCOPES.reject do |i18n_scope|
1802
+ nested_model_name.nil? && i18n_scope.match(/nested_model/)
1803
+ end.collect do |i18n_scope|
1724
1804
  i18n_path = i18n_scope.dup
1725
1805
  i18n_path.gsub!('%{action}', action_name)
1726
1806
  i18n_path.gsub!('%{model}', model_name)
@@ -1731,8 +1811,17 @@ module Formtastic #:nodoc:
1731
1811
  end
1732
1812
  defaults << ''
1733
1813
 
1734
- i18n_value = ::Formtastic::I18n.t(defaults.shift,
1814
+ defaults.uniq!
1815
+
1816
+ default_key = defaults.shift
1817
+ i18n_value = ::Formtastic::I18n.t(default_key,
1735
1818
  options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
1819
+ if i18n_value.blank? && type == :label
1820
+ # This is effectively what Rails label helper does for i18n lookup
1821
+ options[:scope] = [:helpers, type]
1822
+ options[:default] = defaults
1823
+ i18n_value = ::I18n.t(default_key, options)
1824
+ end
1736
1825
  i18n_value = escape_html_entities(i18n_value) if i18n_value.is_a?(::String)
1737
1826
  i18n_value.blank? ? nil : i18n_value
1738
1827
  end
@@ -1798,7 +1887,7 @@ module Formtastic #:nodoc:
1798
1887
  # <% end %>
1799
1888
  #
1800
1889
  # The above examples use a resource-oriented style of form_for() helper where only the @post
1801
- # object is given as an argument, but the generic style is also supported, as are forms with
1890
+ # object is given as an argument, but the generic style is also supported, as are forms with
1802
1891
  # inline objects (Post.new) rather than objects with instance variables (@post):
1803
1892
  #
1804
1893
  # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
@@ -1810,8 +1899,9 @@ module Formtastic #:nodoc:
1810
1899
  # <% end %>
1811
1900
  module SemanticFormHelper
1812
1901
  @@builder = ::Formtastic::SemanticFormBuilder
1813
- mattr_accessor :builder
1814
-
1902
+ @@default_form_class = 'formtastic'
1903
+ mattr_accessor :builder, :default_form_class
1904
+
1815
1905
  # Override the default ActiveRecordHelper behaviour of wrapping the input.
1816
1906
  # This gets taken care of semantically by adding an error class to the LI tag
1817
1907
  # containing the input.
@@ -1819,7 +1909,7 @@ module Formtastic #:nodoc:
1819
1909
  FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
1820
1910
  html_tag
1821
1911
  end
1822
-
1912
+
1823
1913
  def with_custom_field_error_proc(&block)
1824
1914
  default_field_error_proc = ::ActionView::Base.field_error_proc
1825
1915
  ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
@@ -1827,7 +1917,7 @@ module Formtastic #:nodoc:
1827
1917
  ensure
1828
1918
  ::ActionView::Base.field_error_proc = default_field_error_proc
1829
1919
  end
1830
-
1920
+
1831
1921
  def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
1832
1922
  options = args.extract_options!
1833
1923
  if self.respond_to? :remote_form_for
@@ -1844,18 +1934,19 @@ module Formtastic #:nodoc:
1844
1934
  options = args.extract_options!
1845
1935
  options[:builder] ||= @@builder
1846
1936
  options[:html] ||= {}
1937
+ @@builder.custom_namespace = options[:namespace].to_s
1847
1938
 
1848
1939
  singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
1849
1940
 
1850
1941
  class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1851
- class_names << "formtastic"
1942
+ class_names << @@default_form_class
1852
1943
  class_names << case record_or_name_or_array
1853
- when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1854
- when Array then singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
1855
- else singularizer.call(record_or_name_or_array.class) # @post => "post"
1944
+ when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1945
+ when Array then options[:as] || singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
1946
+ else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
1856
1947
  end
1857
1948
  options[:html][:class] = class_names.join(" ")
1858
-
1949
+
1859
1950
  with_custom_field_error_proc do
1860
1951
  #{meth}(record_or_name_or_array, *(args << options), &proc)
1861
1952
  end
@@ -1863,8 +1954,8 @@ module Formtastic #:nodoc:
1863
1954
  END_SRC
1864
1955
  end
1865
1956
  alias :semantic_remote_form_for_real :semantic_remote_form_for
1866
- alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
1957
+ alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
1867
1958
  alias :semantic_form_remote_for :semantic_remote_form_for
1868
-
1959
+
1869
1960
  end
1870
1961
  end