formtastic 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -91,20 +91,28 @@ Then install the Formtastic gem:
91
91
  sudo gem install formtastic
92
92
  </pre>
93
93
 
94
+ And add it to your environment.rb configuration as a gem dependency:
95
+
96
+ <pre>
97
+ config.gem 'formtastic'
98
+ </pre>
99
+
94
100
  Optionally, run @./script/generate formtastic@ to copy the following files into your app:
95
101
 
96
102
  * @config/initializers/formtastic.rb@ - a commented out Formtastic config initializer
97
103
  * @public/stylesheets/formtastic.css@
98
104
  * @public/stylesheets/formtastic_changes.css@
99
105
 
100
- A proof-of-concept stylesheet is provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet so that the Formtastic styles can be updated without clobbering your changes. If you want to use these stylesheets, add both to your layout:
106
+ A proof-of-concept stylesheet is provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet so that the Formtastic styles can be updated without clobbering your changes. If you want to use these stylesheets, add both to your layout with this helper:
101
107
 
102
108
  <pre>
103
- <%= stylesheet_link_tag "formtastic" %>
104
- <%= stylesheet_link_tag "formtastic_changes" %>
109
+ <head>
110
+ ...
111
+ <%= formtastic_stylesheet_link_tag %>
112
+ ...
113
+ </head>
105
114
  </pre>
106
115
 
107
-
108
116
  h2. Usage
109
117
 
110
118
  Forms are really boring to code... you want to get onto the good stuff as fast as possible.
@@ -165,7 +173,7 @@ If you want to customize the label text, or render some hint text below the fiel
165
173
  <% end %>
166
174
  </pre>
167
175
 
