ShadowBelmolve-formtastic 0.1.6 → 0.2.1

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.
@@ -0,0 +1,10 @@
1
+ /* -------------------------------------------------------------------------------------------------
2
+
3
+ Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs.
4
+ This will allow you to update formtastic.css with new releases without clobbering your own changes.
5
+
6
+ For example, to make the inline hint paragraphs a little darker in color than the standard #666:
7
+
8
+ form.formtastic fieldset ol li p.inline-hints { color:#333; }
9
+
10
+ --------------------------------------------------------------------------------------------------*/
data/lib/formtastic.rb CHANGED
@@ -18,13 +18,18 @@ module Formtastic #:nodoc:
18
18
  @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
19
19
  @@inline_order = [ :input, :hints, :errors ]
20
20
  @@file_methods = [ :file?, :public_filename ]
21
+ @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
22
+ @@i18n_lookups_by_default = false
21
23
 
22
24
  cattr_accessor :default_text_field_size, :all_fields_required_by_default, :required_string,
23
25
  :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
24
- :inline_order, :file_methods
26
+ :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default
27
+
28
+ I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
29
+ '{{model}}.{{attribute}}',
30
+ '{{attribute}}']
25
31
 
26
32
  # Keeps simple mappings in a hash
27
- #
28
33
  INPUT_MAPPINGS = {
29
34
  :string => :text_field,
30
35
  :password => :password_field,
@@ -42,10 +47,11 @@ module Formtastic #:nodoc:
42
47
  # Options:
43
48
  #
44
49
  # * :as - override the input type (eg force a :string to render as a :password field)
45
- # * :label - use something other than the method name as the label (or fieldset legend) text
50
+ # * :label - use something other than the method name as the label text, when false no label is printed
46
51
  # * :required - specify if the column is required (true) or not (false)
47
52
  # * :hint - provide some text to hint or help the user provide the correct information for a field
48
53
  # * :input_html - provide options that will be passed down to the generated input
54
+ # * :wrapper_html - provide options that will be passed down to the li wrapper
49
55
  #
50
56
  # Input Types:
51
57
  #
@@ -54,8 +60,9 @@ module Formtastic #:nodoc:
54
60
  # columns all map to a single numeric_input, for example).
55
61
  #
56
62
  # * :select (a select menu for associations) - default to association names
63
+ # * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
64
+ # * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
57
65
  # * :time_zone (a select menu with time zones)
58
- # * :radio (a set of radio inputs for associations) - default to association names
59
66
  # * :password (a password input) - default for :string column types with 'password' in the method name
60
67
  # * :text (a textarea) - default for :text column types
61
68
  # * :date (a date select) - default for :date column types
@@ -64,6 +71,8 @@ module Formtastic #:nodoc:
64
71
  # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
65
72
  # * :string (a text field) - default for :string column types
66
73
  # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
74
+ # * :country (a select menu of country names) - requires a country_select plugin to be installed
75
+ # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
67
76
  #
68
77
  # Example:
69
78
  #
@@ -77,29 +86,30 @@ module Formtastic #:nodoc:
77
86
  # <% end %>
78
87
  #
79
88
  def input(method, options = {})
80
- options[:required] = method_required?(method, options[:required])
89
+ options[:required] = method_required?(method) unless options.key?(:required)
81
90
  options[:as] ||= default_input_type(method)
82
91
 
83
- options[:label] ||= if @object && @object.class.respond_to?(:human_attribute_name)
84
- @object.class.human_attribute_name(method.to_s)
85
- else
86
- method.to_s.send(@@label_str_method)
87
- end
92
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ]
93
+ html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors[method.to_sym]
94
+
95
+ wrapper_html = options.delete(:wrapper_html) || {}
96
+ wrapper_html[:id] ||= generate_html_id(method)
97
+ wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
88
98
 
89
99
  if [:boolean_select, :boolean_radio].include?(options[:as])
90
100
  ::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
91
101
  end
92
102
 
93
- html_class = [ options[:as], (options[:required] ? :required : :optional) ].join(' ')
94
- html_class << ' error' if @object && @object.respond_to?(:errors) && @object.errors.on(method.to_s)
95
-
96
- html_id = generate_html_id(method)
103
+ if options[:input_html] && options[:input_html][:id]
104
+ options[:label_html] ||= {}
105
+ options[:label_html][:for] ||= options[:input_html][:id]
106
+ end
97
107
 
98
108
  list_item_content = @@inline_order.map do |type|
99
109
  send(:"inline_#{type}_for", method, options)
100
110
  end.compact.join("\n")
101
111
 
