ShadowBelmolve-formtastic 0.1.6 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +179 -40
- data/Rakefile +1 -1
- data/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +21 -0
- data/generators/formtastic_stylesheets/templates/formtastic.css +136 -0
- data/generators/formtastic_stylesheets/templates/formtastic_changes.css +10 -0
- data/lib/formtastic.rb +351 -170
- data/lib/justin_french/formtastic.rb +3 -3
- data/spec/formtastic_spec.rb +775 -274
- metadata +9 -6
- data/spec/spec.opts +0 -2
@@ -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
|
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
|
89
|
+
options[:required] = method_required?(method) unless options.key?(:required)
|
81
90
|
options[:as] ||= default_input_type(method)
|
82
91
|
|
83
|
-
options[:
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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,
|
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
|
-
|
283
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
638
|
-
#
|
639
|
-
#
|
640
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
|
700
|
-
|
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
|
727
|
-
#
|
728
|
-
#
|
729
|
-
#
|
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
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
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
|
922
|
-
html_options =
|
1040
|
+
collection = find_collection_for_column(method, options)
|
1041
|
+
html_options = options.delete(:input_html) || {}
|
923
1042
|
|
924
|
-
input_name
|
925
|
-
|
926
|
-
|
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
|
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
|
-
|
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
|
-
|
945
|
-
|
946
|
-
|
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
|
-
|
959
|
-
|
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
|
-
|
962
|
-
|
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]
|
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
|
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?
|
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>#{
|
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
|
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 :
|
1106
|
-
return :
|
1107
|
-
return :
|
1108
|
-
return :
|
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
|
-
|
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
|
-
|
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
|
-
|
1189
|
-
|
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.
|
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
|
-
|
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
|
#
|