168
- Nested forms (Rails 2.3) are also supported. You can do it in the Rails way:
176
+ Nested forms (Rails 2.3) are also supported (don't forget your models need to be setup correctly with accepts_nested_attributes_for – search the Rails docs). You can do it in the Rails way:
169
177
 
170
178
  <pre>
171
179
  <% semantic_form_for @post do |form| %>
@@ -231,6 +239,23 @@ Customize the HTML attributes for the @<li>@ wrapper around every input with the
231
239
  <% end %>
232
240
  </pre>
233
241
 
242
+ Many inputs provide a collection of options to choose from (like @:select@, @:radio@, @:check_boxes@, @:boolean@). In many cases, Formtastic can find choices through the model associations, but if you want to use your own set of choices, the @:collection@ option is what you want. You can pass in an Array of objects, an array of Strings, a Hash... Throw almost anything at it! Examples:
243
+
244
+ <pre>
245
+ f.input :authors, :as => :check_boxes, :collection => User.find(:all, :order => "last_name ASC")
246
+ f.input :authors, :as => :check_boxes, :collection => current_user.company.users.active
247
+ f.input :authors, :as => :check_boxes, :collection => [@justin, @kate]
248
+ f.input :authors, :as => :check_boxes, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
249
+ f.input :author, :as => :select, :collection => Author.find(:all)
250
+ f.input :author, :as => :select, :collection => { @justin.name => @justin.id, @kate.name => @kate.id }
251
+ f.input :author, :as => :select, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
252
+ f.input :author, :as => :radio, :collection => User.find(:all)
253
+ f.input :author, :as => :radio, :collection => [@justin, @kate]
254
+ f.input :author, :as => :radio, :collection => { @justin.name => @justin.id, @kate.name => @kate.id }
255
+ f.input :author, :as => :radio, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
256
+ f.input :admin, :as => :radio, :collection => ["Yes!", "No"]
257
+ </pre>
258
+
234
259
 
235
260
  h2. The Available Inputs
236
261
 
@@ -252,7 +277,19 @@ The Formtastic input types:
252
277
  * @:country@ - a select menu of country names. Default for column types: :string with name @"country"@ - requires a *country_select* plugin to be installed.
253
278
  * @:hidden@ - a hidden field. Creates a hidden field (added for compatibility).
254
279
 
255
- The documentation is pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.
280
+ The comments in the code are pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.
281
+
282
+
283
+ h2. Delegation for label lookups
284
+
285
+ Formtastic decides which label to use in the following order:
286
+
287
+ <pre>
288
+ 1. :label # :label => "Choose Title"
289
+ 2. Formtastic i18n # if either :label => true || i18n_lookups_by_default = true (see Internationalization)
290
+ 3. Activerecord i18n # if localization file found for the given attribute
291
+ 4. label_str_method # if nothing provided this defaults to :humanize but can be set to a custom method
292
+ </pre>
256
293
 
257
294
  h2. Internationalization (I18n)
258
295
 
@@ -406,6 +443,17 @@ For more flexible forms; Formtastic find translations using a bottom-up approach
406
443
  Values for @labels@/@hints@/@actions@ are can take values: @String@ (explicit value), @Symbol@ (i18n-lookup-key relative to the current "type", e.g. actions:), @true@ (force I18n lookup), @false@ (force no I18n lookup). Titles (legends) can only take: @String@ and @Symbol@ - true/false have no meaning.
407
444
 
408
445
 
446
+ h2. Semantic errors
447
+
448
+ You can show errors on base (by default) and any other attribute just passing it name to semantic_errors method:
449
+
450
+ <pre>
451
+ <% semantic_form_for @post do |form| %>
452
+ <%= form.semantic_errors :state %>
453
+ <% end %>
454
+ </pre>
455
+
456
+
409
457
  h2. ValidationReflection plugin
410
458
 
411
459
  If you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin installed, you won't have to specify the @:required@ option (it checks the validations on the model instead).
@@ -428,10 +476,10 @@ $ ./script/generate form Post
428
476
  # GENERATED FORMTASTIC CODE
429
477
  # ---------------------------------------------------------
430
478
 
431
- <% form.inputs do %>
432
- <%= form.input :title, :label => 'Title' %>
433
- <%= form.input :body, :label => 'Body' %>
434
- <%= form.input :published, :label => 'Published' %>
479
+ <% f.inputs do %>
480
+ <%= f.input :title, :label => 'Title' %>
481
+ <%= f.input :body, :label => 'Body' %>
482
+ <%= f.input :published, :label => 'Published' %>
435
483
  <% end %>
436
484
 
437
485
  # ---------------------------------------------------------
@@ -497,9 +545,18 @@ Well...there's a TextMate-bundle in town, dedicated to make usage of Formtastic
497
545
  "Formtastic.tmbundle":http://github.com/grimen/formtastic_tmbundle
498
546
 
499
547
 
500
- h2. Contributors
548
+ h2. How to contribute
549
+
550
+ *Before you send a pull request*, please ensure that you provide appropriate spec/test coverage and ensure the documentation is up-to-date. Bonus points if you perform your changes in a clean topic branch rather than master.
551
+
552
+ Please also keep your commits *atomic* so that they are more likely to apply cleanly. That means that each commit should contain the smallest possible logical change. Don't commit two features at once, don't update the gemspec at the same time you add a feature, don't fix a whole bunch of whitespace in a file at the same time you change a few lines, etc, etc.
553
+
554
+ For significant changes, you may wish to discuss your idea on the Formtastic Google group before coding to ensure that your change is likely to be accepted. Formtastic relies heavily on i18n, so if you're unsure of the impact this has on your changes, please discuss them with the group.
555
+
556
+
557
+ h2. Maintainers & Contributors
501
558
 
502
- Formtastic is maintained by "Justin French":http://justinfrench.com, "José Valim":http://github.com/josevalim and "Jonas Grimfelt":http://github.com/grimen, but it wouldn't be as awesome as it is today without help from over 30 contributors.
559
+ Formtastic is maintained by "Justin French":http://justinfrench.com, "José Valim":http://github.com/josevalim and "Jonas Grimfelt":http://github.com/grimen, but it wouldn't be as awesome as it is today without help from over 40 contributors.
503
560
 
504
561
  @git shortlog -n -s --no-merges@
505
562
 
data/Rakefile CHANGED
@@ -27,7 +27,7 @@ begin
27
27
  a config initializer into your application:
28
28
  ./script/generate formtastic
29
29
 
30
- To generate some semantic form markup for your exisiting models, just run:
30
+ To generate some semantic form markup for your existing models, just run:
31
31
  ./script/generate form MODEL_NAME
32
32
 
33
33
  Find out more and get involved:
@@ -1,5 +1,5 @@
1
- <%% form.inputs do %>
1
+ <%% f.inputs do %>
2
2
  <% attributes.each do |attribute| -%>
3
- <%%= form.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>' %>
3
+ <%%= f.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>' %>
4
4
  <% end -%>
5
5
  <%% end %>
@@ -1,4 +1,4 @@
1
- - form.inputs do
1
+ - f.inputs do
2
2
  <% attributes.each do |attribute| -%>
3
- = form.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>'
3
+ = f.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>'
4
4
  <% end -%>
@@ -19,7 +19,13 @@ form.formtastic ol, form.formtastic ul { list-style:none; }
19
19
  form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; }
