formtastic 1.1.0 → 1.2.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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