102
- return template.content_tag(:li, list_item_content, { :id => html_id, :class => html_class })
112
+ return template.content_tag(:li, list_item_content, wrapper_html)
103
113
  end
104
114
 
105
115
  # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
@@ -241,7 +251,7 @@ module Formtastic #:nodoc:
241
251
  if @object && args.empty?
242
252
  args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
243
253
  args += @object.class.content_columns.map(&:name)
244
- args -= %w[created_at updated_at created_on updated_on]
254
+ args -= %w[created_at updated_at created_on updated_on lock_version]
245
255
  args.compact!
246
256
  end
247
257
  contents = args.map { |method| input(method.to_sym) }
@@ -279,8 +289,14 @@ module Formtastic #:nodoc:
279
289
  #
280
290
  # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
281
291
  #
282
- def commit_button(value=nil, options={})
283
- value ||= save_or_create_button_text
292
+ # And you can pass html atributes down to the input, with or without the button text:
293
+ #
294
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
295
+ # <%= form.commit_button :class => "pretty" %> => <input name="commit" type="submit" value="Save Post" class="pretty" />
296
+
297
+ def commit_button(*args)
298
+ value = args.first.is_a?(String) ? args.shift : save_or_create_button_text
299
+ options = args.shift || {}
284
300
  button_html = options.delete(:button_html) || {}
285
301
  template.content_tag(:li, self.submit(value, button_html), :class => "commit")
286
302
  end
@@ -311,6 +327,68 @@ module Formtastic #:nodoc:
311
327
  fields_for(record_or_name_or_array, *args, &block)
312
328
  end
313
329
 
330
+ # Generates the label for the input. It also accepts the same arguments as
331
+ # Rails label method. It has three options that are not supported by Rails
332
+ # label method:
333
+ #
334
+ # * :required - Appends an abbr tag if :required is true
335
+ # * :label - An alternative form to give the label content. Whenever label
336
+ # is false, a blank string is returned.
337
+ # * :as_span - When true returns a span tag with class label instead of a label element
338
+ # * :input_name - Gives the input to match for. This is needed when you want to
339
+ # to call f.label :authors but it should match :author_ids.
340
+ #
341
+ # == Examples
342
+ #
343
+ # f.label :title # like in rails, except that it searches the label on I18n API too
344
+ #
345
+ # f.label :title, "Your post title"
346
+ # f.label :title, :label => "Your post title" # Added for formtastic API
347
+ #
348
+ # f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
349
+ #
350
+ def label(method, options_or_text=nil, options=nil)
351
+ if options_or_text.is_a?(Hash)
352
+ return "" if options_or_text[:label] == false
353
+ options = options_or_text
354
+ text = options.delete(:label)
355
+ else
356
+ text = options_or_text
357
+ options ||= {}
358
+ end
359
+
360
+ text = localized_attribute_string(method, text, :label) || humanized_attribute_name(method)
361
+ text += required_or_optional_string(options.delete(:required))
362
+
363
+ input_name = options.delete(:input_name) || method
364
+ if options.delete(:as_span)
365
+ options[:class] ||= 'label'
366
+ template.content_tag(:span, text, options)
367
+ else
368
+ super(input_name, text, options)
369
+ end
370
+ end
371
+
372
+ # Generates error messages for the given method. Errors can be shown as list
373
+ # or as sentence. If :none is set, no error is shown.
374
+ #
375
+ # This method is also aliased as errors_on, so you can call on your custom
376
+ # inputs as well:
377
+ #
378
+ # semantic_form_for :post do |f|
379
+ # f.text_field(:body)
380
+ # f.errors_on(:body)
381
+ # end
382
+ #
383
+ def inline_errors_for(method, options=nil) #:nodoc:
384
+ return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
385
+
386
+ errors = @object.errors[method.to_sym]
387
+ send("error_#{@@inline_errors}", Array(errors)) unless errors.blank?
388
+ end
389
+ alias :errors_on :inline_errors_for
390
+
391
+ #
314
392
  #
315
393
  # Stolen from Attribute_fu (http://github.com/giraffesoft/attribute_fu)
316
394
  # Rails 2.3 Patches from http://github.com/odadata/attribute_fu
@@ -499,7 +577,7 @@ module Formtastic #:nodoc:
499
577
  end
500
578
  end
501
579
 
502
- private
580
+ protected
503
581
 
504
582
  def association_name(class_name)
505
583
  @object.respond_to?("#{class_name}_attributes=") ? class_name : class_name.pluralize
@@ -508,9 +586,12 @@ module Formtastic #:nodoc:
508
586
  def extract_option_or_class_name(hash, option, object)