20
20
  form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; }
21
21
  form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; }
22
- form.formtastic legend { color:#000; }
22
+ form.formtastic legend { white-space:normal; color:#000; }
23
+
24
+
25
+ /* SEMANTIC ERRORS
26
+ --------------------------------------------------------------------------------------------------*/
27
+ form.formtastic ul.errors { color:#cc0000; margin:0.5em 0 1.5em 25%; list-style:square; }
28
+ form.formtastic ul.errors li { padding:0; border:none; display:list-item; }
23
29
 
24
30
 
25
31
  /* FIELDSETS & LISTS
@@ -39,100 +45,102 @@ html[xmlns] form.formtastic fieldset { display: block; }
39
45
 
40
46
  /* INPUT LIs
41
47
  --------------------------------------------------------------------------------------------------*/
42
- form.formtastic fieldset ol li { margin-bottom:1.5em; }
48
+ form.formtastic fieldset > ol > li { margin-bottom:1.5em; }
43
49
 
44
50
  /* clearfixing the li's */
45
- form.formtastic fieldset ol li { display: inline-block; }
46
- form.formtastic fieldset ol li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
47
- html[xmlns] form.formtastic fieldset ol li { display: block; }
48
- * html form.formtastic fieldset ol li { height: 1%; }
49
-
50
- form.formtastic fieldset ol li.required { }
51
- form.formtastic fieldset ol li.optional { }
52
- form.formtastic fieldset ol li.error { }
51
+ form.formtastic fieldset > ol > li { display: inline-block; }
52
+ form.formtastic fieldset > ol > li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
53
+ html[xmlns] form.formtastic fieldset > ol > li { display: block; }
54
+ * html form.formtastic fieldset > ol > li { height: 1%; }
55
+
56
+ form.formtastic fieldset > ol > li.required { }
57
+ form.formtastic fieldset > ol > li.optional { }
58
+ form.formtastic fieldset > ol > li.error { }
53
59
 
54
60
 
55
61
  /* LABELS
56
62
  --------------------------------------------------------------------------------------------------*/
57
- form.formtastic fieldset ol li label { display:block; width:25%; float:left; padding-top:.2em; }
58
- form.formtastic fieldset ol li li label { line-height:100%; padding-top:0; }
59
- form.formtastic fieldset ol li li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;}
63
+ form.formtastic fieldset > ol > li label { display:block; width:25%; float:left; padding-top:.2em; }
64
+ form.formtastic fieldset > ol > li > li label { line-height:100%; padding-top:0; }
65
+ form.formtastic fieldset > ol > li > li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;}
60
66
 
61
67
 
62
68
  /* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets)
63
69
  --------------------------------------------------------------------------------------------------*/
64
- form.formtastic fieldset ol li fieldset { position:relative; }
65
- form.formtastic fieldset ol li fieldset legend { position:absolute; width:25%; padding-top:0.1em; }
66
- form.formtastic fieldset ol li fieldset legend span { position:absolute; }
67
- form.formtastic fieldset ol li fieldset legend.label label { position:absolute; }
68
- form.formtastic fieldset ol li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
69
- form.formtastic fieldset ol li fieldset ol li { padding:0; border:0; }
70
+ form.formtastic fieldset > ol > li fieldset { position:relative; }
71
+ form.formtastic fieldset > ol > li fieldset legend { position:absolute; width:95%; padding-top:0.1em; left: 0px; }
72
+ form.formtastic fieldset > ol > li fieldset legend span { position:absolute; }
73
+ form.formtastic fieldset > ol > li fieldset legend.label label { position:absolute; }
74
+ form.formtastic fieldset > ol > li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
75
+ form.formtastic fieldset > ol > li fieldset ol li { padding:0; border:0; }
70
76
 
71
77
 
72
78
  /* INLINE HINTS
73
79
  --------------------------------------------------------------------------------------------------*/
74
- form.formtastic fieldset ol li p.inline-hints { color:#666; margin:0.5em 0 0 25%; }
80
+ form.formtastic fieldset > ol > li p.inline-hints { color:#666; margin:0.5em 0 0 25%; }
75
81
 
76
82
 
77
83
  /* INLINE ERRORS
78
84
  --------------------------------------------------------------------------------------------------*/
79
- form.formtastic fieldset ol li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; }
80
- form.formtastic fieldset ol li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; }
81
- form.formtastic fieldset ol li ul.errors li { padding:0; border:none; display:list-item; }
85
+ form.formtastic fieldset > ol > li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; }
86
+ form.formtastic fieldset > ol > li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; }
87
+ form.formtastic fieldset > ol > li ul.errors li { padding:0; border:none; display:list-item; }
82
88
 
