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 +69 -12
- data/Rakefile +1 -1
- data/generators/form/templates/view__form.html.erb +2 -2
- data/generators/form/templates/view__form.html.haml +2 -2
- data/generators/formtastic/templates/formtastic.css +58 -50
- data/generators/formtastic/templates/formtastic.rb +3 -0
- data/generators/formtastic/templates/formtastic_changes.css +5 -1
- data/lib/formtastic.rb +108 -37
- data/lib/formtastic/layout_helper.rb +10 -0
- data/rails/init.rb +2 -0
- data/spec/commit_button_spec.rb +26 -4
- data/spec/custom_macros.rb +0 -101
- data/spec/form_helper_spec.rb +6 -0
- data/spec/input_spec.rb +34 -0
- data/spec/inputs/boolean_input_spec.rb +9 -5
- data/spec/inputs/check_boxes_input_spec.rb +33 -10
- data/spec/inputs/date_input_spec.rb +137 -25
- data/spec/inputs/datetime_input_spec.rb +177 -54
- data/spec/inputs/hidden_input_spec.rb +9 -0
- data/spec/inputs/radio_input_spec.rb +15 -9
- data/spec/inputs/select_input_spec.rb +113 -83
- data/spec/inputs/time_input_spec.rb +151 -25
- data/spec/inputs/time_zone_input_spec.rb +4 -2
- data/spec/layout_helper_spec.rb +29 -0
- data/spec/semantic_errors_spec.rb +98 -0
- data/spec/spec_helper.rb +26 -18
- metadata +50 -22
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
|
-
|
104
|
-
|
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
|
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
|
-
<%
|
432
|
-
<%=
|
433
|
-
<%=
|
434
|
-
<%=
|
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.
|
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
|
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
|
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
|
-
<%%
|
1
|
+
<%% f.inputs do %>
|
2
2
|
<% attributes.each do |attribute| -%>
|
3
|
-
<%%=
|
3
|
+
<%%= f.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>' %>
|
4
4
|
<% end -%>
|
5
5
|
<%% 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:
|
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:
|
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
|
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
|
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
|
-
|
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
|
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.
|
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.
|
853
|
-
#
|
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.
|
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 <<
|
992
|
+
time_inputs << :second if options[:include_seconds]
|
927
993
|
|
928
994
|
list_items_capture = ""
|
929
995
|
hidden_fields_capture = ""
|
930
996
|
|
931
|
-
|
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
|
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
|
-
|
954
|
-
|
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
|
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 == :
|
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
|
-
|
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
|
1626
|
-
else record_or_name_or_array.class
|
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
|
|