509
587
  (hash.delete(option) || object.class.name.split('::').last.underscore)
510
588
  end
511
- # End attribute_fu magic #
512
589
 
513
- protected
590
+ # Prepare options to be sent to label
591
+ #
592
+ def options_for_label(options)
593
+ options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
594
+ end
514
595
 
515
596
  # Deals with :for option when it's supplied to inputs methods. Additional
516
597
  # options to be passed down to :for should be supplied using :for_options
@@ -571,9 +652,7 @@ module Formtastic #:nodoc:
571
652
  # * if the :required option isn't provided, and the plugin isn't available, the value of the
572
653
  # configuration option @@all_fields_required_by_default is used.
573
654
  #
574
- def method_required?(attribute, required_option) #:nodoc:
575
- return required_option unless required_option.nil?
576
-
655
+ def method_required?(attribute) #:nodoc:
577
656
  if @object && @object.class.respond_to?(:reflect_on_all_validations)
578
657
  attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
579
658
 
@@ -593,11 +672,21 @@ module Formtastic #:nodoc:
593
672
  #
594
673
  def input_simple(type, method, options)
595
674
  html_options = options.delete(:input_html) || {}
596
- html_options = default_string_options(method).merge(html_options) if STRING_MAPPINGS.include?(type)
675
+ html_options = default_string_options(method, type).merge(html_options) if STRING_MAPPINGS.include?(type)
597
676
 
598
- input_label(method, options.delete(:label), options.slice(:required)) + send(INPUT_MAPPINGS[type], method, html_options)
677
+ self.label(method, options_for_label(options)) +
678
+ self.send(INPUT_MAPPINGS[type], method, html_options)
599
679
  end
600
680
 
681
+ # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
682
+ # Additionals options can be given and will be sent straight to hidden input
683
+ # element.
684
+ #
685
+ def hidden_input(method, options)
686
+ self.hidden_field(method, set_options(options))
687
+ end
688
+
689
+
601
690
  # Outputs a label and a select box containing options from the parent
602
691
  # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
603
692
  # is has_many or has_and_belongs_to_many the select box will be set as multi-select
@@ -609,6 +698,7 @@ module Formtastic #:nodoc:
609
698
  #
610
699
  # <label for="book_author_id">Author</label>
611
700
  # <select id="book_author_id" name="book[author_id]">
701
+ # <option value=""></option>
612
702
  # <option value="1">Justin French</option>
613
703
  # <option value="2">Jane Doe</option>
614
704
  # </select>
@@ -619,6 +709,7 @@ module Formtastic #:nodoc:
619
709
  #
620
710
  # <label for="book_chapter_ids">Chapters</label>
621
711
  # <select id="book_chapter_ids" name="book[chapter_ids]">
712
+ # <option value=""></option>
622
713
  # <option value="1">Chapter 1</option>
623
714
  # <option value="2">Chapter 2</option>
624
715
  # </select>
@@ -629,15 +720,16 @@ module Formtastic #:nodoc:
629
720
  #
630
721
  # <label for="book_author_ids">Authors</label>
631
722
  # <select id="book_author_ids" name="book[author_ids]">
723
+ # <option value=""></option>
632
724
  # <option value="1">Justin French</option>
633
725
  # <option value="2">Jane Doe</option>
634
726
  # </select>
635
727
  #
636
728
  #
637
- # You can customize the options available in the select by passing in a collection (Array) of
638
- # ActiveRecord objects through the :collection option. If not provided, the choices are found
639
- # by inferring the parent's class name from the method name and simply calling find(:all) on
640
- # it (VehicleOwner.find(:all) in the example above).
729
+ # You can customize the options available in the select by passing in a collection (an Array or
730
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
731
+ # parent's class name from the method name and simply calling find(:all) on it
732
+ # (VehicleOwner.find(:all) in the example above).
641
733
  #
642
734
  # Examples:
643
735
  #
@@ -645,6 +737,7 @@ module Formtastic #:nodoc:
645
737
  # f.input :author, :collection => Author.find(:all)
646
738
  # f.input :author, :collection => [@justin, @kate]
647
739
  # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
740
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
648
741
  #
649
742
  # Note: This input looks for a label method in the parent association.
650
743
  #
@@ -676,29 +769,42 @@ module Formtastic #:nodoc:
676
769
  #
677
770
  # f.input :authors, :input_html => {:size => 20, :multiple => true}
678
771
  #
772
+ # By default, all select inputs will have a blank option at the top of the list. You can add
773
+ # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
774
+ #
679
775
  def select_input(method, options)
680
776
  collection = find_collection_for_column(method, options)