83
89
 
84
90
  /* STRING & NUMERIC OVERRIDES
85
91
  --------------------------------------------------------------------------------------------------*/
86
- form.formtastic fieldset ol li.string input { width:74%; }
87
- form.formtastic fieldset ol li.password input { width:74%; }
88
- form.formtastic fieldset ol li.numeric input { width:74%; }
92
+ form.formtastic fieldset > ol > li.string input { max-width:74%; }
93
+ form.formtastic fieldset > ol > li.password input { max-width: 13em; }
94
+ form.formtastic fieldset > ol > li.numeric input { max-width:74%; }
89
95
 
90
96
 
91
97
  /* TEXTAREA OVERRIDES
92
98
  --------------------------------------------------------------------------------------------------*/
93
- form.formtastic fieldset ol li.text textarea { width:74%; }
99
+ form.formtastic fieldset > ol > li.text textarea { width:74%; }
94
100
 
95
101
 
96
102
  /* HIDDEN OVERRIDES
103
+ The dual declarations are required because of our clearfix display hack on the LIs, which is more
104
+ specific than the more general rule below. TODO: Revist the clearing hack and this rule.
97
105
  --------------------------------------------------------------------------------------------------*/
98
- form.formtastic fieldset ol li.hidden { display:none; }
99
-
106
+ form.formtastic fieldset ol li.hidden,
107
+ html[xmlns] form.formtastic fieldset ol li.hidden { display:none; }
100
108
 
101
109
  /* BOOLEAN OVERRIDES
102
110
  --------------------------------------------------------------------------------------------------*/
103
- form.formtastic fieldset ol li.boolean label { padding-left:25%; width:auto; }
104
- form.formtastic fieldset ol li.boolean label input { margin:0 0.5em 0 0.2em; }
111
+ form.formtastic fieldset > ol > li.boolean label { padding-left:25%; width:auto; }
112
+ form.formtastic fieldset > ol > li.boolean label input { margin:0 0.5em 0 0.2em; }
105
113
 
106
114
 
107
115
  /* RADIO OVERRIDES
108
116
  --------------------------------------------------------------------------------------------------*/
109
- form.formtastic fieldset ol li.radio { }
110
- form.formtastic fieldset ol li.radio fieldset ol { margin-bottom:-0.6em; }
111
- form.formtastic fieldset ol li.radio fieldset ol li { margin:0.1em 0 0.5em 0; }
112
- form.formtastic fieldset ol li.radio fieldset ol li label { float:none; width:100%; }
113
- form.formtastic fieldset ol li.radio fieldset ol li label input { margin-right:0.2em; }
117
+ form.formtastic fieldset > ol > li.radio { }
118
+ form.formtastic fieldset > ol > li.radio fieldset ol { margin-bottom:-0.6em; }
119
+ form.formtastic fieldset > ol > li.radio fieldset ol li { margin:0.1em 0 0.5em 0; }
120
+ form.formtastic fieldset > ol > li.radio fieldset ol li label { float:none; width:100%; }
121
+ form.formtastic fieldset > ol > li.radio fieldset ol li label input { margin-right:0.2em; }
114
122
 
115
123
 
116
124
  /* CHECK BOXES (COLLECTION) OVERRIDES
117
125
  --------------------------------------------------------------------------------------------------*/
118
- form.formtastic fieldset ol li.check_boxes { }
119
- form.formtastic fieldset ol li.check_boxes fieldset ol { margin-bottom:-0.6em; }
120
- form.formtastic fieldset ol li.check_boxes fieldset ol li { margin:0.1em 0 0.5em 0; }
121
- form.formtastic fieldset ol li.check_boxes fieldset ol li label { float:none; width:100%; }
122
- form.formtastic fieldset ol li.check_boxes fieldset ol li label input { margin-right:0.2em; }
126
+ form.formtastic fieldset > ol > li.check_boxes { }
127
+ form.formtastic fieldset > ol > li.check_boxes fieldset ol { margin-bottom:-0.6em; }
128
+ form.formtastic fieldset > ol > li.check_boxes fieldset ol li { margin:0.1em 0 0.5em 0; }
129
+ form.formtastic fieldset > ol > li.check_boxes fieldset ol li label { float:none; width:100%; }
130
+ form.formtastic fieldset > ol > li.check_boxes fieldset ol li label input { margin-right:0.2em; }
123
131
 
