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.
- 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
|
#
|