681
777
  html_options = options.delete(:input_html) || {}
682
778
 
779
+ unless options.key?(:include_blank) || options.key?(:prompt)
780
+ options[:include_blank] = true
781
+ end
782
+
683
783
  reflection = find_reflection(method)
684
784
  if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
785
+ options[:include_blank] = false
685
786
  html_options[:multiple] ||= true
686
787
  html_options[:size] ||= 5
687
788
  end
688
789
 
689
790
  input_name = generate_association_input_name(method)
690
- input_label(input_name, options.delete(:label), options.slice(:required)) +
791
+ self.label(method, options_for_label(options).merge(:input_name => input_name)) +
691
792
  self.select(input_name, collection, set_options(options), html_options)
692
793
  end
693
794
  alias :boolean_select_input :select_input
694
795
 
695
- # Output ...
796
+ # Outputs a timezone select input as Rails' time_zone_select helper. You
797
+ # can give priority zones as option.
798
+ #
799
+ # Examples:
800
+ #
801
+ # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
802
+ #
696
803
  def time_zone_input(method, options)
697
804
  html_options = options.delete(:input_html) || {}
698
805
 
699
- input_name = generate_association_input_name(method)
700
- input_label(input_name, options.delete(:label), options.slice(:required)) +
701
- self.time_zone_select(input_name, options.delete(:priority_zones), set_options(options), html_options)
806
+ self.label(method, options_for_label(options)) +
807
+ self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
702
808
  end
703
809
 
704
810
  # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
@@ -723,16 +829,17 @@ module Formtastic #:nodoc:
723
829
  # </ol>
724
830
  # </fieldset>
725
831
  #
726
- # You can customize the options available in the set by passing in a collection (Array) of
727
- # ActiveRecord objects through the :collection option. If not provided, the choices are found
728
- # by inferring the parent's class name from the method name and simply calling find(:all) on
729
- # it (VehicleOwner.find(:all) in the example above).
832
+ # You can customize the options available in the select by passing in a collection (an Array or
833
+ # Hash) through the :collection option. If not provided, the choices are found by inferring the
834
+ # parent's class name from the method name and simply calling find(:all) on it
835
+ # (Author.find(:all) in the example above).
730
836
  #
731
837
  # Examples:
732
838
  #
733
839
  # f.input :author, :as => :radio, :collection => @authors
734
840
  # f.input :author, :as => :radio, :collection => Author.find(:all)
735
841
  # f.input :author, :as => :radio, :collection => [@justin, @kate]
842
+ # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
736
843
  #
737
844
  # You can also customize the text label inside each option tag, by naming the correct method
738
845
  # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
@@ -773,9 +880,6 @@ module Formtastic #:nodoc:
773
880
  end
774
881
  alias :boolean_radio_input :radio_input
775
882
 
776
-
777
-
778
-
779
883
  # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
780
884
  # items (li), one for each fragment for the date (year, month, day). Each li contains a label
781
885
  # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
@@ -847,6 +951,7 @@ module Formtastic #:nodoc:
847
951
  time_inputs << [:second] if options[:include_seconds]
848
952
 
849
953
  list_items_capture = ""
954
+ hidden_fields_capture = ""
850
955
 
851
956
  # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
852
957
  datetime = @object ? @object.send(method) : nil
@@ -855,84 +960,100 @@ module Formtastic #:nodoc:
855
960
  (inputs + time_inputs).each do |input|
856
961
  html_id = generate_html_id(method, "#{position[input]}i")
857
962
  field_name = "#{method}(#{position[input]}i)"
858
-
859
- list_items_capture << if options["discard_#{input}".intern]
963
+ if options["discard_#{input}".intern]
860
964
  break if time_inputs.include?(input)
861
-
965
+
862
966
  hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
863
- template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
967
+ hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
864
968
  else
865
969
  opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
866
970
  item_label_text = I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
867
971
 