124
132
 
125
133
 
126
134
  /* DATE & TIME OVERRIDES
127
135
  --------------------------------------------------------------------------------------------------*/
128
- form.formtastic fieldset ol li.date fieldset ol li,
129
- form.formtastic fieldset ol li.time fieldset ol li,
130
- form.formtastic fieldset ol li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; }
136
+ form.formtastic fieldset > ol > li.date fieldset ol li,
137
+ form.formtastic fieldset > ol > li.time fieldset ol li,
138
+ form.formtastic fieldset > ol > li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; }
131
139
 
132
- form.formtastic fieldset ol li.date fieldset ol li label,
133
- form.formtastic fieldset ol li.time fieldset ol li label,
134
- form.formtastic fieldset ol li.datetime fieldset ol li label { display:none; }
140
+ form.formtastic fieldset > ol > li.date fieldset ol li label,
141
+ form.formtastic fieldset > ol > li.time fieldset ol li label,
142
+ form.formtastic fieldset > ol > li.datetime fieldset ol li label { display:none; }
135
143
 
136
- form.formtastic fieldset ol li.date fieldset ol li label input,
137
- form.formtastic fieldset ol li.time fieldset ol li label input,
138
- form.formtastic fieldset ol li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; }
144
+ form.formtastic fieldset > ol > li.date fieldset ol li label input,
145
+ form.formtastic fieldset > ol > li.time fieldset ol li label input,
146
+ form.formtastic fieldset > ol > li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; }
@@ -1,6 +1,9 @@
1
1
  # Set the default text field size when input is a string. Default is 50.
2
2
  # Formtastic::SemanticFormBuilder.default_text_field_size = 50
3
3
 
4
+ # Set the default text area height when input is a text. Default is 20.
5
+ # Formtastic::SemanticFormBuilder.default_text_area_height = 5
6
+
4
7
  # Should all fields be considered "required" by default?
5
8
  # Defaults to true, see ValidationReflection notes below.
6
9
  # Formtastic::SemanticFormBuilder.all_fields_required_by_default = true
@@ -5,6 +5,10 @@ This will allow you to update formtastic.css with new releases without clobberin
5
5
 
6
6
  For example, to make the inline hint paragraphs a little darker in color than the standard #666:
7
7
 