868
- template.content_tag(:li,
972
+ list_items_capture << template.content_tag(:li,
869
973
  template.content_tag(:label, item_label_text, :for => html_id) +
870
974
  template.send("select_#{input}".intern, datetime, opts, html_options.merge(:id => html_id))
871
975
  )
872
976
  end
873
977
  end
874
978
 
875
- field_set_and_list_wrapping_for_method(method, options, list_items_capture)
979
+ hidden_fields_capture + field_set_and_list_wrapping_for_method(method, options, list_items_capture)
876
980
  end
877
981
 
878
- # Add a list of checkboxes, like radio
879
- #
880
- # Example:
881
- # Link
882
- # belongs_to :author
883
- # belongs_to :post
884
- #
885
- # Author
886
- # has_many :links
887
- # has_many :posts, :through => :links
888
- #
889
- # Post
890
- # has_many :links
891
- # has_many :authors, :through => :links
892
- #
893
- # # posts/new.html.erb
894
- # <% semantic_form_for @post do |post| %>
895
- # <%= post.input :title %>
896
- # <h3>This post belongs to<h3>
897
- # <%= post.input :author, :as => :check_boxes %>
898
- # <% end %>
899
- #
900
- # # Output
901
- # ...
902
- # <li class="check_boxes required" id="post_authors_input">
903
- # <fieldset><legend><span class="label">Authors</span></legend>
904
- # <ol><li>
905
- # <label for="post_author_ids_1">
906
- # <input name="post[author_ids][]" type="hidden" value="" />
907
- # <input checked="checked" id="post_author_ids" name="post[author_ids][]"
908
- # type="checkbox" value="1" /> Renan T. Fernandes
909
- # </label>
910
- # </li>
911
- # ....
912
- # </ol>
913
- # </fieldset>
914
- # </li>
915
- # ....
916
- #
917
- # NOTE : Accept all radio options, plus :checked_value and :unchecked_value
918
- # NOTE²: Don't change :(un)checkd_value unless you know what you're doing !
919
- #
982
+
983
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
984
+ # items, one for each possible choice in the belongs_to association. Each li contains a
985
+ # label and a check_box input.
986
+ #
987
+ # This is an alternative for has many and has and belongs to many associations.
988
+ #
989
+ # Example:
990
+ #
991
+ # f.input :author, :as => :check_boxes
992
+ #
993
+ # Output:
994
+ #
995
+ # <fieldset>
996
+ # <legend><span>Authors</span></legend>
997
+ # <ol>
998
+ # <li>
999
+ # <input type="hidden" name="book[author_id][1]" value="">
1000
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
1001
+ # </li>
1002
+ # <li>
1003
+ # <input type="hidden" name="book[author_id][2]" value="">
1004
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
1005
+ # </li>
1006
+ # </ol>
1007
+ # </fieldset>
1008
+ #
1009
+ # Notice that the value of the checkbox is the same as the id and the hidden
1010
+ # field has empty value. You can override the hidden field value using the
1011
+ # unchecked_value option.
1012
+ #
1013
+ # You can customize the options available in the set by passing in a collection (Array) of
1014
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
1015
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
1016
+ # it (Author.find(:all) in the example above).
1017
+ #
1018
+ # Examples:
1019
+ #
1020
+ # f.input :author, :as => :check_boxes, :collection => @authors
1021
+ # f.input :author, :as => :check_boxes, :collection => Author.find(:all)
1022
+ # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
1023
+ #
1024
+ # You can also customize the text label inside each option tag, by naming the correct method
1025
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
1026
+ # by passing in the :label_method option. By default the :label_method is whichever element of
1027
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
1028
+ #
1029
+ # Examples:
1030
+ #
1031
+ # f.input :author, :as => :check_boxes, :label_method => :full_name
1032
+ # f.input :author, :as => :check_boxes, :label_method => :display_name
1033
+ # f.input :author, :as => :check_boxes, :label_method => :to_s
1034
+ # f.input :author, :as => :check_boxes, :label_method => :label
1035
+ #
1036
+ # You can set :value_as_class => true if you want that LI wrappers contains
1037
+ # a class with the wrapped checkbox input value.
1038
+ #
920
1039
  def check_boxes_input(method, options)
921
- collection = find_collection_for_column(method, options)
922
- html_options = set_options(options).merge(options.delete(:input_html) || {})
1040
+ collection = find_collection_for_column(method, options)
1041
+ html_options = options.delete(:input_html) || {}
923
1042
 
924
- input_name = generate_association_input_name(method)
925
- html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
926
- value_as_class = options.delete(:value_as_class)
1043
+ input_name = generate_association_input_name(method)
1044
+ value_as_class = options.delete(:value_as_class)
1045
+ unchecked_value = options.delete(:unchecked_value) || ''
1046
+ html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
927
1047
 
928
1048
  list_item_content = collection.map do |c|
929
1049
  label = c.is_a?(Array) ? c.first : c
930
- value = c.is_a?(Array) ? c.last : c
1050
+ value = c.is_a?(Array) ? c.last : c
931
1051
 
1052
+ html_options.merge!(:id => generate_html_id(input_name, value.to_s.downcase))
1053
+
932
1054
  li_content = template.content_tag(:label,
933
- "#{self.check_box(input_name, html_options,
934
- options.delete(:checked_value) || value, options.delete(:unchecked_value) || '')
935
- } #{label}", :for => generate_html_id(input_name, value.to_s.downcase)
1055
+ "#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
1056
+ :for => html_options[:id]
936
1057
  )