8
- form.formtastic fieldset ol li p.inline-hints { color:#333; }
8
+ form.formtastic fieldset > ol > li p.inline-hints { color:#333; }
9
+
10
+ HINT:
11
+ The following style may be *conditionally* included for improved support on older versions of IE(<8)
12
+ form.formtastic fieldset ol li fieldset legend { margin-left: -6px;}
9
13
 
10
14
  --------------------------------------------------------------------------------------------------*/
data/lib/formtastic.rb CHANGED
@@ -6,6 +6,7 @@ module Formtastic #:nodoc:
6
6
  class SemanticFormBuilder < ActionView::Helpers::FormBuilder
7
7
 
8
8
  @@default_text_field_size = 50
9
+ @@default_text_area_height = 20
9
10
  @@all_fields_required_by_default = true
10
11
  @@include_blank_for_select_by_default = true
11
12
  @@required_string = proc { %{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>} }
@@ -14,12 +15,12 @@ module Formtastic #:nodoc:
14
15
  @@label_str_method = :humanize
15
16
  @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
16
17
  @@inline_order = [ :input, :hints, :errors ]
17
- @@file_methods = [ :file?, :public_filename ]
18
+ @@file_methods = [ :file?, :public_filename, :filename ]
18
19
  @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
19
20
  @@i18n_lookups_by_default = false
20
21
  @@default_commit_button_accesskey = nil
21
22
 
22
- cattr_accessor :default_text_field_size, :all_fields_required_by_default, :include_blank_for_select_by_default,
23
+ cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
23
24
  :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
24
25
  :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
25
26
 
@@ -75,6 +76,13 @@ module Formtastic #:nodoc:
75
76
  # <% end %>
76
77
  #
77
78
  def input(method, options = {})
79
+ if options.key?(:selected) || options.key?(:checked) || options.key?(:default)
80
+ ::ActiveSupport::Deprecation.warn(
81
+ "The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " <<
82
+ "Please set default values in your models (using an after_initialize callback) or in your controller set-up. " <<
83
+ "See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller)
84
+ end
85
+
78
86
  options[:required] = method_required?(method) unless options.key?(:required)
79
87
  options[:as] ||= default_input_type(method, options)
80
88
 
@@ -91,7 +99,7 @@ module Formtastic #:nodoc:
91
99
  end
92
100
 
93
101
  input_parts = @@inline_order.dup
94
- input_parts.delete(:errors) if options[:as] == :hidden
102
+ input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
95
103
 
96
104
  list_item_content = input_parts.map do |type|
97
105
  send(:"inline_#{type}_for", method, options)
@@ -310,9 +318,17 @@ module Formtastic #:nodoc:
310
318
  options = args.extract_options!
311
319
  text = options.delete(:label) || args.shift
312
320
 
313
- if @object
321
+ if @object && @object.respond_to?(:new_record?)
314
322
  key = @object.new_record? ? :create : :update
315
- object_name = @object.class.human_name
323
+
324
+ # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
325
+ # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
326
+ # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
327
+ # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
328
+ object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
329
+ crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
330
+ decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
331
+ object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
316
332
  else
317
333
  key = :submit
318
334
  object_name = @object_name.to_s.send(@@label_str_method)
@@ -414,6 +430,32 @@ module Formtastic #:nodoc:
414
430
  end
415
431
  alias :errors_on :inline_errors_for
416
432
 
433
+ # Generates error messages for given method names and for base.
434
+ # You can pass a hash with html options that will be added to ul tag
435
+ #
436
+ # == Examples
437
+ #
438
+ # f.semantic_errors # This will show only errors on base
439
+ # f.semantic_errors :state # This will show errors on base and state
440
+ # f.semantic_errors :state, :class => "awesome" # errors will be rendered in ul.awesome
441
+ #
442
+ def semantic_errors(*args)
443
+ html_options = args.extract_options!
444
+ full_errors = args.inject([]) do |array, method|
445
+ attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
446
+ errors = Array(@object.errors[method.to_sym]).to_sentence
447
+ errors.present? ? array << [attribute, errors].join(" ") : array ||= []
448
+ end
449
+ full_errors << @object.errors.on_base
450
+ full_errors.flatten!
451
+ full_errors.compact!
452
+ return nil if full_errors.blank?
453
+ html_options[:class] ||= "errors"
454
+ template.content_tag(:ul, html_options) do
455
+ full_errors.map { |error| template.content_tag(:li, error) }.join
456
+ end
457
+ end
458
+
417
459
  protected
418
460
 
419
461
  def render_inline_errors?
@@ -523,7 +565,7 @@ module Formtastic #:nodoc:
523
565
 
524
566
  def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
525
567
  html_options = options.delete(:input_html) || {}
526
- html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password].include?(type)
568
+ html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text].include?(type)
527
569
 
528
570
  self.label(method, options_for_label(options)) <<
529
571
  self.send(form_helper_method, method, html_options)
@@ -825,13 +867,15 @@ module Formtastic #:nodoc:
825
867
  template.content_tag(:li, li_content, li_options)
826
868
  end
827
869
 
828
- field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_item_content)
870
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
829
871
  end
830
872
  alias :boolean_radio_input :radio_input
831
873
 
832
874
  # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
833
875
  # items (li), one for each fragment for the date (year, month, day). Each li contains a label
834
- # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
876
+ # (eg "Year") and a select box. Overwriting the label is possible by adding the :labels option.
877
+ # :labels should be a hash with the field (e.g. day) as key and the label text as value.
878
+ # See date_or_datetime_input for a more detailed output example.
835
879
  #
836
880
  # You can pre-select a specific option value by passing in the :selected option.
837
881
  #
@@ -839,9 +883,10 @@ module Formtastic #:nodoc:
839
883
  #
840
884
  # f.input :created_at, :as => :date, :selected => 1.day.ago
841
885
  # f.input :created_at, :as => :date, :selected => nil # override any defaults: select none
886
+ # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day" }
842
887
  #
843
- # Some of Rails' options for select_date are supported, but not everything yet.
844
- #
888
+ # Some of Rails' options for select_date are supported, but not everything yet, see
889
+ # documentation of date_or_datetime_input() for more information.
845
890
  def date_input(method, options)
846
891
  options = set_include_blank(options)
847
892
  date_or_datetime_input(method, options.merge(:discard_hour => true))
@@ -849,8 +894,9 @@ module Formtastic #:nodoc:
849
894
 
850
895
  # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
851
896
  # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
852
- # contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
853
- # detailed output example.
897
+ # contains a label (eg "Year") and a select box. Overwriting the label is possible by adding
898
+ # the :labels option. :labels should be a hash with the field (e.g. day) as key and the label
899
+ # text as value. See date_or_datetime_input for a more detailed output example.
854
900
  #
855
901
  # You can pre-select a specific option value by passing in the :selected option.
856
902
  #
@@ -858,9 +904,11 @@ module Formtastic #:nodoc:
858
904
  #
859
905
  # f.input :created_at, :as => :datetime, :selected => 1.day.ago
860
906
  # f.input :created_at, :as => :datetime, :selected => nil # override any defaults: select none
907
+ # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day",
908
+ # :hour => "Hour", :minute => "Minute" }
861
909
  #
862
- # Some of Rails' options for select_date are supported, but not everything yet.
863
- #
910
+ # Some of Rails' options for select_date are supported, but not everything yet, see
911
+ # documentation of date_or_datetime_input() for more information.
864
912
  def datetime_input(method, options)
865
913
  options = set_include_blank(options)
866
914
  date_or_datetime_input(method, options)
@@ -868,7 +916,9 @@ module Formtastic #:nodoc:
868
916
 
869
917
  # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
870
918
  # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
871
- # (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
919
+ # (eg "Hour") and a select box. Overwriting the label is possible by adding the :labels option.
920
+ # :labels should be a hash with the field (e.g. day) as key and the label text as value.
921
+ # See date_or_datetime_input for a more detailed output example.
872
922
  #
873
923
  # You can pre-select a specific option value by passing in the :selected option.
874
924
  #
@@ -876,14 +926,19 @@ module Formtastic #:nodoc:
876
926
  #
877
927
  # f.input :created_at, :as => :time, :selected => 1.hour.ago
878
928
  # f.input :created_at, :as => :time, :selected => nil # override any defaults: select none
929
+ # f.input :created_at, :as => :date, :labels => { :hour => "Hour", :minute => "Minute" }
879
930
  #
880
- # Some of Rails' options for select_time are supported, but not everything yet.
881
- #
931
+ # Some of Rails' options for select_time are supported, but not everything yet, see
932
+ # documentation of date_or_datetime_input() for more information.
882
933
  def time_input(method, options)
883
934
  options = set_include_blank(options)
884
935
  date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
885
936
  end
886
-
937
+
938
+ # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
939
+ # legend (for what would normally be considered the label), and an ordered list of list items
940
+ # for year, month, day, hour, etc, each containing a label and a select. Example:
941
+ #
887
942
  # <fieldset>
888
943
  # <legend>Created At</legend>
889
944
  # <ol>
@@ -916,23 +971,32 @@ module Formtastic #:nodoc:
916
971
  #
917
972
  # This is an absolute abomination, but so is the official Rails select_date().
918
973
  #
974
+ # Options:
975
+ #
976
+ # * @:order => [:month, :day, :year]@
977
+ # * @:include_seconds@ => true@
978
+ # * @:selected => Time.mktime(2008)@
979
+ # * @:selected => Date.new(2008)@
980
+ # * @:selected => nil@
981
+ # * @:discard_(year|month|day|hour|minute) => true@
982
+ # * @:include_blank => true@
983
+ # * @:labels => {}@
919
984
  def date_or_datetime_input(method, options)
920
985
  position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
921
986
  i18n_date_order = ::I18n.t(:order, :scope => [:date])
922
987
  i18n_date_order = nil unless i18n_date_order.is_a?(Array)
923
988
  inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
989
+ labels = options.delete(:labels) || {}
924
990
 
925
991
  time_inputs = [:hour, :minute]
926
- time_inputs << [:second] if options[:include_seconds]
992
+ time_inputs << :second if options[:include_seconds]
927
993
 
928
994
  list_items_capture = ""
929
995
  hidden_fields_capture = ""
930
996
 
931
- default_time = ::Time.now
997
+ datetime = options.key?(:selected) ? options[:selected] : Time.now # can't do an || because nil is an important value
998
+ datetime = @object.send(method) if @object && @object.send(method) # object trumps :selected
932
999
 