937
1058
 
938
1059
  li_options = value_as_class ? { :class => value.to_s.downcase } : {}
@@ -941,25 +1062,45 @@ module Formtastic #:nodoc:
941
1062
 
942
1063
  field_set_and_list_wrapping_for_method(method, options, list_item_content)
943
1064
  end
944
- alias :checkboxes_input :check_boxes_input
945
- alias :check_box_input :check_boxes_input
946
- alias :checkbox_input :check_boxes_input
1065
+
1066
+
1067
+ # Outputs a country select input, wrapping around a regular country_select helper.
1068
+ # Rails doesn't come with a country_select helper by default any more, so you'll need to install
1069
+ # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
1070
+ # same way.
1071
+ #
1072
+ # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
1073
+ #
1074
+ # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
1075
+ # which you can change to suit your market and user base (see README for more info on config).
1076
+ #
1077
+ # Examples:
1078
+ # f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
1079
+ # f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
1080
+ #
1081
+ def country_input(method, options)
1082
+ raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless self.respond_to?(:country_select)
1083
+
1084
+ html_options = options.delete(:input_html) || {}
1085
+ priority_countries = options.delete(:priority_countries) || @@priority_countries
947
1086
 
1087
+ self.label(method, options_for_label(options)) +
1088
+ self.country_select(method, priority_countries, set_options(options), html_options)
1089
+ end
1090
+
948
1091
 
949
1092
  # Outputs a label containing a checkbox and the label text. The label defaults
950
1093
  # to the column name (method name) and can be altered with the :label option.
951
- #
952
- # Different from other inputs, :required options has no effect here and
953
1094
  # :checked_value and :unchecked_value options are also available.
954
1095
  #
955
1096
  def boolean_input(method, options)
956
1097
  html_options = options.delete(:input_html) || {}
957
1098
 