933
- # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
934
- datetime = options[:selected] || (@object ? @object.send(method) : default_time) || default_time
935
-
936
1000
  html_options = options.delete(:input_html) || {}
937
1001
  input_ids = []
938
1002
 
@@ -943,15 +1007,16 @@ module Formtastic #:nodoc:
943
1007
  if options[:"discard_#{input}"]
944
1008
  break if time_inputs.include?(input)
945
1009
 
946
- hidden_value = datetime.respond_to?(input) ? datetime.send(input.to_sym) : datetime
1010
+ hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
947
1011
  hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
948
1012
  else
949
1013
  opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
950
- item_label_text = ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
1014
+ item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
951
1015
 
952
- list_items_capture << template.content_tag(:li,
953
- template.content_tag(:label, item_label_text, :for => input_id) <<
954
- template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
1016
+ list_items_capture << template.content_tag(:li, [
1017
+ !item_label_text.blank? ? template.content_tag(:label, item_label_text, :for => input_id) : "",
1018
+ template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
1019
+ ].join("")
955
1020
  )
956
1021
  end
957
1022
  end
@@ -1064,7 +1129,7 @@ module Formtastic #:nodoc:
1064
1129
  template.content_tag(:li, li_content, li_options)
1065
1130
  end
1066
1131
 
1067
- field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_item_content)
1132
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
1068
1133
  end
1069
1134
 
1070
1135
  # Outputs a country select input, wrapping around a regular country_select helper.
@@ -1406,7 +1471,7 @@ module Formtastic #:nodoc:
1406
1471
  if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
1407
1472
  "#{method.to_s.singularize}_ids"
1408
1473
  else
1409
- reflection.options[:foreign_key] || "#{method}_id"
1474
+ reflection.options[:foreign_key] || reflection.options[:class_name].try(:foreign_key) || "#{method}_id"
1410
1475
  end
1411
1476
  else
1412
1477
  method
@@ -1432,7 +1497,9 @@ module Formtastic #:nodoc:
1432
1497
  def default_string_options(method, type) #:nodoc:
1433
1498
  column = self.column_for(method)
1434
1499
 
1435
- if type == :numeric || column.nil? || column.limit.nil?
1500
+ if type == :text
1501
+ { :cols => @@default_text_field_size, :rows => @@default_text_area_height }
1502
+ elsif type == :numeric || column.nil? || column.limit.nil?
1436
1503
  { :size => @@default_text_field_size }
1437
1504
  else
1438
1505
  { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
@@ -1479,7 +1546,12 @@ module Formtastic #:nodoc:
1479
1546
 
1480
1547
  def humanized_attribute_name(method) #:nodoc:
1481
1548
  if @object && @object.class.respond_to?(:human_attribute_name)
1482
- @object.class.human_attribute_name(method.to_s)
1549
+ humanized_name = @object.class.human_attribute_name(method.to_s)
1550
+ if humanized_name == method.to_s.send(:humanize)
1551
+ method.to_s.send(@@label_str_method)
1552
+ else
1553
+ humanized_name
1554
+ end
1483
1555
  else
1484
1556
  method.to_s.send(@@label_str_method)
1485
1557
  end
@@ -1612,18 +1684,18 @@ module Formtastic #:nodoc:
1612
1684
  end
1613
1685
 
1614
1686
  [:form_for, :fields_for, :remote_form_for].each do |meth|
1615
- src = <<-END_SRC
1687
+ module_eval <<-END_SRC, __FILE__, __LINE__ + 1
1616
1688
  def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1617
1689
  options = args.extract_options!
1618
1690
  options[:builder] ||= @@builder
1619
1691
  options[:html] ||= {}
1620
-
1692
+
1621
1693
  class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1622
1694
  class_names << "formtastic"
1623
1695
  class_names << case record_or_name_or_array
1624
1696
  when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1625
- when Array then record_or_name_or_array.last.class.to_s.underscore # [@post, @comment] # => "comment"
1626
- else record_or_name_or_array.class.to_s.underscore # @post => "post"
1697
+ when Array then ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
1698
+ else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class) # @post => "post"
1627
1699
  end
1628
1700
  options[:html][:class] = class_names.join(" ")
1629
1701
 
@@ -1632,7 +1704,6 @@ module Formtastic #:nodoc:
1632
1704
  end
1633
1705
  end
1634
1706
  END_SRC
1635
- module_eval src, __FILE__, __LINE__
1636
1707
  end
1637
1708
  alias :semantic_form_remote_for :semantic_remote_form_for
1638
1709