958
- content = self.check_box(method, set_options(options).merge(html_options),
959
- options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
1099
+ input = self.check_box(method, set_options(options).merge(html_options),
1100
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
960
1101
 
961
- # required does not make sense in check box
962
- input_label(method, content + options.delete(:label), :skip_required => true)
1102
+ label = options.delete(:label) || humanized_attribute_name(method)
1103
+ self.label(method, input + label, options_for_label(options))
963
1104
  end
964
1105
 
965
1106
  # Generates an input for the given method using the type supplied with :as.
@@ -979,33 +1120,18 @@ module Formtastic #:nodoc:
979
1120
  end
980
1121
  end
981
1122
 
982
- # Generates error messages for the given method. Errors can be shown as list
983
- # or as sentence. If :none is set, no error is shown.
984
- #
985
- def inline_errors_for(method, options) #:nodoc:
986
- return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
987
-
988
- # Ruby 1.9: Strings are not Enumerable, ie no String#to_a
989
- errors = @object.errors.on(method.to_s)
990
- unless errors.respond_to?(:to_a)
991
- errors = [errors]
992
- else
993
- errors = errors.to_a
994
- end
995
-
996
- send("error_#{@@inline_errors}", errors) unless errors.empty?
997
- end
998
-
999
1123
  # Generates hints for the given method using the text supplied in :hint.
1000
1124
  #
1001
1125
  def inline_hints_for(method, options) #:nodoc:
1002
- options[:hint].blank? ? '' : template.content_tag(:p, options[:hint], :class => 'inline-hints')
1126
+ options[:hint] = localized_attribute_string(method, options[:hint], :hint)
1127
+ return if options[:hint].blank?
1128
+ template.content_tag(:p, options[:hint], :class => 'inline-hints')
1003
1129
  end
1004
1130
 
1005
1131
  # Creates an error sentence by calling to_sentence on the errors array.
1006
1132
  #
1007
1133
  def error_sentence(errors) #:nodoc:
1008
- template.content_tag(:p, errors.to_sentence, :class => 'inline-errors')
1134
+ template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
1009
1135
  end
1010
1136
 
1011
1137
  # Creates an error li list.
@@ -1013,36 +1139,28 @@ module Formtastic #:nodoc:
1013
1139
  def error_list(errors) #:nodoc:
1014
1140
  list_elements = []
1015
1141
  errors.each do |error|
1016
- list_elements << template.content_tag(:li, error)
1142
+ list_elements << template.content_tag(:li, error.untaint)
1017
1143
  end
1018
1144
  template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
1019
1145
  end
1020
1146
 
1021
- # Generates the label for the input. Accepts the same options as Rails label
1022
- # method and a fourth option that allows the label to be generated as span
1023
- # with class label.
1024
- #
1025
- def input_label(method, text, options={}, as_span=false) #:nodoc:
1026
- text << required_or_optional_string(options.delete(:required)) unless options.delete(:skip_required)
1027
-
1028
- if as_span
1029
- options[:class] ||= 'label'
1030
- template.content_tag(:span, text, options)
1031
- else
1032
- self.label(method, text, options)
1033
- end
1034
- end
1035
-
1036
1147
  # Generates the required or optional string. If the value set is a proc,
1037
1148
  # it evaluates the proc first.
1038
1149
  #
1039
1150
  def required_or_optional_string(required) #:nodoc:
1040
- string_or_proc = required ? @@required_string : @@optional_string
1151
+ string_or_proc = case required
1152
+ when true
1153
+ @@required_string
1154
+ when false
1155
+ @@optional_string
1156
+ else
1157
+ required
1158
+ end
1041
1159
 
1042
- if string_or_proc.is_a? Proc
1160
+ if string_or_proc.is_a?(Proc)
1043
1161
  string_or_proc.call
1044
1162
  else
1045
- string_or_proc
1163
+ string_or_proc.to_s
1046
1164
  end
1047
1165
  end
1048
1166
 
@@ -1077,13 +1195,15 @@ module Formtastic #:nodoc:
1077
1195
  # method. This methods is currently used by radio and datetime inputs.
1078
1196
  #
1079
1197
  def field_set_and_list_wrapping_for_method(method, options, contents)
1198
+ contents = contents.join if contents.respond_to?(:join)
1199
+
1080
1200
  template.content_tag(:fieldset,
1081
- %{<legend>#{input_label(method, options.delete(:label), options.slice(:required), true)}</legend>} +
1201
+ %{<legend>#{self.label(method, options_for_label(options).merge!(:as_span => true))}</legend>} +
1082
1202
  template.content_tag(:ol, contents)
1083
1203
  )
1084
1204
  end
1085
1205
 
1086
- # For methods that have a database column, take a best guess as to what the inout method
1206
+ # For methods that have a database column, take a best guess as to what the input method
1087
1207
  # should be. In most cases, it will just return the column type (eg :string), but for special
1088
1208
  # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
1089
1209
  # something different (like :password and :select).
@@ -1092,26 +1212,27 @@ module Formtastic #:nodoc:
1092
1212
  # default is a :string, a similar behaviour to Rails' scaffolding.
1093
1213
  #
1094
1214
  def default_input_type(method) #:nodoc:
1095
- return :string if @object.nil?
1096
-
1097
- # Find the column object by attribute
1098
1215
  column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1099
1216
 
1100
- # Associations map by default to a select
1101
- return :select if column.nil? && find_reflection(method)
1102
-
1103
1217
  if column
1104
1218
  # handle the special cases where the column type doesn't map to an input method
1105
- return :select if column.type == :integer && method.to_s =~ /_id$/
1106
- return :datetime if column.type == :timestamp
1107
- return :numeric if [:integer, :float, :decimal].include?(column.type)
1108
- return :password if column.type == :string && method.to_s =~ /password/
1219
+ return :time_zone if column.type == :string && method.to_s =~ /time_zone/
1220
+ return :select if column.type == :integer && method.to_s =~ /_id$/
1221
+ return :datetime if column.type == :timestamp
1222
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
1223
+ return :password if column.type == :string && method.to_s =~ /password/
1224
+ return :country if column.type == :string && method.to_s =~ /country/
1225
+
1109
1226
  # otherwise assume the input name will be the same as the column type (eg string_input)
1110
1227
  return column.type
1111
1228
  else
1112
- obj = @object.send(method) if @object.respond_to?(method)
1229
+ if @object
1230
+ return :select if find_reflection(method)
1231
+
1232
+ file = @object.send(method) if @object.respond_to?(method)
1233
+ return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
1234
+ end
1113
1235
 
1114
- return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
1115
1236
  return :password if method.to_s =~ /password/
1116
1237
  return :string
1117
1238
  end
@@ -1173,38 +1294,43 @@ module Formtastic #:nodoc:
1173
1294
  options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
1174
1295
  options[:value_as_class] = true unless options.key?(:value_as_class)
1175
1296
 
1176
- { options.delete(:true) => true, options.delete(:false) => false }
1297
+ [ [ options.delete(:true), true], [ options.delete(:false), false ] ]
1177
1298
  end
1178
1299
 
1179
1300
  # Used by association inputs (select, radio) to generate the name that should
1180
1301
  # be used for the input
1181
1302
  #
1182
1303
  # belongs_to :author; f.input :author; will generate 'author_id'
1304
+ # belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
1183
1305
  # has_many :authors; f.input :authors; will generate 'author_ids'
1184
1306
  # has_and_belongs_to_many will act like has_many
1185
1307
  #
1186
1308
  def generate_association_input_name(method)
1187
1309
  if reflection = find_reflection(method)
1188
- method = "#{method.to_s.singularize}_id"
1189
- method = method.pluralize if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1310
+ if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1311
+ "#{method.to_s.singularize}_ids"
1312
+ else
1313
+ reflection.options[:foreign_key] || "#{method}_id"
1314
+ end
1315
+ else
1316
+ method
1190
1317
  end
1191
- method
1192
1318
  end
1193
1319
 
1194
1320
  # If an association method is passed in (f.input :author) try to find the
1195
1321
  # reflection object.
1196
1322
  #
1197
1323
  def find_reflection(method)
1198
- object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
1324
+ @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
1199
1325
  end
1200
1326
 
1201
1327
  # Generates default_string_options by retrieving column information from
1202
1328
  # the database.
1203
1329
  #
1204
- def default_string_options(method) #:nodoc:
1330
+ def default_string_options(method, type) #:nodoc:
1205
1331
  column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
1206
1332
 
1207
- if column.nil? || column.limit.nil?
1333
+ if type == :numeric || column.nil? || column.limit.nil?
1208
1334
  { :size => @@default_text_field_size }
1209
1335
  else
1210
1336
  { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
@@ -1224,8 +1350,8 @@ module Formtastic #:nodoc:
1224
1350
  else
1225
1351
  index = ""
1226
1352
  end
1227
- sanitized_method_name = method_name.to_s.sub(/\?$/,"")
1228
-
1353
+ sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
1354
+
1229
1355
  "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
1230
1356
  end
1231
1357
 
@@ -1249,8 +1375,63 @@ module Formtastic #:nodoc:
1249
1375
  @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1250
1376
  end
1251
1377
 
1252
- end
1378
+ def humanized_attribute_name(method)
1379
+ if @object && @object.class.respond_to?(:human_attribute_name)
1380
+ @object.class.human_attribute_name(method.to_s)
1381
+ else
1382
+ method.to_s.send(@@label_str_method)
1383
+ end
1384
+ end
1253
1385
 
1386
+ # Internal generic method for looking up localized values within Formtastic
1387
+ # using I18n, if no explicit value is set and I18n-lookups are enabled.
1388
+ #
1389
+ # Enabled/Disable this by setting:
1390
+ #
1391
+ # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
1392
+ #
1393
+ # Lookup priority:
1394
+ #
1395
+ # 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
1396
+ # 'formtastic.{{type}}.{{model}}.{{attribute}}'
1397
+ # 'formtastic.{{type}}.{{attribute}}'
1398
+ #
1399
+ # Example:
1400
+ #
1401
+ # 'formtastic.labels.post.edit.title'
1402
+ # 'formtastic.labels.post.title'
1403
+ # 'formtastic.labels.title'
1404
+ #
1405
+ # NOTE: Generic, but only used for form input labels/hints.
1406
+ #
1407
+ def localized_attribute_string(attr_name, attr_value, i18n_key)
1408
+ if attr_value.is_a?(String)
1409
+ attr_value
1410
+ else
1411
+ use_i18n = attr_value.nil? ? @@i18n_lookups_by_default : attr_value
1412
+ if use_i18n
1413
+ model_name = @object.class.name.underscore
1414
+ action_name = template.params[:action].to_s rescue ''
1415
+ attribute_name = attr_name.to_s
1416
+
1417
+ defaults = I18N_SCOPES.collect do |i18n_scope|
1418
+ i18n_path = i18n_scope.dup
1419
+ i18n_path.gsub!('{{action}}', action_name)
1420
+ i18n_path.gsub!('{{model}}', model_name)
1421
+ i18n_path.gsub!('{{attribute}}', attribute_name)
1422
+ i18n_path.gsub!('..', '.')
1423
+ i18n_path.to_sym
1424
+ end
1425
+ defaults << ''
1426
+
1427
+ i18n_value = ::I18n.t(defaults.shift, :default => defaults,
1428
+ :scope => "formtastic.#{i18n_key.to_s.pluralize}")
1429
+ i18n_value.blank? ? nil : i18n_value
1430
+ end
1431
+ end
1432
+ end
1433
+
1434
+ end
1254
1435
 
1255
1436
  # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
1256
1437
  #