formtastic 1.2.5 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/CHANGELOG +279 -0
- data/Gemfile +3 -0
- data/README.textile +155 -172
- data/RELEASE_PROCESS +7 -0
- data/Rakefile +52 -0
- data/app/assets/stylesheets/formtastic.css +275 -0
- data/app/assets/stylesheets/formtastic_ie6.css +27 -0
- data/app/assets/stylesheets/formtastic_ie7.css +17 -0
- data/formtastic.gemspec +51 -0
- data/lib/formtastic.rb +21 -1960
- data/lib/formtastic/engine.rb +7 -0
- data/lib/formtastic/form_builder.rb +83 -0
- data/lib/formtastic/helpers.rb +16 -0
- data/lib/formtastic/helpers/buttons_helper.rb +277 -0
- data/lib/formtastic/helpers/errors_helper.rb +113 -0
- data/lib/formtastic/helpers/fieldset_wrapper.rb +75 -0
- data/lib/formtastic/helpers/file_column_detection.rb +16 -0
- data/lib/formtastic/helpers/form_helper.rb +198 -0
- data/lib/formtastic/helpers/input_helper.rb +366 -0
- data/lib/formtastic/helpers/inputs_helper.rb +392 -0
- data/lib/formtastic/helpers/reflection.rb +33 -0
- data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
- data/lib/formtastic/html_attributes.rb +21 -0
- data/lib/formtastic/i18n.rb +1 -0
- data/lib/formtastic/inputs.rb +31 -0
- data/lib/formtastic/inputs/base.rb +61 -0
- data/lib/formtastic/inputs/base/associations.rb +31 -0
- data/lib/formtastic/inputs/base/choices.rb +103 -0
- data/lib/formtastic/inputs/base/collections.rb +94 -0
- data/lib/formtastic/inputs/base/database.rb +17 -0
- data/lib/formtastic/inputs/base/errors.rb +58 -0
- data/lib/formtastic/inputs/base/fileish.rb +23 -0
- data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
- data/lib/formtastic/inputs/base/hints.rb +31 -0
- data/lib/formtastic/inputs/base/html.rb +52 -0
- data/lib/formtastic/inputs/base/labelling.rb +55 -0
- data/lib/formtastic/inputs/base/naming.rb +42 -0
- data/lib/formtastic/inputs/base/options.rb +18 -0
- data/lib/formtastic/inputs/base/stringish.rb +35 -0
- data/lib/formtastic/inputs/base/timeish.rb +128 -0
- data/lib/formtastic/inputs/base/validations.rb +166 -0
- data/lib/formtastic/inputs/base/wrapping.rb +40 -0
- data/lib/formtastic/inputs/boolean_input.rb +96 -0
- data/lib/formtastic/inputs/check_boxes_input.rb +179 -0
- data/lib/formtastic/inputs/country_input.rb +66 -0
- data/lib/formtastic/inputs/date_input.rb +14 -0
- data/lib/formtastic/inputs/datetime_input.rb +9 -0
- data/lib/formtastic/inputs/email_input.rb +40 -0
- data/lib/formtastic/inputs/file_input.rb +42 -0
- data/lib/formtastic/inputs/hidden_input.rb +66 -0
- data/lib/formtastic/inputs/number_input.rb +118 -0
- data/lib/formtastic/inputs/numeric_input.rb +21 -0
- data/lib/formtastic/inputs/password_input.rb +40 -0
- data/lib/formtastic/inputs/phone_input.rb +41 -0
- data/lib/formtastic/inputs/radio_input.rb +157 -0
- data/lib/formtastic/inputs/range_input.rb +119 -0
- data/lib/formtastic/inputs/search_input.rb +40 -0
- data/lib/formtastic/inputs/select_input.rb +210 -0
- data/lib/formtastic/inputs/string_input.rb +34 -0
- data/lib/formtastic/inputs/text_input.rb +47 -0
- data/lib/formtastic/inputs/time_input.rb +14 -0
- data/lib/formtastic/inputs/time_zone_input.rb +48 -0
- data/lib/formtastic/inputs/url_input.rb +40 -0
- data/lib/formtastic/localized_string.rb +105 -0
- data/lib/formtastic/railtie.rb +5 -7
- data/lib/formtastic/semantic_form_builder.rb +11 -0
- data/lib/formtastic/util.rb +6 -19
- data/lib/formtastic/version.rb +3 -0
- data/lib/generators/formtastic/install/install_generator.rb +28 -6
- data/lib/generators/templates/_form.html.erb +10 -4
- data/lib/generators/templates/_form.html.haml +8 -4
- data/lib/generators/templates/formtastic.rb +25 -32
- data/lib/locale/en.yml +1 -0
- data/lib/tasks/verify_rcov.rb +44 -0
- data/sample/basic_inputs.html +182 -0
- data/sample/config.ru +69 -0
- data/sample/index.html +14 -0
- data/spec/builder/custom_builder_spec.rb +109 -0
- data/spec/builder/error_proc_spec.rb +27 -0
- data/spec/builder/errors_spec.rb +193 -0
- data/spec/builder/semantic_fields_for_spec.rb +88 -0
- data/spec/helpers/buttons_helper_spec.rb +150 -0
- data/spec/helpers/commit_button_helper_spec.rb +470 -0
- data/spec/helpers/form_helper_spec.rb +135 -0
- data/spec/helpers/input_helper_spec.rb +837 -0
- data/spec/helpers/inputs_helper_spec.rb +562 -0
- data/spec/helpers/semantic_errors_helper_spec.rb +112 -0
- data/spec/i18n_spec.rb +199 -0
- data/spec/inputs/boolean_input_spec.rb +184 -0
- data/spec/inputs/check_boxes_input_spec.rb +375 -0
- data/spec/inputs/country_input_spec.rb +133 -0
- data/spec/inputs/custom_input_spec.rb +52 -0
- data/spec/inputs/date_input_spec.rb +110 -0
- data/spec/inputs/datetime_input_spec.rb +115 -0
- data/spec/inputs/email_input_spec.rb +55 -0
- data/spec/inputs/file_input_spec.rb +59 -0
- data/spec/inputs/hidden_input_spec.rb +120 -0
- data/spec/inputs/include_blank_spec.rb +70 -0
- data/spec/inputs/label_spec.rb +104 -0
- data/spec/inputs/number_input_spec.rb +487 -0
- data/spec/inputs/numeric_input_spec.rb +41 -0
- data/spec/inputs/password_input_spec.rb +69 -0
- data/spec/inputs/phone_input_spec.rb +55 -0
- data/spec/inputs/placeholder_spec.rb +71 -0
- data/spec/inputs/radio_input_spec.rb +234 -0
- data/spec/inputs/range_input_spec.rb +477 -0
- data/spec/inputs/search_input_spec.rb +55 -0
- data/spec/inputs/select_input_spec.rb +545 -0
- data/spec/inputs/string_input_spec.rb +163 -0
- data/spec/inputs/text_input_spec.rb +158 -0
- data/spec/inputs/time_input_spec.rb +155 -0
- data/spec/inputs/time_zone_input_spec.rb +87 -0
- data/spec/inputs/url_input_spec.rb +55 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +361 -0
- data/spec/support/custom_macros.rb +656 -0
- data/spec/support/deferred_garbage_collection.rb +21 -0
- data/spec/support/deprecation.rb +6 -0
- data/spec/support/test_environment.rb +30 -0
- metadata +306 -88
- data/generators/form/USAGE +0 -16
- data/generators/form/form_generator.rb +0 -111
- data/generators/formtastic/formtastic_generator.rb +0 -26
- data/init.rb +0 -5
- data/lib/formtastic/layout_helper.rb +0 -12
- data/lib/generators/formtastic/form/form_generator.rb +0 -84
- data/lib/generators/templates/formtastic.css +0 -145
- data/lib/generators/templates/formtastic_changes.css +0 -14
- data/lib/generators/templates/rails2/_form.html.erb +0 -5
- data/lib/generators/templates/rails2/_form.html.haml +0 -4
- data/rails/init.rb +0 -2
data/RELEASE_PROCESS
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# edit version.rb
|
2
|
+
# edit gemspec release date
|
3
|
+
git ci formtastic.gemspec -m "new gemspec" # commit changes
|
4
|
+
git tag X.X.X # tag the new version in the code base too
|
5
|
+
gem build formtastic.gemspec # build the gem
|
6
|
+
gem push formtastic-X.X.X.gem # publish the gem
|
7
|
+
git push && git push --tags # push to remote
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'tasks/verify_rcov'
|
7
|
+
require 'bundler'
|
8
|
+
|
9
|
+
Bundler::GemHelper.install_tasks
|
10
|
+
|
11
|
+
desc 'Default: run unit specs.'
|
12
|
+
task :default => :spec_and_verify_coverage
|
13
|
+
|
14
|
+
desc 'Generate documentation for the formtastic plugin.'
|
15
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Formtastic'
|
18
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
19
|
+
rdoc.rdoc_files.include('README.textile')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Test the formtastic plugin.'
|
24
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
25
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Test the formtastic inputs.'
|
29
|
+
RSpec::Core::RakeTask.new('spec:inputs') do |t|
|
30
|
+
t.pattern = FileList['spec/inputs/*_spec.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Test the formtastic plugin with specdoc formatting and colors'
|
34
|
+
RSpec::Core::RakeTask.new('specdoc') do |t|
|
35
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Run all examples with RCov'
|
39
|
+
RSpec::Core::RakeTask.new('rcov') do |t|
|
40
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
41
|
+
t.rcov = true
|
42
|
+
t.rcov_opts = %w(--exclude gems/*,spec/*,.bundle/*, --aggregate coverage.data)
|
43
|
+
end
|
44
|
+
|
45
|
+
RCov::VerifyTask.new(:verify_coverage) do |t|
|
46
|
+
t.require_exact_threshold = false
|
47
|
+
t.threshold = 95
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Run all examples and verify coverage"
|
51
|
+
task :spec_and_verify_coverage => [:rcov, :verify_coverage] do
|
52
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
/* -------------------------------------------------------------------------------------------------
|
2
|
+
|
3
|
+
It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after
|
4
|
+
this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs.
|
5
|
+
This will allow you to update formtastic.css with new releases without clobbering your own changes.
|
6
|
+
|
7
|
+
This stylesheet forms part of the Formtastic Rails Plugin
|
8
|
+
(c) 2008-2011 Justin French
|
9
|
+
|
10
|
+
--------------------------------------------------------------------------------------------------*/
|
11
|
+
|
12
|
+
/* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just .formtastic
|
13
|
+
--------------------------------------------------------------------------------------------------*/
|
14
|
+
.formtastic,
|
15
|
+
.formtastic ul,
|
16
|
+
.formtastic ol,
|
17
|
+
.formtastic li,
|
18
|
+
.formtastic fieldset,
|
19
|
+
.formtastic legend,
|
20
|
+
.formtastic input,
|
21
|
+
.formtastic textarea,
|
22
|
+
.formtastic select,
|
23
|
+
.formtastic p {
|
24
|
+
margin:0;
|
25
|
+
padding:0;
|
26
|
+
}
|
27
|
+
|
28
|
+
.formtastic fieldset {
|
29
|
+
border:0;
|
30
|
+
}
|
31
|
+
|
32
|
+
.formtastic em,
|
33
|
+
.formtastic strong {
|
34
|
+
font-style:normal;
|
35
|
+
font-weight:normal;
|
36
|
+
}
|
37
|
+
|
38
|
+
.formtastic ol,
|
39
|
+
.formtastic ul {
|
40
|
+
list-style:none;
|
41
|
+
}
|
42
|
+
|
43
|
+
.formtastic abbr,
|
44
|
+
.formtastic acronym {
|
45
|
+
border:0;
|
46
|
+
font-variant:normal;
|
47
|
+
}
|
48
|
+
|
49
|
+
.formtastic input,
|
50
|
+
.formtastic textarea {
|
51
|
+
font-family:sans-serif;
|
52
|
+
font-size:inherit;
|
53
|
+
font-weight:inherit;
|
54
|
+
}
|
55
|
+
|
56
|
+
.formtastic input,
|
57
|
+
.formtastic textarea,
|
58
|
+
.formtastic select {
|
59
|
+
font-size:100%;
|
60
|
+
}
|
61
|
+
|
62
|
+
.formtastic legend {
|
63
|
+
white-space:normal;
|
64
|
+
color:#000;
|
65
|
+
}
|
66
|
+
|
67
|
+
|
68
|
+
/* SEMANTIC ERRORS
|
69
|
+
--------------------------------------------------------------------------------------------------*/
|
70
|
+
.formtastic .errors {
|
71
|
+
color:#cc0000;
|
72
|
+
margin:0.5em 0 1.5em 25%;
|
73
|
+
list-style:square;
|
74
|
+
}
|
75
|
+
|
76
|
+
.formtastic .errors li {
|
77
|
+
padding:0;
|
78
|
+
border:none;
|
79
|
+
display:list-item;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
/* BUTTONS
|
84
|
+
--------------------------------------------------------------------------------------------------*/
|
85
|
+
.formtastic .buttons {
|
86
|
+
overflow:hidden; zoom:1; /* clear containing floats */
|
87
|
+
padding-left:25%;
|
88
|
+
}
|
89
|
+
|
90
|
+
.formtastic .button {
|
91
|
+
float:left;
|
92
|
+
padding-right:0.5em;
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
/* INPUTS
|
97
|
+
--------------------------------------------------------------------------------------------------*/
|
98
|
+
.formtastic .inputs {
|
99
|
+
overflow:hidden; zoom:1; /* clear containing floats */
|
100
|
+
}
|
101
|
+
|
102
|
+
.formtastic .input {
|
103
|
+
overflow:hidden; zoom:1; /* clear containing floats */
|
104
|
+
padding:0.5em 0; /* padding and negative margin juggling is for Firefox */
|
105
|
+
margin-top:-0.5em;
|
106
|
+
margin-bottom:1em;
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
/* LEFT ALIGNED LABELS
|
111
|
+
--------------------------------------------------------------------------------------------------*/
|
112
|
+
.formtastic .input .label {
|
113
|
+
display:block;
|
114
|
+
width:25%;
|
115
|
+
float:left;
|
116
|
+
padding-top:.2em;
|
117
|
+
}
|
118
|
+
|
119
|
+
.formtastic .fragments .label,
|
120
|
+
.formtastic .choices .label {
|
121
|
+
position:absolute;
|
122
|
+
width:95%;
|
123
|
+
left:0px;
|
124
|
+
}
|
125
|
+
|
126
|
+
.formtastic .fragments .label label,
|
127
|
+
.formtastic .choices .label label {
|
128
|
+
position:absolute;
|
129
|
+
}
|
130
|
+
|
131
|
+
/* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets)
|
132
|
+
--------------------------------------------------------------------------------------------------*/
|
133
|
+
.formtastic .choices {
|
134
|
+
position:relative;
|
135
|
+
}
|
136
|
+
|
137
|
+
.formtastic .choices-group {
|
138
|
+
float:left;
|
139
|
+
width:74%;
|
140
|
+
margin:0;
|
141
|
+
padding:0 0 0 25%;
|
142
|
+
}
|
143
|
+
|
144
|
+
.formtastic .choice {
|
145
|
+
padding:0;
|
146
|
+
border:0;
|
147
|
+
}
|
148
|
+
|
149
|
+
|
150
|
+
/* INLINE HINTS
|
151
|
+
--------------------------------------------------------------------------------------------------*/
|
152
|
+
.formtastic .input .inline-hints {
|
153
|
+
color:#666;
|
154
|
+
margin:0.5em 0 0 25%;
|
155
|
+
}
|
156
|
+
|
157
|
+
|
158
|
+
/* INLINE ERRORS
|
159
|
+
--------------------------------------------------------------------------------------------------*/
|
160
|
+
.formtastic .inline-errors {
|
161
|
+
color:#cc0000;
|
162
|
+
margin:0.5em 0 0 25%;
|
163
|
+
}
|
164
|
+
|
165
|
+
.formtastic .errors {
|
166
|
+
color:#cc0000;
|
167
|
+
margin:0.5em 0 0 25%;
|
168
|
+
list-style:square;
|
169
|
+
}
|
170
|
+
|
171
|
+
.formtastic .errors li {
|
172
|
+
padding:0;
|
173
|
+
border:none;
|
174
|
+
display:list-item;
|
175
|
+
}
|
176
|
+
|
177
|
+
|
178
|
+
/* STRING, NUMERIC, PASSWORD, EMAIL, URL, PHONE, SEARCH (ETC) OVERRIDES
|
179
|
+
--------------------------------------------------------------------------------------------------*/
|
180
|
+
.formtastic .stringish input {
|
181
|
+
width:72%;
|
182
|
+
}
|
183
|
+
|
184
|
+
.formtastic .stringish input[size] {
|
185
|
+
width:auto;
|
186
|
+
max-width:72%;
|
187
|
+
}
|
188
|
+
|
189
|
+
|
190
|
+
/* TEXTAREA OVERRIDES
|
191
|
+
--------------------------------------------------------------------------------------------------*/
|
192
|
+
.formtastic .text textarea {
|
193
|
+
width:72%;
|
194
|
+
}
|
195
|
+
|
196
|
+
.formtastic .text textarea[cols] {
|
197
|
+
width:auto;
|
198
|
+
max-width:72%;
|
199
|
+
}
|
200
|
+
|
201
|
+
|
202
|
+
/* HIDDEN OVERRIDES
|
203
|
+
--------------------------------------------------------------------------------------------------*/
|
204
|
+
.formtastic .hidden {
|
205
|
+
display:none;
|
206
|
+
}
|
207
|
+
|
208
|
+
|
209
|
+
/* BOOLEAN LABELS
|
210
|
+
--------------------------------------------------------------------------------------------------*/
|
211
|
+
.formtastic .boolean label {
|
212
|
+
padding-left:25%;
|
213
|
+
display:block;
|
214
|
+
}
|
215
|
+
|
216
|
+
|
217
|
+
/* CHOICE GROUPS
|
218
|
+
--------------------------------------------------------------------------------------------------*/
|
219
|
+
.formtastic .choices-group {
|
220
|
+
margin-bottom:-0.5em;
|
221
|
+
}
|
222
|
+
|
223
|
+
.formtastic .choice {
|
224
|
+
margin:0.1em 0 0.5em 0;
|
225
|
+
}
|
226
|
+
|
227
|
+
.formtastic .choice label {
|
228
|
+
float:none;
|
229
|
+
width:100%;
|
230
|
+
line-height:100%;
|
231
|
+
padding-top:0;
|
232
|
+
margin-bottom:0.6em;
|
233
|
+
}
|
234
|
+
|
235
|
+
|
236
|
+
/* ADJUSTMENTS FOR INPUTS INSIDE LABELS (boolean input, radio input, check_boxes input)
|
237
|
+
--------------------------------------------------------------------------------------------------*/
|
238
|
+
.formtastic .choice label input,
|
239
|
+
.formtastic .boolean label input {
|
240
|
+
margin:0 0.3em 0 0.1em;
|
241
|
+
line-height:100%;
|
242
|
+
}
|
243
|
+
|
244
|
+
|
245
|
+
/* FRAGMENTED INPUTS (DATE/TIME/DATETIME)
|
246
|
+
--------------------------------------------------------------------------------------------------*/
|
247
|
+
.formtastic .fragments {
|
248
|
+
position:relative;
|
249
|
+
}
|
250
|
+
|
251
|
+
.formtastic .fragments-group {
|
252
|
+
float:left;
|
253
|
+
width:74%;
|
254
|
+
margin:0;
|
255
|
+
padding:0 0 0 25%;
|
256
|
+
}
|
257
|
+
|
258
|
+
.formtastic .fragment {
|
259
|
+
float:left;
|
260
|
+
width:auto;
|
261
|
+
margin:0 .3em 0 0;
|
262
|
+
padding:0;
|
263
|
+
border:0;
|
264
|
+
}
|
265
|
+
|
266
|
+
.formtastic .fragment label {
|
267
|
+
display:none;
|
268
|
+
}
|
269
|
+
|
270
|
+
.formtastic .fragment label input {
|
271
|
+
display:inline;
|
272
|
+
margin:0;
|
273
|
+
padding:0;
|
274
|
+
}
|
275
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
/* additional stylesheets only for IE6, if you wish to support it */
|
2
|
+
|
3
|
+
/* legend labels apper to have a left margin or padding I couldn't get rid of cleanly in main stylesheet */
|
4
|
+
.formtastic .input fieldset legend.label {
|
5
|
+
margin-left:-7px;
|
6
|
+
}
|
7
|
+
|
8
|
+
/* checkbox and radio inputs appear to have a margin around them that I couldn't remove in main stylesheet */
|
9
|
+
.formtastic .choice label input,
|
10
|
+
.formtastic .boolean label input {
|
11
|
+
position:relative;
|
12
|
+
left:-1px;
|
13
|
+
size:15px;
|
14
|
+
margin-left:0;
|
15
|
+
margin-right:0;
|
16
|
+
}
|
17
|
+
|
18
|
+
/* inline hints and errors appear a few pixel too far over to the left */
|
19
|
+
.formtastic .inline-hints,
|
20
|
+
.formtastic .inline-errors {
|
21
|
+
padding-left:3px;
|
22
|
+
}
|
23
|
+
|
24
|
+
/* fragment (eg year, month, day) appear a few pixels too far to the left*/
|
25
|
+
.formtastic .fragment {
|
26
|
+
padding-left:3px;
|
27
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
/* additional stylesheets only for IE7, if you wish to support it */
|
2
|
+
|
3
|
+
/* legend labels apper to have a left margin or padding I couldn't get rid of cleanly in main stylesheet */
|
4
|
+
.formtastic .input fieldset legend.label {
|
5
|
+
margin-left:-7px;
|
6
|
+
}
|
7
|
+
|
8
|
+
/* checkbox and radio inputs appear to have a margin around them that I couldn't remove in main stylesheet */
|
9
|
+
.formtastic .choice label input,
|
10
|
+
.formtastic .boolean label input {
|
11
|
+
position:relative;
|
12
|
+
left:-4px;
|
13
|
+
right:-4px;
|
14
|
+
size:15px;
|
15
|
+
margin-left:0;
|
16
|
+
margin-right:0;
|
17
|
+
}
|
data/formtastic.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "formtastic/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{formtastic}
|
7
|
+
s.version = Formtastic::VERSION
|
8
|
+
s.date = %q{2011-06-09}
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = [%q{Justin French}]
|
11
|
+
s.email = [%q{justin@indent.com.au}]
|
12
|
+
s.homepage = %q{http://github.com/justinfrench/formtastic}
|
13
|
+
s.summary = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
|
14
|
+
s.description = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.post_install_message = %q{
|
22
|
+
========================================================================
|
23
|
+
Thanks for installing Formtastic!
|
24
|
+
------------------------------------------------------------------------
|
25
|
+
You can now (optionally) run the generator to copy a config initializer
|
26
|
+
(and some stylesheets, in Rails 3.0.x) into your application:
|
27
|
+
rails generate formtastic:install
|
28
|
+
|
29
|
+
Find out more and get involved:
|
30
|
+
http://github.com/justinfrench/formtastic
|
31
|
+
http://rdoc.info/github/justinfrench/formtastic/master/frames
|
32
|
+
http://groups.google.com.au/group/formtastic
|
33
|
+
http://twitter.com/formtastic
|
34
|
+
========================================================================
|
35
|
+
}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.extra_rdoc_files = ["README.textile"]
|
38
|
+
|
39
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
40
|
+
s.rubygems_version = %q{1.3.6}
|
41
|
+
|
42
|
+
s.add_dependency(%q<rails>, ["~> 3.0"])
|
43
|
+
|
44
|
+
s.add_development_dependency(%q<rspec-rails>, ["~> 2.5"])
|
45
|
+
s.add_development_dependency(%q<rspec_tag_matchers>, [">= 1.0.0"])
|
46
|
+
s.add_development_dependency(%q<hpricot>, ["~> 0.8.3"])
|
47
|
+
s.add_development_dependency(%q<BlueCloth>) # for YARD
|
48
|
+
s.add_development_dependency(%q<yard>, ["~> 0.6"])
|
49
|
+
s.add_development_dependency(%q<rcov>, ["~> 0.9.9"])
|
50
|
+
s.add_development_dependency(%q<colored>)
|
51
|
+
end
|
data/lib/formtastic.rb
CHANGED
@@ -1,1964 +1,25 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
20
|
-
class_inheritable_accessor *configurables
|
21
|
-
end
|
22
|
-
|
23
|
-
cattr_accessor :custom_namespace
|
24
|
-
|
25
|
-
self.default_text_field_size = nil
|
26
|
-
self.default_text_area_height = 20
|
27
|
-
self.default_text_area_width = nil
|
28
|
-
self.all_fields_required_by_default = true
|
29
|
-
self.include_blank_for_select_by_default = true
|
30
|
-
self.required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
|
31
|
-
self.optional_string = ''
|
32
|
-
self.inline_errors = :sentence
|
33
|
-
self.label_str_method = :humanize
|
34
|
-
self.collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
|
35
|
-
self.collection_value_methods = %w[id to_s]
|
36
|
-
self.inline_order = [ :input, :hints, :errors ]
|
37
|
-
self.custom_inline_order = {}
|
38
|
-
self.file_methods = [ :file?, :public_filename, :filename ]
|
39
|
-
self.file_metadata_suffixes = ['content_type', 'file_name', 'file_size']
|
40
|
-
self.priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
|
41
|
-
self.i18n_lookups_by_default = false
|
42
|
-
self.escape_html_entities_in_hints_and_labels = true
|
43
|
-
self.default_commit_button_accesskey = nil
|
44
|
-
self.default_inline_error_class = 'inline-errors'
|
45
|
-
self.default_error_list_class = 'errors'
|
46
|
-
self.default_hint_class = 'inline-hints'
|
47
|
-
|
48
|
-
RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
49
|
-
|
50
|
-
INLINE_ERROR_TYPES = [:sentence, :list, :first]
|
51
|
-
|
52
|
-
attr_accessor :template
|
53
|
-
|
54
|
-
# Returns a suitable form input for the given +method+, using the database column information
|
55
|
-
# and other factors (like the method name) to figure out what you probably want.
|
56
|
-
#
|
57
|
-
# Options:
|
58
|
-
#
|
59
|
-
# * :as - override the input type (eg force a :string to render as a :password field)
|
60
|
-
# * :label - use something other than the method name as the label text, when false no label is printed
|
61
|
-
# * :required - specify if the column is required (true) or not (false)
|
62
|
-
# * :hint - provide some text to hint or help the user provide the correct information for a field
|
63
|
-
# * :input_html - provide options that will be passed down to the generated input
|
64
|
-
# * :wrapper_html - provide options that will be passed down to the li wrapper
|
65
|
-
#
|
66
|
-
# Input Types:
|
67
|
-
#
|
68
|
-
# Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
|
69
|
-
# but there are a few special cases and some simplification (:integer, :float and :decimal
|
70
|
-
# columns all map to a single numeric_input, for example).
|
71
|
-
#
|
72
|
-
# * :select (a select menu for associations) - default to association names
|
73
|
-
# * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
|
74
|
-
# * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
|
75
|
-
# * :time_zone (a select menu with time zones)
|
76
|
-
# * :password (a password input) - default for :string column types with 'password' in the method name
|
77
|
-
# * :text (a textarea) - default for :text column types
|
78
|
-
# * :date (a date select) - default for :date column types
|
79
|
-
# * :datetime (a date and time select) - default for :datetime and :timestamp column types
|
80
|
-
# * :time (a time select) - default for :time column types
|
81
|
-
# * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
|
82
|
-
# * :string (a text field) - default for :string column types
|
83
|
-
# * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
|
84
|
-
# * :email (an email input) - default for :string column types with 'email' as the method name.
|
85
|
-
# * :url (a url input) - default for :string column types with 'url' as the method name.
|
86
|
-
# * :phone (a tel input) - default for :string column types with 'phone' or 'fax' in the method name.
|
87
|
-
# * :search (a search input) - default for :string column types with 'search' as the method name.
|
88
|
-
# * :country (a select menu of country names) - requires a country_select plugin to be installed
|
89
|
-
# * :email (an email input) - New in HTML5 - needs to be explicitly provided with :as => :email
|
90
|
-
# * :url (a url input) - New in HTML5 - needs to be explicitly provided with :as => :url
|
91
|
-
# * :phone (a tel input) - New in HTML5 - needs to be explicitly provided with :as => :phone
|
92
|
-
# * :search (a search input) - New in HTML5 - needs to be explicity provided with :as => :search
|
93
|
-
# * :country (a select menu of country names) - requires a country_select plugin to be installed
|
94
|
-
# * :hidden (a hidden field) - creates a hidden field (added for compatibility)
|
95
|
-
#
|
96
|
-
# Example:
|
97
|
-
#
|
98
|
-
# <% semantic_form_for @employee do |form| %>
|
99
|
-
# <% form.inputs do -%>
|
100
|
-
# <%= form.input :name, :label => "Full Name" %>
|
101
|
-
# <%= form.input :manager, :as => :radio %>
|
102
|
-
# <%= form.input :secret, :as => :password, :input_html => { :value => "xxxx" } %>
|
103
|
-
# <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
|
104
|
-
# <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
|
105
|
-
# <%= form.input :email %>
|
106
|
-
# <%= form.input :website, :as => :url, :hint => "You may wish to omit the http://" %>
|
107
|
-
# <% end %>
|
108
|
-
# <% end %>
|
109
|
-
#
|
110
|
-
def input(method, options = {})
|
111
|
-
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
112
|
-
|
113
|
-
options[:required] = method_required?(method) unless options.key?(:required)
|
114
|
-
options[:as] ||= default_input_type(method, options)
|
115
|
-
|
116
|
-
html_class = [ options[:as], (options[:required] ? :required : :optional) ]
|
117
|
-
html_class << 'error' if has_errors?(method, options)
|
118
|
-
|
119
|
-
wrapper_html = options.delete(:wrapper_html) || {}
|
120
|
-
wrapper_html[:id] ||= generate_html_id(method)
|
121
|
-
wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
|
122
|
-
|
123
|
-
if options[:input_html] && options[:input_html][:id]
|
124
|
-
options[:label_html] ||= {}
|
125
|
-
options[:label_html][:for] ||= options[:input_html][:id]
|
126
|
-
end
|
127
|
-
|
128
|
-
input_parts = (custom_inline_order[options[:as]] || inline_order).dup
|
129
|
-
input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
|
130
|
-
|
131
|
-
list_item_content = input_parts.map do |type|
|
132
|
-
send(:"inline_#{type}_for", method, options)
|
133
|
-
end.compact.join("\n")
|
134
|
-
|
135
|
-
return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
|
139
|
-
# called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
|
140
|
-
# or with a list of fields. These two examples are functionally equivalent:
|
141
|
-
#
|
142
|
-
# # With a block:
|
143
|
-
# <% semantic_form_for @post do |form| %>
|
144
|
-
# <% form.inputs do %>
|
145
|
-
# <%= form.input :title %>
|
146
|
-
# <%= form.input :body %>
|
147
|
-
# <% end %>
|
148
|
-
# <% end %>
|
149
|
-
#
|
150
|
-
# # With a list of fields:
|
151
|
-
# <% semantic_form_for @post do |form| %>
|
152
|
-
# <%= form.inputs :title, :body %>
|
153
|
-
# <% end %>
|
154
|
-
#
|
155
|
-
# # Output:
|
156
|
-
# <form ...>
|
157
|
-
# <fieldset class="inputs">
|
158
|
-
# <ol>
|
159
|
-
# <li class="string">...</li>
|
160
|
-
# <li class="text">...</li>
|
161
|
-
# </ol>
|
162
|
-
# </fieldset>
|
163
|
-
# </form>
|
164
|
-
#
|
165
|
-
# === Quick Forms
|
166
|
-
#
|
167
|
-
# When called without a block or a field list, an input is rendered for each column in the
|
168
|
-
# model's database table, just like Rails' scaffolding. You'll obviously want more control
|
169
|
-
# than this in a production application, but it's a great way to get started, then come back
|
170
|
-
# later to customise the form with a field list or a block of inputs. Example:
|
171
|
-
#
|
172
|
-
# <% semantic_form_for @post do |form| %>
|
173
|
-
# <%= form.inputs %>
|
174
|
-
# <% end %>
|
175
|
-
#
|
176
|
-
# With a few arguments:
|
177
|
-
# <% semantic_form_for @post do |form| %>
|
178
|
-
# <%= form.inputs "Post details", :title, :body %>
|
179
|
-
# <% end %>
|
180
|
-
#
|
181
|
-
# === Options
|
182
|
-
#
|
183
|
-
# All options (with the exception of :name/:title) are passed down to the fieldset as HTML
|
184
|
-
# attributes (id, class, style, etc). If provided, the :name/:title option is passed into a
|
185
|
-
# legend tag inside the fieldset.
|
186
|
-
#
|
187
|
-
# # With a block:
|
188
|
-
# <% semantic_form_for @post do |form| %>
|
189
|
-
# <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
|
190
|
-
# ...
|
191
|
-
# <% end %>
|
192
|
-
# <% end %>
|
193
|
-
#
|
194
|
-
# # With a list (the options must come after the field list):
|
195
|
-
# <% semantic_form_for @post do |form| %>
|
196
|
-
# <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
|
197
|
-
# <% end %>
|
198
|
-
#
|
199
|
-
# # ...or the equivalent:
|
200
|
-
# <% semantic_form_for @post do |form| %>
|
201
|
-
# <%= form.inputs "Create a new post", :title, :body, :style => "border:1px;" %>
|
202
|
-
# <% end %>
|
203
|
-
#
|
204
|
-
# === It's basically a fieldset!
|
205
|
-
#
|
206
|
-
# Instead of hard-coding fieldsets & legends into your form to logically group related fields,
|
207
|
-
# use inputs:
|
208
|
-
#
|
209
|
-
# <% semantic_form_for @post do |f| %>
|
210
|
-
# <% f.inputs do %>
|
211
|
-
# <%= f.input :title %>
|
212
|
-
# <%= f.input :body %>
|
213
|
-
# <% end %>
|
214
|
-
# <% f.inputs :name => "Advanced", :id => "advanced" do %>
|
215
|
-
# <%= f.input :created_at %>
|
216
|
-
# <%= f.input :user_id, :label => "Author" %>
|
217
|
-
# <% end %>
|
218
|
-
# <% f.inputs "Extra" do %>
|
219
|
-
# <%= f.input :update_at %>
|
220
|
-
# <% end %>
|
221
|
-
# <% end %>
|
222
|
-
#
|
223
|
-
# # Output:
|
224
|
-
# <form ...>
|
225
|
-
# <fieldset class="inputs">
|
226
|
-
# <ol>
|
227
|
-
# <li class="string">...</li>
|
228
|
-
# <li class="text">...</li>
|
229
|
-
# </ol>
|
230
|
-
# </fieldset>
|
231
|
-
# <fieldset class="inputs" id="advanced">
|
232
|
-
# <legend><span>Advanced</span></legend>
|
233
|
-
# <ol>
|
234
|
-
# <li class="datetime">...</li>
|
235
|
-
# <li class="select">...</li>
|
236
|
-
# </ol>
|
237
|
-
# </fieldset>
|
238
|
-
# <fieldset class="inputs">
|
239
|
-
# <legend><span>Extra</span></legend>
|
240
|
-
# <ol>
|
241
|
-
# <li class="datetime">...</li>
|
242
|
-
# </ol>
|
243
|
-
# </fieldset>
|
244
|
-
# </form>
|
245
|
-
#
|
246
|
-
# === Nested attributes
|
247
|
-
#
|
248
|
-
# As in Rails, you can use semantic_fields_for to nest attributes:
|
249
|
-
#
|
250
|
-
# <% semantic_form_for @post do |form| %>
|
251
|
-
# <%= form.inputs :title, :body %>
|
252
|
-
#
|
253
|
-
# <% form.semantic_fields_for :author, @bob do |author_form| %>
|
254
|
-
# <% author_form.inputs do %>
|
255
|
-
# <%= author_form.input :first_name, :required => false %>
|
256
|
-
# <%= author_form.input :last_name %>
|
257
|
-
# <% end %>
|
258
|
-
# <% end %>
|
259
|
-
# <% end %>
|
260
|
-
#
|
261
|
-
# But this does not look formtastic! This is equivalent:
|
262
|
-
#
|
263
|
-
# <% semantic_form_for @post do |form| %>
|
264
|
-
# <%= form.inputs :title, :body %>
|
265
|
-
# <% form.inputs :for => [ :author, @bob ] do |author_form| %>
|
266
|
-
# <%= author_form.input :first_name, :required => false %>
|
267
|
-
# <%= author_form.input :last_name %>
|
268
|
-
# <% end %>
|
269
|
-
# <% end %>
|
270
|
-
#
|
271
|
-
# And if you don't need to give options to your input call, you could do it
|
272
|
-
# in just one line:
|
273
|
-
#
|
274
|
-
# <% semantic_form_for @post do |form| %>
|
275
|
-
# <%= form.inputs :title, :body %>
|
276
|
-
# <%= form.inputs :first_name, :last_name, :for => @bob %>
|
277
|
-
# <% end %>
|
278
|
-
#
|
279
|
-
# Just remember that calling inputs generates a new fieldset to wrap your
|
280
|
-
# inputs. If you have two separate models, but, semantically, on the page
|
281
|
-
# they are part of the same fieldset, you should use semantic_fields_for
|
282
|
-
# instead (just as you would do with Rails' form builder).
|
283
|
-
#
|
284
|
-
def inputs(*args, &block)
|
285
|
-
title = field_set_title_from_args(*args)
|
286
|
-
html_options = args.extract_options!
|
287
|
-
html_options[:class] ||= "inputs"
|
288
|
-
html_options[:name] = title
|
289
|
-
|
290
|
-
if html_options[:for] # Nested form
|
291
|
-
inputs_for_nested_attributes(*(args << html_options), &block)
|
292
|
-
elsif block_given?
|
293
|
-
field_set_and_list_wrapping(*(args << html_options), &block)
|
294
|
-
else
|
295
|
-
if @object && args.empty?
|
296
|
-
args = association_columns(:belongs_to)
|
297
|
-
args += content_columns
|
298
|
-
args -= RESERVED_COLUMNS
|
299
|
-
args.compact!
|
300
|
-
end
|
301
|
-
legend = args.shift if args.first.is_a?(::String)
|
302
|
-
contents = args.collect { |method| input(method.to_sym) }
|
303
|
-
args.unshift(legend) if legend.present?
|
304
|
-
|
305
|
-
field_set_and_list_wrapping(*((args << html_options) << contents))
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
# Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
|
310
|
-
# See inputs documentation for a full example. The fieldset's default class attriute
|
311
|
-
# is set to "buttons".
|
312
|
-
#
|
313
|
-
# See inputs for html attributes and special options.
|
314
|
-
def buttons(*args, &block)
|
315
|
-
html_options = args.extract_options!
|
316
|
-
html_options[:class] ||= "buttons"
|
317
|
-
|
318
|
-
if block_given?
|
319
|
-
field_set_and_list_wrapping(html_options, &block)
|
320
|
-
else
|
321
|
-
args = [:commit] if args.empty?
|
322
|
-
contents = args.map { |button_name| send(:"#{button_name}_button") }
|
323
|
-
field_set_and_list_wrapping(html_options, contents)
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
# Creates a submit input tag with the value "Save [model name]" (for existing records) or
|
328
|
-
# "Create [model name]" (for new records) by default:
|
329
|
-
#
|
330
|
-
# <%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />
|
331
|
-
#
|
332
|
-
# The value of the button text can be overridden:
|
333
|
-
#
|
334
|
-
# <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
|
335
|
-
# <%= form.commit_button :label => "Go" %> => <input name="commit" type="submit" value="Go" class="{create|update|submit}" />
|
336
|
-
#
|
337
|
-
# And you can pass html atributes down to the input, with or without the button text:
|
338
|
-
#
|
339
|
-
# <%= form.commit_button :button_html => { :class => "pretty" } %> => <input name="commit" type="submit" value="Save Post" class="pretty {create|update|submit}" />
|
340
|
-
def commit_button(*args)
|
341
|
-
options = args.extract_options!
|
342
|
-
text = options.delete(:label) || args.shift
|
343
|
-
|
344
|
-
if @object && (@object.respond_to?(:persisted?) || @object.respond_to?(:new_record?))
|
345
|
-
if @object.respond_to?(:persisted?) # ActiveModel
|
346
|
-
key = @object.persisted? ? :update : :create
|
347
|
-
else # Rails 2
|
348
|
-
key = @object.new_record? ? :create : :update
|
349
|
-
end
|
350
|
-
|
351
|
-
# Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
|
352
|
-
# ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
|
353
|
-
# if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
|
354
|
-
# fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
|
355
|
-
if @object.class.model_name.respond_to?(:human)
|
356
|
-
object_name = @object.class.model_name.human
|
357
|
-
else
|
358
|
-
object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
|
359
|
-
crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
|
360
|
-
decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
|
361
|
-
object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
|
362
|
-
end
|
363
|
-
else
|
364
|
-
key = :submit
|
365
|
-
object_name = @object_name.to_s.send(label_str_method)
|
366
|
-
end
|
367
|
-
|
368
|
-
text = (localized_string(key, text, :action, :model => object_name) ||
|
369
|
-
::Formtastic::I18n.t(key, :model => object_name)) unless text.is_a?(::String)
|
370
|
-
|
371
|
-
button_html = options.delete(:button_html) || {}
|
372
|
-
button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
|
373
|
-
|
374
|
-
wrapper_html_class = ['commit'] # TODO: Add class reflecting on form action.
|
375
|
-
wrapper_html = options.delete(:wrapper_html) || {}
|
376
|
-
wrapper_html[:class] = (wrapper_html_class << wrapper_html[:class]).flatten.compact.join(' ')
|
377
|
-
|
378
|
-
accesskey = (options.delete(:accesskey) || default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
|
379
|
-
button_html = button_html.merge(:accesskey => accesskey) if accesskey
|
380
|
-
template.content_tag(:li, Formtastic::Util.html_safe(submit(text, button_html)), wrapper_html)
|
381
|
-
end
|
382
|
-
|
383
|
-
# A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
|
384
|
-
# for nesting forms:
|
385
|
-
#
|
386
|
-
# # Example:
|
387
|
-
# <% semantic_form_for @post do |post| %>
|
388
|
-
# <% post.semantic_fields_for :author do |author| %>
|
389
|
-
# <% author.inputs :name %>
|
390
|
-
# <% end %>
|
391
|
-
# <% end %>
|
392
|
-
#
|
393
|
-
# # Output:
|
394
|
-
# <form ...>
|
395
|
-
# <fieldset class="inputs">
|
396
|
-
# <ol>
|
397
|
-
# <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
|
398
|
-
# </ol>
|
399
|
-
# </fieldset>
|
400
|
-
# </form>
|
401
|
-
#
|
402
|
-
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
403
|
-
opts = args.extract_options!
|
404
|
-
opts[:builder] ||= self.class
|
405
|
-
args.push(opts)
|
406
|
-
fields_for(record_or_name_or_array, *args, &block)
|
407
|
-
end
|
408
|
-
|
409
|
-
# Generates the label for the input. It also accepts the same arguments as
|
410
|
-
# Rails label method. It has three options that are not supported by Rails
|
411
|
-
# label method:
|
412
|
-
#
|
413
|
-
# * :required - Appends an abbr tag if :required is true
|
414
|
-
# * :label - An alternative form to give the label content. Whenever label
|
415
|
-
# is false, a blank string is returned.
|
416
|
-
# * :input_name - Gives the input to match for. This is needed when you want to
|
417
|
-
# to call f.label :authors but it should match :author_ids.
|
418
|
-
#
|
419
|
-
# == Examples
|
420
|
-
#
|
421
|
-
# f.label :title # like in rails, except that it searches the label on I18n API too
|
422
|
-
#
|
423
|
-
# f.label :title, "Your post title"
|
424
|
-
# f.label :title, :label => "Your post title" # Added for formtastic API
|
425
|
-
#
|
426
|
-
# f.label :title, :required => true # Returns <label>Title<abbr title="required">*</abbr></label>
|
427
|
-
#
|
428
|
-
def label(method, options_or_text=nil, options=nil)
|
429
|
-
if options_or_text.is_a?(Hash)
|
430
|
-
return "" if options_or_text[:label] == false
|
431
|
-
options = options_or_text
|
432
|
-
text = options.delete(:label)
|
433
|
-
else
|
434
|
-
text = options_or_text
|
435
|
-
options ||= {}
|
436
|
-
end
|
437
|
-
|
438
|
-
text = localized_string(method, text, :label) || humanized_attribute_name(method)
|
439
|
-
text += required_or_optional_string(options.delete(:required))
|
440
|
-
text = Formtastic::Util.html_safe(text)
|
441
|
-
|
442
|
-
# special case for boolean (checkbox) labels, which have a nested input
|
443
|
-
if options.key?(:label_prefix_for_nested_input)
|
444
|
-
text = options.delete(:label_prefix_for_nested_input) + text
|
445
|
-
end
|
446
|
-
|
447
|
-
input_name = options.delete(:input_name) || method
|
448
|
-
super(input_name, text, options)
|
449
|
-
end
|
450
|
-
|
451
|
-
# Generates error messages for the given method. Errors can be shown as list,
|
452
|
-
# as sentence or just the first error can be displayed. If :none is set, no error is shown.
|
453
|
-
#
|
454
|
-
# This method is also aliased as errors_on, so you can call on your custom
|
455
|
-
# inputs as well:
|
456
|
-
#
|
457
|
-
# semantic_form_for :post do |f|
|
458
|
-
# f.text_field(:body)
|
459
|
-
# f.errors_on(:body)
|
460
|
-
# end
|
461
|
-
#
|
462
|
-
def inline_errors_for(method, options = {}) #:nodoc:
|
463
|
-
if render_inline_errors?
|
464
|
-
errors = error_keys(method, options).map{|x| @object.errors[x] }.flatten.compact.uniq
|
465
|
-
send(:"error_#{inline_errors}", [*errors], options) if errors.any?
|
466
|
-
else
|
467
|
-
nil
|
468
|
-
end
|
469
|
-
end
|
470
|
-
alias :errors_on :inline_errors_for
|
471
|
-
|
472
|
-
# Generates error messages for given method names and for base.
|
473
|
-
# You can pass a hash with html options that will be added to ul tag
|
474
|
-
#
|
475
|
-
# == Examples
|
476
|
-
#
|
477
|
-
# f.semantic_errors # This will show only errors on base
|
478
|
-
# f.semantic_errors :state # This will show errors on base and state
|
479
|
-
# f.semantic_errors :state, :class => "awesome" # errors will be rendered in ul.awesome
|
480
|
-
#
|
481
|
-
def semantic_errors(*args)
|
482
|
-
html_options = args.extract_options!
|
483
|
-
full_errors = args.inject([]) do |array, method|
|
484
|
-
attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
|
485
|
-
errors = Array(@object.errors[method.to_sym]).to_sentence
|
486
|
-
errors.present? ? array << [attribute, errors].join(" ") : array ||= []
|
487
|
-
end
|
488
|
-
full_errors << @object.errors[:base]
|
489
|
-
full_errors.flatten!
|
490
|
-
full_errors.compact!
|
491
|
-
return nil if full_errors.blank?
|
492
|
-
html_options[:class] ||= "errors"
|
493
|
-
template.content_tag(:ul, html_options) do
|
494
|
-
Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
|
495
|
-
end
|
496
|
-
end
|
497
|
-
|
498
|
-
protected
|
499
|
-
|
500
|
-
def error_keys(method, options)
|
501
|
-
@methods_for_error ||= {}
|
502
|
-
@methods_for_error[method] ||= begin
|
503
|
-
methods_for_error = [method.to_sym]
|
504
|
-
methods_for_error << file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
|
505
|
-
methods_for_error << [association_primary_key(method)] if association_macro_for_method(method) == :belongs_to
|
506
|
-
methods_for_error.flatten.compact.uniq
|
507
|
-
end
|
508
|
-
end
|
509
|
-
|
510
|
-
def has_errors?(method, options)
|
511
|
-
methods_for_error = error_keys(method,options)
|
512
|
-
@object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
|
513
|
-
end
|
514
|
-
|
515
|
-
def render_inline_errors?
|
516
|
-
@object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(inline_errors)
|
517
|
-
end
|
518
|
-
|
519
|
-
# Collects content columns (non-relation columns) for the current form object class.
|
520
|
-
#
|
521
|
-
def content_columns #:nodoc:
|
522
|
-
model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
|
523
|
-
end
|
524
|
-
|
525
|
-
# Collects association columns (relation columns) for the current form object class.
|
526
|
-
#
|
527
|
-
def association_columns(*by_associations) #:nodoc:
|
528
|
-
if @object.present? && @object.class.respond_to?(:reflections)
|
529
|
-
@object.class.reflections.collect do |name, association_reflection|
|
530
|
-
if by_associations.present?
|
531
|
-
name if by_associations.include?(association_reflection.macro)
|
532
|
-
else
|
533
|
-
name
|
534
|
-
end
|
535
|
-
end.compact
|
536
|
-
else
|
537
|
-
[]
|
538
|
-
end
|
539
|
-
end
|
540
|
-
|
541
|
-
# Returns nil, or a symbol like :belongs_to or :has_many
|
542
|
-
def association_macro_for_method(method) #:nodoc:
|
543
|
-
reflection = reflection_for(method)
|
544
|
-
reflection.macro if reflection
|
545
|
-
end
|
546
|
-
|
547
|
-
def association_primary_key(method)
|
548
|
-
reflection = reflection_for(method)
|
549
|
-
reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
|
550
|
-
:"#{method}_id"
|
551
|
-
end
|
552
|
-
|
553
|
-
# Prepare options to be sent to label
|
554
|
-
#
|
555
|
-
def options_for_label(options) #:nodoc:
|
556
|
-
options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
|
557
|
-
end
|
558
|
-
|
559
|
-
# Deals with :for option when it's supplied to inputs methods. Additional
|
560
|
-
# options to be passed down to :for should be supplied using :for_options
|
561
|
-
# key.
|
562
|
-
#
|
563
|
-
# It should raise an error if a block with arity zero is given.
|
564
|
-
#
|
565
|
-
def inputs_for_nested_attributes(*args, &block) #:nodoc:
|
566
|
-
options = args.extract_options!
|
567
|
-
args << options.merge!(:parent => { :builder => self, :for => options[:for] })
|
568
|
-
|
569
|
-
fields_for_block = if block_given?
|
570
|
-
raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
|
571
|
-
'but the block does not accept any argument.' if block.arity <= 0
|
572
|
-
lambda do |f|
|
573
|
-
contents = f.inputs(*args){ block.call(f) }
|
574
|
-
template.concat(contents) if ::Formtastic::Util.rails3?
|
575
|
-
contents
|
576
|
-
end
|
577
|
-
else
|
578
|
-
lambda do |f|
|
579
|
-
contents = f.inputs(*args)
|
580
|
-
template.concat(contents) if ::Formtastic::Util.rails3?
|
581
|
-
contents
|
582
|
-
end
|
583
|
-
end
|
584
|
-
|
585
|
-
fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
|
586
|
-
semantic_fields_for(*fields_for_args, &fields_for_block)
|
587
|
-
end
|
588
|
-
|
589
|
-
# Remove any Formtastic-specific options before passing the down options.
|
590
|
-
#
|
591
|
-
def strip_formtastic_options(options) #:nodoc:
|
592
|
-
options.except(:value_method, :label_method, :collection, :required, :label,
|
593
|
-
:as, :hint, :input_html, :label_html, :value_as_class, :find_options, :class)
|
594
|
-
end
|
595
|
-
|
596
|
-
# Determins if the attribute (eg :title) should be considered required or not.
|
597
|
-
#
|
598
|
-
# * if the :required option was provided in the options hash, the true/false value will be
|
599
|
-
# returned immediately, allowing the view to override any guesswork that follows:
|
600
|
-
#
|
601
|
-
# * if the :required option isn't provided in the options hash, and the ValidationReflection
|
602
|
-
# plugin is installed (http://github.com/redinger/validation_reflection), or the object is
|
603
|
-
# an ActiveModel, true is returned
|
604
|
-
# if the validates_presence_of macro has been used in the class for this attribute, or false
|
605
|
-
# otherwise.
|
606
|
-
#
|
607
|
-
# * if the :required option isn't provided, and validates_presence_of can't be determined, the
|
608
|
-
# configuration option all_fields_required_by_default is used.
|
609
|
-
#
|
610
|
-
def method_required?(attribute) #:nodoc:
|
611
|
-
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
|
612
|
-
|
613
|
-
if @object && @object.class.respond_to?(:reflect_on_validations_for)
|
614
|
-
@object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
|
615
|
-
(validation.macro == :validates_presence_of || validation.macro == :validates_inclusion_of) &&
|
616
|
-
validation.name == attribute_sym &&
|
617
|
-
(validation.options.present? ? options_require_validation?(validation.options) : true)
|
618
|
-
end
|
619
|
-
else
|
620
|
-
if @object && @object.class.respond_to?(:validators_on)
|
621
|
-
!@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence || validator.kind == :inclusion) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
|
622
|
-
else
|
623
|
-
all_fields_required_by_default
|
624
|
-
end
|
625
|
-
end
|
626
|
-
end
|
627
|
-
|
628
|
-
# Determines whether the given options evaluate to true
|
629
|
-
def options_require_validation?(options) #nodoc
|
630
|
-
allow_blank = options[:allow_blank]
|
631
|
-
return !allow_blank unless allow_blank.nil?
|
632
|
-
if_condition = !options[:if].nil?
|
633
|
-
condition = if_condition ? options[:if] : options[:unless]
|
634
|
-
|
635
|
-
condition = if condition.respond_to?(:call)
|
636
|
-
condition.call(@object)
|
637
|
-
elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
|
638
|
-
@object.send(condition)
|
639
|
-
else
|
640
|
-
condition
|
641
|
-
end
|
642
|
-
|
643
|
-
if_condition ? !!condition : !condition
|
644
|
-
end
|
645
|
-
|
646
|
-
def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
|
647
|
-
html_options = options.delete(:input_html) || {}
|
648
|
-
html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text, :phone, :search, :url, :email].include?(type)
|
649
|
-
field_id = generate_html_id(method, "")
|
650
|
-
html_options[:id] ||= field_id
|
651
|
-
label_options = options_for_label(options)
|
652
|
-
label_options[:for] ||= html_options[:id]
|
653
|
-
label(method, label_options) <<
|
654
|
-
send(respond_to?(form_helper_method) ? form_helper_method : :text_field, method, html_options)
|
655
|
-
end
|
656
|
-
|
657
|
-
# Outputs a label and standard Rails text field inside the wrapper.
|
658
|
-
def string_input(method, options)
|
659
|
-
basic_input_helper(:text_field, :string, method, options)
|
660
|
-
end
|
661
|
-
|
662
|
-
# Outputs a label and standard Rails password field inside the wrapper.
|
663
|
-
def password_input(method, options)
|
664
|
-
basic_input_helper(:password_field, :password, method, options)
|
665
|
-
end
|
666
|
-
|
667
|
-
# Outputs a label and standard Rails text field inside the wrapper.
|
668
|
-
def numeric_input(method, options)
|
669
|
-
basic_input_helper(:text_field, :numeric, method, options)
|
670
|
-
end
|
671
|
-
|
672
|
-
# Ouputs a label and standard Rails text area inside the wrapper.
|
673
|
-
def text_input(method, options)
|
674
|
-
basic_input_helper(:text_area, :text, method, options)
|
675
|
-
end
|
676
|
-
|
677
|
-
# Outputs a label and a standard Rails file field inside the wrapper.
|
678
|
-
def file_input(method, options)
|
679
|
-
basic_input_helper(:file_field, :file, method, options)
|
680
|
-
end
|
681
|
-
|
682
|
-
# Outputs a label and a standard Rails email field inside the wrapper.
|
683
|
-
def email_input(method, options)
|
684
|
-
basic_input_helper(:email_field, :email, method, options)
|
685
|
-
end
|
686
|
-
|
687
|
-
# Outputs a label and a standard Rails phone field inside the wrapper.
|
688
|
-
def phone_input(method, options)
|
689
|
-
basic_input_helper(:phone_field, :phone, method, options)
|
690
|
-
end
|
691
|
-
|
692
|
-
# Outputs a label and a standard Rails url field inside the wrapper.
|
693
|
-
def url_input(method, options)
|
694
|
-
basic_input_helper(:url_field, :url, method, options)
|
695
|
-
end
|
696
|
-
|
697
|
-
# Outputs a label and a standard Rails search field inside the wrapper.
|
698
|
-
def search_input(method, options)
|
699
|
-
basic_input_helper(:search_field, :search, method, options)
|
700
|
-
end
|
701
|
-
|
702
|
-
# Outputs a hidden field inside the wrapper, which should be hidden with CSS.
|
703
|
-
# Additionals options can be given using :input_hml. Should :input_html not be
|
704
|
-
# specified every option except for formtastic options will be sent straight
|
705
|
-
# to hidden input element.
|
706
|
-
#
|
707
|
-
def hidden_input(method, options)
|
708
|
-
options ||= {}
|
709
|
-
html_options = options.delete(:input_html) || strip_formtastic_options(options)
|
710
|
-
html_options[:id] ||= generate_html_id(method, "")
|
711
|
-
hidden_field(method, html_options)
|
712
|
-
end
|
713
|
-
|
714
|
-
# Outputs a label and a select box containing options from the parent
|
715
|
-
# (belongs_to, has_many, has_and_belongs_to_many) association. If an association
|
716
|
-
# is has_many or has_and_belongs_to_many the select box will be set as multi-select
|
717
|
-
# and size = 5
|
718
|
-
#
|
719
|
-
# Example (belongs_to):
|
720
|
-
#
|
721
|
-
# f.input :author
|
722
|
-
#
|
723
|
-
# <label for="book_author_id">Author</label>
|
724
|
-
# <select id="book_author_id" name="book[author_id]">
|
725
|
-
# <option value=""></option>
|
726
|
-
# <option value="1">Justin French</option>
|
727
|
-
# <option value="2">Jane Doe</option>
|
728
|
-
# </select>
|
729
|
-
#
|
730
|
-
# Example (has_many):
|
731
|
-
#
|
732
|
-
# f.input :chapters
|
733
|
-
#
|
734
|
-
# <label for="book_chapter_ids">Chapters</label>
|
735
|
-
# <select id="book_chapter_ids" name="book[chapter_ids]">
|
736
|
-
# <option value=""></option>
|
737
|
-
# <option value="1">Chapter 1</option>
|
738
|
-
# <option value="2">Chapter 2</option>
|
739
|
-
# </select>
|
740
|
-
#
|
741
|
-
# Example (has_and_belongs_to_many):
|
742
|
-
#
|
743
|
-
# f.input :authors
|
744
|
-
#
|
745
|
-
# <label for="book_author_ids">Authors</label>
|
746
|
-
# <select id="book_author_ids" name="book[author_ids]">
|
747
|
-
# <option value=""></option>
|
748
|
-
# <option value="1">Justin French</option>
|
749
|
-
# <option value="2">Jane Doe</option>
|
750
|
-
# </select>
|
751
|
-
#
|
752
|
-
#
|
753
|
-
# You can customize the options available in the select by passing in a collection. A collection can be given
|
754
|
-
# as an Array, a Hash or as a String (containing pre-rendered HTML options). If not provided, the choices are
|
755
|
-
# found by inferring the parent's class name from the method name and simply calling all on it
|
756
|
-
# (VehicleOwner.all in the example above).
|
757
|
-
#
|
758
|
-
# Examples:
|
759
|
-
#
|
760
|
-
# f.input :author, :collection => @authors
|
761
|
-
# f.input :author, :collection => Author.all
|
762
|
-
# f.input :author, :collection => [@justin, @kate]
|
763
|
-
# f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
|
764
|
-
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
765
|
-
# f.input :author, :collection => grouped_options_for_select(["North America",[["United States","US"],["Canada","CA"]]])
|
766
|
-
#
|
767
|
-
# The :label_method option allows you to customize the text label inside each option tag two ways:
|
768
|
-
#
|
769
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
770
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
771
|
-
#
|
772
|
-
# Examples:
|
773
|
-
#
|
774
|
-
# f.input :author, :label_method => :full_name
|
775
|
-
# f.input :author, :label_method => :login
|
776
|
-
# f.input :author, :label_method => :full_name_with_post_count
|
777
|
-
# f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
778
|
-
#
|
779
|
-
# The :value_method option provides the same customization of the value attribute of each option tag.
|
780
|
-
#
|
781
|
-
# Examples:
|
782
|
-
#
|
783
|
-
# f.input :author, :value_method => :full_name
|
784
|
-
# f.input :author, :value_method => :login
|
785
|
-
# f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
|
786
|
-
#
|
787
|
-
# You can pass html_options to the select tag using :input_html => {}
|
788
|
-
#
|
789
|
-
# Examples:
|
790
|
-
#
|
791
|
-
# f.input :authors, :input_html => {:size => 20, :multiple => true}
|
792
|
-
#
|
793
|
-
# By default, all select inputs will have a blank option at the top of the list. You can add
|
794
|
-
# a prompt with the :prompt option, or disable the blank option with :include_blank => false.
|
795
|
-
#
|
796
|
-
#
|
797
|
-
# You can group the options in optgroup elements by passing the :group_by option
|
798
|
-
# (Note: only tested for belongs_to relations)
|
799
|
-
#
|
800
|
-
# Examples:
|
801
|
-
#
|
802
|
-
# f.input :author, :group_by => :continent
|
803
|
-
#
|
804
|
-
# All the other options should work as expected. If you want to call a custom method on the
|
805
|
-
# group item. You can include the option:group_label_method
|
806
|
-
# Examples:
|
807
|
-
#
|
808
|
-
# f.input :author, :group_by => :continents, :group_label_method => :something_different
|
809
|
-
#
|
810
|
-
def select_input(method, options)
|
811
|
-
html_options = options.delete(:input_html) || {}
|
812
|
-
html_options[:multiple] = html_options[:multiple] || options.delete(:multiple)
|
813
|
-
html_options.delete(:multiple) if html_options[:multiple].nil?
|
814
|
-
|
815
|
-
reflection = reflection_for(method)
|
816
|
-
if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
|
817
|
-
html_options[:multiple] = true if html_options[:multiple].nil?
|
818
|
-
html_options[:size] ||= 5
|
819
|
-
options[:include_blank] ||= false
|
820
|
-
end
|
821
|
-
options = set_include_blank(options)
|
822
|
-
input_name = generate_association_input_name(method)
|
823
|
-
html_options[:id] ||= generate_html_id(input_name, "")
|
824
|
-
|
825
|
-
select_html = if options[:group_by]
|
826
|
-
# The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
|
827
|
-
# The formtastic user however shouldn't notice this too much.
|
828
|
-
raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
|
829
|
-
label, value = detect_label_and_value_method!(raw_collection, options)
|
830
|
-
group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
|
831
|
-
group_label_method = options[:group_label_method] || detect_label_method(group_collection)
|
832
|
-
group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
|
833
|
-
group_association = options[:group_association] || detect_group_association(method, options[:group_by])
|
834
|
-
|
835
|
-
# Here comes the monster with 8 arguments
|
836
|
-
grouped_collection_select(input_name, group_collection,
|
837
|
-
group_association, group_label_method,
|
838
|
-
value, label,
|
839
|
-
strip_formtastic_options(options), html_options)
|
840
|
-
else
|
841
|
-
collection = find_collection_for_column(method, options)
|
842
|
-
|
843
|
-
select(input_name, collection, strip_formtastic_options(options), html_options)
|
844
|
-
end
|
845
|
-
|
846
|
-
if html_options[:multiple]
|
847
|
-
select_html = create_hidden_field_for_multiple_select(input_name) << select_html
|
848
|
-
end
|
849
|
-
|
850
|
-
label_options = options_for_label(options).merge(:input_name => input_name)
|
851
|
-
label_options[:for] ||= html_options[:id]
|
852
|
-
label(method, label_options) << select_html
|
853
|
-
end
|
854
|
-
|
855
|
-
# Outputs a custom hidden field for multiple selects
|
856
|
-
def create_hidden_field_for_multiple_select(method) #:nodoc:
|
857
|
-
input_name = "#{object_name}[#{method.to_s}][]"
|
858
|
-
template.hidden_field_tag(input_name, '')
|
859
|
-
end
|
860
|
-
|
861
|
-
# Outputs a timezone select input as Rails' time_zone_select helper. You
|
862
|
-
# can give priority zones as option.
|
863
|
-
#
|
864
|
-
# Examples:
|
865
|
-
#
|
866
|
-
# f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
|
867
|
-
def time_zone_input(method, options)
|
868
|
-
html_options = options.delete(:input_html) || {}
|
869
|
-
field_id = generate_html_id(method, "")
|
870
|
-
html_options[:id] ||= field_id
|
871
|
-
label_options = options_for_label(options)
|
872
|
-
label_options[:for] ||= html_options[:id]
|
873
|
-
label(method, label_options) <<
|
874
|
-
time_zone_select(method, options.delete(:priority_zones),
|
875
|
-
strip_formtastic_options(options), html_options)
|
876
|
-
end
|
877
|
-
|
878
|
-
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
879
|
-
# items, one for each possible choice in the belongs_to association. Each li contains a
|
880
|
-
# label and a radio input.
|
881
|
-
#
|
882
|
-
# Example:
|
883
|
-
#
|
884
|
-
# f.input :author, :as => :radio
|
885
|
-
#
|
886
|
-
# Output:
|
887
|
-
#
|
888
|
-
# <fieldset>
|
889
|
-
# <legend><span>Author</span></legend>
|
890
|
-
# <ol>
|
891
|
-
# <li>
|
892
|
-
# <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
|
893
|
-
# </li>
|
894
|
-
# <li>
|
895
|
-
# <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
|
896
|
-
# </li>
|
897
|
-
# </ol>
|
898
|
-
# </fieldset>
|
899
|
-
#
|
900
|
-
# You can customize the choices available in the radio button set by passing in a collection (an Array or
|
901
|
-
# Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
|
902
|
-
# (Author.all in the example above).
|
903
|
-
#
|
904
|
-
# Examples:
|
905
|
-
#
|
906
|
-
# f.input :author, :as => :radio, :collection => @authors
|
907
|
-
# f.input :author, :as => :radio, :collection => Author.all
|
908
|
-
# f.input :author, :as => :radio, :collection => [@justin, @kate]
|
909
|
-
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
910
|
-
#
|
911
|
-
# The :label_method option allows you to customize the label for each radio button two ways:
|
912
|
-
#
|
913
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
914
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
915
|
-
#
|
916
|
-
# Examples:
|
917
|
-
#
|
918
|
-
# f.input :author, :as => :radio, :label_method => :full_name
|
919
|
-
# f.input :author, :as => :radio, :label_method => :login
|
920
|
-
# f.input :author, :as => :radio, :label_method => :full_name_with_post_count
|
921
|
-
# f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
922
|
-
#
|
923
|
-
# The :value_method option provides the same customization of the value attribute of each option tag.
|
924
|
-
#
|
925
|
-
# Examples:
|
926
|
-
#
|
927
|
-
# f.input :author, :as => :radio, :value_method => :full_name
|
928
|
-
# f.input :author, :as => :radio, :value_method => :login
|
929
|
-
# f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
|
930
|
-
#
|
931
|
-
# Finally, you can set :value_as_class => true if you want the li wrapper around each radio
|
932
|
-
# button / label combination to contain a class with the value of the radio button (useful for
|
933
|
-
# applying specific CSS or Javascript to a particular radio button).
|
934
|
-
def radio_input(method, options)
|
935
|
-
collection = find_collection_for_column(method, options)
|
936
|
-
html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})
|
937
|
-
|
938
|
-
input_name = generate_association_input_name(method)
|
939
|
-
value_as_class = options.delete(:value_as_class)
|
940
|
-
input_ids = []
|
941
|
-
|
942
|
-
list_item_content = collection.map do |c|
|
943
|
-
label = c.is_a?(Array) ? c.first : c
|
944
|
-
value = c.is_a?(Array) ? c.last : c
|
945
|
-
input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
946
|
-
input_ids << input_id
|
947
|
-
|
948
|
-
html_options[:id] = input_id
|
949
|
-
|
950
|
-
li_content = template.content_tag(:label,
|
951
|
-
Formtastic::Util.html_safe("#{radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
|
952
|
-
:for => input_id
|
953
|
-
)
|
954
|
-
|
955
|
-
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
956
|
-
template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
|
957
|
-
end
|
958
|
-
|
959
|
-
template.content_tag(:fieldset,
|
960
|
-
legend_tag(method, options) << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
|
961
|
-
)
|
962
|
-
end
|
963
|
-
|
964
|
-
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
965
|
-
# items (li), one for each fragment for the date (year, month, day). Each li contains a label
|
966
|
-
# (eg "Year") and a select box. Overwriting the label is possible by adding the :labels option.
|
967
|
-
# :labels should be a hash with the field (e.g. day) as key and the label text as value.
|
968
|
-
# See date_or_datetime_input for a more detailed output example.
|
969
|
-
#
|
970
|
-
# Some of Rails' options for select_date are supported, but not everything yet, see
|
971
|
-
# documentation of date_or_datetime_input() for more information.
|
972
|
-
def date_input(method, options)
|
973
|
-
options = set_include_blank(options)
|
974
|
-
date_or_datetime_input(method, options.merge(:discard_hour => true))
|
975
|
-
end
|
976
|
-
|
977
|
-
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
978
|
-
# items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
|
979
|
-
# contains a label (eg "Year") and a select box. Overwriting the label is possible by adding
|
980
|
-
# the :labels option. :labels should be a hash with the field (e.g. day) as key and the label
|
981
|
-
# text as value. See date_or_datetime_input for a more detailed output example.
|
982
|
-
#
|
983
|
-
# Some of Rails' options for select_date are supported, but not everything yet, see
|
984
|
-
# documentation of date_or_datetime_input() for more information.
|
985
|
-
def datetime_input(method, options)
|
986
|
-
options = set_include_blank(options)
|
987
|
-
date_or_datetime_input(method, options)
|
988
|
-
end
|
989
|
-
|
990
|
-
# Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
|
991
|
-
# items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
|
992
|
-
# (eg "Hour") and a select box. Overwriting the label is possible by adding the :labels option.
|
993
|
-
# :labels should be a hash with the field (e.g. day) as key and the label text as value.
|
994
|
-
# See date_or_datetime_input for a more detailed output example.
|
995
|
-
#
|
996
|
-
# Some of Rails' options for select_time are supported, but not everything yet, see
|
997
|
-
# documentation of date_or_datetime_input() for more information.
|
998
|
-
def time_input(method, options)
|
999
|
-
options = set_include_blank(options)
|
1000
|
-
date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
|
1001
|
-
end
|
1002
|
-
|
1003
|
-
# Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
|
1004
|
-
# legend (for what would normally be considered the label), and an ordered list of list items
|
1005
|
-
# for year, month, day, hour, etc, each containing a label and a select. Example:
|
1006
|
-
#
|
1007
|
-
# <fieldset>
|
1008
|
-
# <legend>Created At</legend>
|
1009
|
-
# <ol>
|
1010
|
-
# <li>
|
1011
|
-
# <label for="user_created_at_1i">Year</label>
|
1012
|
-
# <select id="user_created_at_1i" name="user[created_at(1i)]">
|
1013
|
-
# <option value="2003">2003</option>
|
1014
|
-
# ...
|
1015
|
-
# <option value="2013">2013</option>
|
1016
|
-
# </select>
|
1017
|
-
# </li>
|
1018
|
-
# <li>
|
1019
|
-
# <label for="user_created_at_2i">Month</label>
|
1020
|
-
# <select id="user_created_at_2i" name="user[created_at(2i)]">
|
1021
|
-
# <option value="1">January</option>
|
1022
|
-
# ...
|
1023
|
-
# <option value="12">December</option>
|
1024
|
-
# </select>
|
1025
|
-
# </li>
|
1026
|
-
# <li>
|
1027
|
-
# <label for="user_created_at_3i">Day</label>
|
1028
|
-
# <select id="user_created_at_3i" name="user[created_at(3i)]">
|
1029
|
-
# <option value="1">1</option>
|
1030
|
-
# ...
|
1031
|
-
# <option value="31">31</option>
|
1032
|
-
# </select>
|
1033
|
-
# </li>
|
1034
|
-
# </ol>
|
1035
|
-
# </fieldset>
|
1036
|
-
#
|
1037
|
-
# This is an absolute abomination, but so is the official Rails select_date().
|
1038
|
-
#
|
1039
|
-
# Options:
|
1040
|
-
#
|
1041
|
-
# * @:order => [:month, :day, :year]@
|
1042
|
-
# * @:include_seconds@ => true@
|
1043
|
-
# * @:discard_(year|month|day|hour|minute) => true@
|
1044
|
-
# * @:include_blank => true@
|
1045
|
-
# * @:labels => {}@
|
1046
|
-
def date_or_datetime_input(method, options)
|
1047
|
-
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
1048
|
-
i18n_date_order = ::I18n.t(:order, :scope => [:date])
|
1049
|
-
i18n_date_order = nil unless i18n_date_order.is_a?(Array)
|
1050
|
-
inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
|
1051
|
-
inputs = [] if options[:ignore_date]
|
1052
|
-
labels = options.delete(:labels) || {}
|
1053
|
-
|
1054
|
-
time_inputs = [:hour, :minute]
|
1055
|
-
time_inputs << :second if options[:include_seconds]
|
1056
|
-
|
1057
|
-
list_items_capture = ""
|
1058
|
-
hidden_fields_capture = ""
|
1059
|
-
|
1060
|
-
datetime = @object.send(method) if @object && @object.send(method)
|
1061
|
-
|
1062
|
-
html_options = options.delete(:input_html) || {}
|
1063
|
-
input_ids = []
|
1064
|
-
|
1065
|
-
(inputs + time_inputs).each do |input|
|
1066
|
-
input_ids << input_id = generate_html_id(method, "#{position[input]}i")
|
1067
|
-
|
1068
|
-
field_name = "#{method}(#{position[input]}i)"
|
1069
|
-
if options[:"discard_#{input}"]
|
1070
|
-
break if time_inputs.include?(input)
|
1071
|
-
|
1072
|
-
hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
|
1073
|
-
hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
|
1074
|
-
else
|
1075
|
-
opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
|
1076
|
-
item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
|
1077
|
-
|
1078
|
-
list_items_capture << template.content_tag(:li, Formtastic::Util.html_safe([
|
1079
|
-
!item_label_text.blank? ? template.content_tag(:label, Formtastic::Util.html_safe(item_label_text), :for => input_id) : "",
|
1080
|
-
template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
|
1081
|
-
].join(""))
|
1082
|
-
)
|
1083
|
-
end
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
hidden_fields_capture << field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_items_capture)
|
1087
|
-
end
|
1088
|
-
|
1089
|
-
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
1090
|
-
# items, one for each possible choice in the belongs_to association. Each li contains a
|
1091
|
-
# label and a check_box input.
|
1092
|
-
#
|
1093
|
-
# This is an alternative for has many and has and belongs to many associations.
|
1094
|
-
#
|
1095
|
-
# Example:
|
1096
|
-
#
|
1097
|
-
# f.input :author, :as => :check_boxes
|
1098
|
-
#
|
1099
|
-
# Output:
|
1100
|
-
#
|
1101
|
-
# <fieldset>
|
1102
|
-
# <legend class="label"><label>Authors</label></legend>
|
1103
|
-
# <ol>
|
1104
|
-
# <li>
|
1105
|
-
# <input type="hidden" name="book[author_id][1]" value="">
|
1106
|
-
# <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id][1]" type="checkbox" value="1" /> Justin French</label>
|
1107
|
-
# </li>
|
1108
|
-
# <li>
|
1109
|
-
# <input type="hidden" name="book[author_id][2]" value="">
|
1110
|
-
# <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id][2]" type="checkbox" value="2" /> Kate French</label>
|
1111
|
-
# </li>
|
1112
|
-
# </ol>
|
1113
|
-
# </fieldset>
|
1114
|
-
#
|
1115
|
-
# Notice that the value of the checkbox is the same as the id and the hidden
|
1116
|
-
# field has empty value. You can override the hidden field value using the
|
1117
|
-
# unchecked_value option.
|
1118
|
-
#
|
1119
|
-
# You can customize the options available in the set by passing in a collection (Array) of
|
1120
|
-
# ActiveRecord objects through the :collection option. If not provided, the choices are found
|
1121
|
-
# by inferring the parent's class name from the method name and simply calling all on
|
1122
|
-
# it (Author.all in the example above).
|
1123
|
-
#
|
1124
|
-
# Examples:
|
1125
|
-
#
|
1126
|
-
# f.input :author, :as => :check_boxes, :collection => @authors
|
1127
|
-
# f.input :author, :as => :check_boxes, :collection => Author.all
|
1128
|
-
# f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
|
1129
|
-
#
|
1130
|
-
# The :label_method option allows you to customize the label for each checkbox two ways:
|
1131
|
-
#
|
1132
|
-
# * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
|
1133
|
-
# * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
|
1134
|
-
#
|
1135
|
-
# Examples:
|
1136
|
-
#
|
1137
|
-
# f.input :author, :as => :check_boxes, :label_method => :full_name
|
1138
|
-
# f.input :author, :as => :check_boxes, :label_method => :login
|
1139
|
-
# f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
|
1140
|
-
# f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
|
1141
|
-
#
|
1142
|
-
# The :value_method option provides the same customization of the value attribute of each checkbox input tag.
|
1143
|
-
#
|
1144
|
-
# Examples:
|
1145
|
-
#
|
1146
|
-
# f.input :author, :as => :check_boxes, :value_method => :full_name
|
1147
|
-
# f.input :author, :as => :check_boxes, :value_method => :login
|
1148
|
-
# f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
|
1149
|
-
#
|
1150
|
-
# Formtastic works around a bug in rails handling of check box collections by
|
1151
|
-
# not generating the hidden fields for state checking of the checkboxes
|
1152
|
-
# The :hidden_fields option provides a way to re-enable these hidden inputs by
|
1153
|
-
# setting it to true.
|
1154
|
-
#
|
1155
|
-
# f.input :authors, :as => :check_boxes, :hidden_fields => false
|
1156
|
-
# f.input :authors, :as => :check_boxes, :hidden_fields => true
|
1157
|
-
#
|
1158
|
-
# Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
|
1159
|
-
# combination to contain a class with the value of the radio button (useful for applying specific
|
1160
|
-
# CSS or Javascript to a particular checkbox).
|
1161
|
-
#
|
1162
|
-
def check_boxes_input(method, options)
|
1163
|
-
collection = find_collection_for_column(method, options)
|
1164
|
-
html_options = options.delete(:input_html) || {}
|
1165
|
-
|
1166
|
-
input_name = generate_association_input_name(method)
|
1167
|
-
hidden_fields = options.delete(:hidden_fields)
|
1168
|
-
value_as_class = options.delete(:value_as_class)
|
1169
|
-
unchecked_value = options.delete(:unchecked_value) || ''
|
1170
|
-
html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
|
1171
|
-
input_ids = []
|
1172
|
-
|
1173
|
-
selected_values = find_selected_values_for_column(method, options)
|
1174
|
-
disabled_option_is_present = options.key?(:disabled)
|
1175
|
-
disabled_values = [*options[:disabled]] if disabled_option_is_present
|
1176
|
-
|
1177
|
-
li_options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
|
1178
|
-
|
1179
|
-
list_item_content = collection.map do |c|
|
1180
|
-
label = c.is_a?(Array) ? c.first : c
|
1181
|
-
value = c.is_a?(Array) ? c.last : c
|
1182
|
-
input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
1183
|
-
input_ids << input_id
|
1184
|
-
|
1185
|
-
html_options[:checked] = selected_values.include?(value)
|
1186
|
-
html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
|
1187
|
-
html_options[:id] = input_id
|
1188
|
-
|
1189
|
-
li_content = template.content_tag(:label,
|
1190
|
-
Formtastic::Util.html_safe("#{create_check_boxes(input_name, html_options, value, unchecked_value, hidden_fields)} #{escape_html_entities(label)}"),
|
1191
|
-
:for => input_id
|
1192
|
-
)
|
1193
|
-
|
1194
|
-
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
1195
|
-
template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
fieldset_content = legend_tag(method, options)
|
1199
|
-
fieldset_content << create_hidden_field_for_check_boxes(input_name, value_as_class) unless hidden_fields
|
1200
|
-
fieldset_content << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
|
1201
|
-
template.content_tag(:fieldset, fieldset_content)
|
1202
|
-
end
|
1203
|
-
|
1204
|
-
# Used by check_boxes input. The selected values will be set by retrieving the value
|
1205
|
-
# through the association.
|
1206
|
-
#
|
1207
|
-
# If the collection is not a hash or an array of strings, fixnums or symbols,
|
1208
|
-
# we use value_method to retrieve an array with the values
|
1209
|
-
def find_selected_values_for_column(method, options)
|
1210
|
-
if object.respond_to?(method)
|
1211
|
-
collection = [object.send(method)].compact.flatten
|
1212
|
-
label, value = detect_label_and_value_method!(collection, options)
|
1213
|
-
[*collection.map { |o| send_or_call(value, o) }].compact
|
1214
|
-
else
|
1215
|
-
[]
|
1216
|
-
end
|
1217
|
-
end
|
1218
|
-
|
1219
|
-
# Outputs a custom hidden field for check_boxes
|
1220
|
-
def create_hidden_field_for_check_boxes(method, value_as_class) #:nodoc:
|
1221
|
-
options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
|
1222
|
-
input_name = "#{object_name}[#{method.to_s}][]"
|
1223
|
-
template.hidden_field_tag(input_name, '', options)
|
1224
|
-
end
|
1225
|
-
|
1226
|
-
# Outputs a checkbox tag. If called with no_hidden_input = true a plain check_box_tag is returned,
|
1227
|
-
# otherwise the helper uses the output generated by the rails check_box method.
|
1228
|
-
def create_check_boxes(input_name, html_options = {}, checked_value = "1", unchecked_value = "0", hidden_fields = false) #:nodoc:
|
1229
|
-
return template.check_box_tag(input_name, checked_value, html_options[:checked], html_options) unless hidden_fields == true
|
1230
|
-
check_box(input_name, html_options, checked_value, unchecked_value)
|
1231
|
-
end
|
1232
|
-
|
1233
|
-
# Outputs a country select input, wrapping around a regular country_select helper.
|
1234
|
-
# Rails doesn't come with a country_select helper by default any more, so you'll need to install
|
1235
|
-
# the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
|
1236
|
-
# same way.
|
1237
|
-
#
|
1238
|
-
# The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
|
1239
|
-
#
|
1240
|
-
# By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
|
1241
|
-
# which you can change to suit your market and user base (see README for more info on config).
|
1242
|
-
#
|
1243
|
-
# Examples:
|
1244
|
-
# f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
|
1245
|
-
# f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
|
1246
|
-
#
|
1247
|
-
def country_input(method, options)
|
1248
|
-
raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless respond_to?(:country_select)
|
1249
|
-
|
1250
|
-
html_options = options.delete(:input_html) || {}
|
1251
|
-
priority_countries = options.delete(:priority_countries) || self.priority_countries
|
1252
|
-
|
1253
|
-
field_id = generate_html_id(method, "")
|
1254
|
-
html_options[:id] ||= field_id
|
1255
|
-
label_options = options_for_label(options)
|
1256
|
-
label_options[:for] ||= html_options[:id]
|
1257
|
-
|
1258
|
-
label(method, label_options) <<
|
1259
|
-
country_select(method, priority_countries, strip_formtastic_options(options), html_options)
|
1260
|
-
end
|
1261
|
-
|
1262
|
-
# Outputs a label containing a checkbox and the label text. The label defaults
|
1263
|
-
# to the column name (method name) and can be altered with the :label option.
|
1264
|
-
# :checked_value and :unchecked_value options are also available.
|
1265
|
-
def boolean_input(method, options)
|
1266
|
-
html_options = options.delete(:input_html) || {}
|
1267
|
-
checked_value = options.delete(:checked_value) || '1'
|
1268
|
-
unchecked_value = options.delete(:unchecked_value) || '0'
|
1269
|
-
checked = @object && ActionView::Helpers::InstanceTag.check_box_checked?(@object.send(:"#{method}"), checked_value)
|
1270
|
-
|
1271
|
-
html_options[:id] = html_options[:id] || generate_html_id(method, "")
|
1272
|
-
input = template.check_box_tag(
|
1273
|
-
"#{@object_name}[#{method}]",
|
1274
|
-
checked_value,
|
1275
|
-
checked,
|
1276
|
-
html_options
|
1277
|
-
)
|
1278
|
-
|
1279
|
-
options = options_for_label(options)
|
1280
|
-
options[:for] ||= html_options[:id]
|
1281
|
-
|
1282
|
-
# the label() method will insert this nested input into the label at the last minute
|
1283
|
-
options[:label_prefix_for_nested_input] = input
|
1284
|
-
|
1285
|
-
template.hidden_field_tag((html_options[:name] || "#{@object_name}[#{method}]"), unchecked_value, :id => nil, :disabled => html_options[:disabled]) << label(method, options)
|
1286
|
-
end
|
1287
|
-
|
1288
|
-
# Generates an input for the given method using the type supplied with :as.
|
1289
|
-
def inline_input_for(method, options)
|
1290
|
-
send(:"#{options.delete(:as)}_input", method, options)
|
1291
|
-
end
|
1292
|
-
|
1293
|
-
# Generates hints for the given method using the text supplied in :hint.
|
1294
|
-
#
|
1295
|
-
def inline_hints_for(method, options) #:nodoc:
|
1296
|
-
options[:hint] = localized_string(method, options[:hint], :hint)
|
1297
|
-
return if options[:hint].blank? or options[:hint].kind_of? Hash
|
1298
|
-
hint_class = options[:hint_class] || default_hint_class
|
1299
|
-
template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => hint_class)
|
1300
|
-
end
|
1301
|
-
|
1302
|
-
# Creates an error sentence by calling to_sentence on the errors array.
|
1303
|
-
#
|
1304
|
-
def error_sentence(errors, options = {}) #:nodoc:
|
1305
|
-
error_class = options[:error_class] || default_inline_error_class
|
1306
|
-
template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => error_class)
|
1307
|
-
end
|
1308
|
-
|
1309
|
-
# Creates an error li list.
|
1310
|
-
#
|
1311
|
-
def error_list(errors, options = {}) #:nodoc:
|
1312
|
-
error_class = options[:error_class] || default_error_list_class
|
1313
|
-
list_elements = []
|
1314
|
-
errors.each do |error|
|
1315
|
-
list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
|
1316
|
-
end
|
1317
|
-
template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
|
1318
|
-
end
|
1319
|
-
|
1320
|
-
# Creates an error sentence containing only the first error
|
1321
|
-
#
|
1322
|
-
def error_first(errors, options = {}) #:nodoc:
|
1323
|
-
error_class = options[:error_class] || default_inline_error_class
|
1324
|
-
template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
|
1325
|
-
end
|
1326
|
-
|
1327
|
-
# Generates the required or optional string. If the value set is a proc,
|
1328
|
-
# it evaluates the proc first.
|
1329
|
-
#
|
1330
|
-
def required_or_optional_string(required) #:nodoc:
|
1331
|
-
string_or_proc = case required
|
1332
|
-
when true
|
1333
|
-
required_string
|
1334
|
-
when false
|
1335
|
-
optional_string
|
1336
|
-
else
|
1337
|
-
required
|
1338
|
-
end
|
1339
|
-
|
1340
|
-
if string_or_proc.is_a?(Proc)
|
1341
|
-
string_or_proc.call
|
1342
|
-
else
|
1343
|
-
string_or_proc.to_s
|
1344
|
-
end
|
1345
|
-
end
|
1346
|
-
|
1347
|
-
# Generates a fieldset and wraps the content in an ordered list. When working
|
1348
|
-
# with nested attributes (in Rails 2.3), it allows %i as interpolation option
|
1349
|
-
# in :name. So you can do:
|
1350
|
-
#
|
1351
|
-
# f.inputs :name => 'Task #%i', :for => :tasks
|
1352
|
-
#
|
1353
|
-
# or the shorter equivalent:
|
1354
|
-
#
|
1355
|
-
# f.inputs 'Task #%i', :for => :tasks
|
1356
|
-
#
|
1357
|
-
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
|
1358
|
-
# 'Task #3' and so on.
|
1359
|
-
#
|
1360
|
-
# Note: Special case for the inline inputs (non-block):
|
1361
|
-
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
1362
|
-
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
1363
|
-
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
1364
|
-
#
|
1365
|
-
def field_set_and_list_wrapping(*args, &block) #:nodoc:
|
1366
|
-
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
|
1367
|
-
html_options = args.extract_options!
|
1368
|
-
|
1369
|
-
legend = html_options.dup.delete(:name).to_s
|
1370
|
-
legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
|
1371
|
-
legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
|
1372
|
-
|
1373
|
-
if block_given?
|
1374
|
-
contents = if template.respond_to?(:is_haml?) && template.is_haml?
|
1375
|
-
template.capture_haml(&block)
|
1376
|
-
else
|
1377
|
-
template.capture(&block)
|
1378
|
-
end
|
1379
|
-
end
|
1380
|
-
|
1381
|
-
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
1382
|
-
contents = contents.join if contents.respond_to?(:join)
|
1383
|
-
fieldset = template.content_tag(:fieldset,
|
1384
|
-
Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
|
1385
|
-
html_options.except(:builder, :parent)
|
1386
|
-
)
|
1387
|
-
|
1388
|
-
template.concat(fieldset) if block_given? && !Formtastic::Util.rails3?
|
1389
|
-
fieldset
|
1390
|
-
end
|
1391
|
-
|
1392
|
-
def field_set_title_from_args(*args) #:nodoc:
|
1393
|
-
options = args.extract_options!
|
1394
|
-
options[:name] ||= options.delete(:title)
|
1395
|
-
title = options[:name]
|
1396
|
-
|
1397
|
-
if title.blank?
|
1398
|
-
valid_name_classes = [::String, ::Symbol]
|
1399
|
-
valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && content_columns.include?(args.first))
|
1400
|
-
title = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
|
1401
|
-
end
|
1402
|
-
title = localized_string(title, title, :title) if title.is_a?(::Symbol)
|
1403
|
-
title
|
1404
|
-
end
|
1405
|
-
|
1406
|
-
# Also generates a fieldset and an ordered list but with label based in
|
1407
|
-
# method. This methods is currently used by radio and datetime inputs.
|
1408
|
-
#
|
1409
|
-
def field_set_and_list_wrapping_for_method(method, options, contents) #:nodoc:
|
1410
|
-
contents = contents.join if contents.respond_to?(:join)
|
1411
|
-
|
1412
|
-
template.content_tag(:fieldset,
|
1413
|
-
template.content_tag(:legend,
|
1414
|
-
label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
|
1415
|
-
) <<
|
1416
|
-
template.content_tag(:ol, Formtastic::Util.html_safe(contents))
|
1417
|
-
)
|
1418
|
-
end
|
1419
|
-
|
1420
|
-
# Generates the legend for radiobuttons and checkboxes
|
1421
|
-
def legend_tag(method, options = {})
|
1422
|
-
if options[:label] == false
|
1423
|
-
Formtastic::Util.html_safe("")
|
1424
|
-
else
|
1425
|
-
text = localized_string(method, options[:label], :label) || humanized_attribute_name(method)
|
1426
|
-
text += required_or_optional_string(options.delete(:required))
|
1427
|
-
text = Formtastic::Util.html_safe(text)
|
1428
|
-
template.content_tag :legend, template.label_tag(nil, text, :for => nil), :class => :label
|
1429
|
-
end
|
1430
|
-
end
|
1431
|
-
|
1432
|
-
# For methods that have a database column, take a best guess as to what the input method
|
1433
|
-
# should be. In most cases, it will just return the column type (eg :string), but for special
|
1434
|
-
# cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
|
1435
|
-
# something different (like :password and :select).
|
1436
|
-
#
|
1437
|
-
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
1438
|
-
# default is a :string, a similar behaviour to Rails' scaffolding.
|
1439
|
-
#
|
1440
|
-
def default_input_type(method, options = {}) #:nodoc:
|
1441
|
-
if column = column_for(method)
|
1442
|
-
# Special cases where the column type doesn't map to an input method.
|
1443
|
-
case column.type
|
1444
|
-
when :string
|
1445
|
-
return :password if method.to_s =~ /password/
|
1446
|
-
return :country if method.to_s =~ /country$/
|
1447
|
-
return :time_zone if method.to_s =~ /time_zone/
|
1448
|
-
return :email if method.to_s =~ /email/
|
1449
|
-
return :url if method.to_s =~ /^url$|^website$|_url$/
|
1450
|
-
return :phone if method.to_s =~ /(phone|fax)/
|
1451
|
-
return :search if method.to_s =~ /^search$/
|
1452
|
-
when :integer
|
1453
|
-
return :select if reflection_for(method)
|
1454
|
-
return :numeric
|
1455
|
-
when :float, :decimal
|
1456
|
-
return :numeric
|
1457
|
-
when :timestamp
|
1458
|
-
return :datetime
|
1459
|
-
end
|
1460
|
-
|
1461
|
-
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
1462
|
-
return :select if column.type == :string && options.key?(:collection)
|
1463
|
-
# Try 3: Assume the input name will be the same as the column type (e.g. string_input).
|
1464
|
-
return column.type
|
1465
|
-
else
|
1466
|
-
if @object
|
1467
|
-
return :select if reflection_for(method)
|
1468
|
-
|
1469
|
-
return :file if is_file?(method, options)
|
1470
|
-
end
|
1471
|
-
|
1472
|
-
return :select if options.key?(:collection)
|
1473
|
-
return :password if method.to_s =~ /password/
|
1474
|
-
return :string
|
1475
|
-
end
|
1476
|
-
end
|
1477
|
-
|
1478
|
-
def is_file?(method, options = {})
|
1479
|
-
@files ||= {}
|
1480
|
-
@files[method] ||= (options[:as].present? && options[:as] == :file) || begin
|
1481
|
-
file = @object.send(method) if @object && @object.respond_to?(method)
|
1482
|
-
file && file_methods.any?{|m| file.respond_to?(m)}
|
1483
|
-
end
|
1484
|
-
end
|
1485
|
-
|
1486
|
-
# Used by select and radio inputs. The collection can be retrieved by
|
1487
|
-
# three ways:
|
1488
|
-
#
|
1489
|
-
# * Explicitly provided through :collection
|
1490
|
-
# * Retrivied through an association
|
1491
|
-
# * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
|
1492
|
-
#
|
1493
|
-
# If the collection is not a hash or an array of strings, fixnums or arrays,
|
1494
|
-
# we use label_method and value_method to retreive an array with the
|
1495
|
-
# appropriate label and value.
|
1496
|
-
#
|
1497
|
-
def find_collection_for_column(column, options) #:nodoc:
|
1498
|
-
collection = find_raw_collection_for_column(column, options)
|
1499
|
-
|
1500
|
-
# Return if we have a plain string
|
1501
|
-
return collection if collection.instance_of?(String) || collection.instance_of?(::Formtastic::Util.rails_safe_buffer_class)
|
1502
|
-
|
1503
|
-
# Return if we have an Array of strings, fixnums or arrays
|
1504
|
-
return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
|
1505
|
-
[Array, Fixnum, String, Symbol].include?(collection.first.class) &&
|
1506
|
-
!(options.include?(:label_method) || options.include?(:value_method))
|
1507
|
-
|
1508
|
-
label, value = detect_label_and_value_method!(collection, options)
|
1509
|
-
collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
|
1510
|
-
end
|
1511
|
-
|
1512
|
-
# As #find_collection_for_column but returns the collection without mapping the label and value
|
1513
|
-
#
|
1514
|
-
def find_raw_collection_for_column(column, options) #:nodoc:
|
1515
|
-
collection = if options[:collection]
|
1516
|
-
options.delete(:collection)
|
1517
|
-
elsif reflection = reflection_for(column)
|
1518
|
-
options[:find_options] ||= {}
|
1519
|
-
|
1520
|
-
if conditions = reflection.options[:conditions]
|
1521
|
-
if reflection.klass.respond_to?(:merge_conditions)
|
1522
|
-
options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
|
1523
|
-
reflection.klass.all(options[:find_options])
|
1524
|
-
else
|
1525
|
-
reflection.klass.where(conditions).where(options[:find_options][:conditions])
|
1526
|
-
end
|
1527
|
-
else
|
1528
|
-
reflection.klass.all(options[:find_options])
|
1529
|
-
end
|
1530
|
-
else
|
1531
|
-
create_boolean_collection(options)
|
1532
|
-
end
|
1533
|
-
|
1534
|
-
collection = collection.to_a if collection.is_a?(Hash)
|
1535
|
-
collection
|
1536
|
-
end
|
1537
|
-
|
1538
|
-
# Detects the label and value methods from a collection using methods set in
|
1539
|
-
# collection_label_methods and collection_value_methods. For some ruby core
|
1540
|
-
# classes sensible defaults have been defined. It will use and delete the options
|
1541
|
-
# :label_method and :value_methods when present.
|
1542
|
-
#
|
1543
|
-
def detect_label_and_value_method!(collection, options = {})
|
1544
|
-
sample = collection.first || collection.last
|
1545
|
-
|
1546
|
-
case sample
|
1547
|
-
when Array
|
1548
|
-
label, value = :first, :last
|
1549
|
-
when Integer
|
1550
|
-
label, value = :to_s, :to_i
|
1551
|
-
when String, NilClass
|
1552
|
-
label, value = :to_s, :to_s
|
1553
|
-
end
|
1554
|
-
|
1555
|
-
# Order of preference: user supplied method, class defaults, auto-detect
|
1556
|
-
label = options[:label_method] || label || collection_label_methods.find { |m| sample.respond_to?(m) }
|
1557
|
-
value = options[:value_method] || value || collection_value_methods.find { |m| sample.respond_to?(m) }
|
1558
|
-
|
1559
|
-
[label, value]
|
1560
|
-
end
|
1561
|
-
|
1562
|
-
# Return the label collection method when none is supplied using the
|
1563
|
-
# values set in collection_label_methods.
|
1564
|
-
#
|
1565
|
-
def detect_label_method(collection) #:nodoc:
|
1566
|
-
detect_label_and_value_method!(collection).first
|
1567
|
-
end
|
1568
|
-
|
1569
|
-
# Detects the method to call for fetching group members from the groups when grouping select options
|
1570
|
-
#
|
1571
|
-
def detect_group_association(method, group_by)
|
1572
|
-
object_to_method_reflection = reflection_for(method)
|
1573
|
-
method_class = object_to_method_reflection.klass
|
1574
|
-
|
1575
|
-
method_to_group_association = method_class.reflect_on_association(group_by)
|
1576
|
-
group_class = method_to_group_association.klass
|
1577
|
-
|
1578
|
-
# This will return in the normal case
|
1579
|
-
return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
|
1580
|
-
|
1581
|
-
# This is for belongs_to associations named differently than their class
|
1582
|
-
# form.input :parent, :group_by => :customer
|
1583
|
-
# eg.
|
1584
|
-
# class Project
|
1585
|
-
# belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
|
1586
|
-
# belongs_to :customer
|
1587
|
-
# end
|
1588
|
-
# class Customer
|
1589
|
-
# has_many :projects
|
1590
|
-
# end
|
1591
|
-
group_method = method_class.to_s.underscore.pluralize.to_sym
|
1592
|
-
return group_method if group_class.reflect_on_association(group_method) # :projects
|
1593
|
-
|
1594
|
-
# This is for has_many associations named differently than their class
|
1595
|
-
# eg.
|
1596
|
-
# class Project
|
1597
|
-
# belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
|
1598
|
-
# belongs_to :customer
|
1599
|
-
# end
|
1600
|
-
# class Customer
|
1601
|
-
# has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
|
1602
|
-
# end
|
1603
|
-
possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
|
1604
|
-
return possible_associations.first.name.to_sym if possible_associations.count == 1
|
1605
|
-
|
1606
|
-
raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
|
1607
|
-
|
1608
|
-
end
|
1609
|
-
|
1610
|
-
# Returns a hash to be used by radio and select inputs when a boolean field
|
1611
|
-
# is provided.
|
1612
|
-
#
|
1613
|
-
def create_boolean_collection(options) #:nodoc:
|
1614
|
-
options[:true] ||= ::Formtastic::I18n.t(:yes)
|
1615
|
-
options[:false] ||= ::Formtastic::I18n.t(:no)
|
1616
|
-
options[:value_as_class] = true unless options.key?(:value_as_class)
|
1617
|
-
|
1618
|
-
[ [ options.delete(:true), true], [ options.delete(:false), false ] ]
|
1619
|
-
end
|
1620
|
-
|
1621
|
-
# Used by association inputs (select, radio) to generate the name that should
|
1622
|
-
# be used for the input
|
1623
|
-
#
|
1624
|
-
# belongs_to :author; f.input :author; will generate 'author_id'
|
1625
|
-
# belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
|
1626
|
-
# has_many :authors; f.input :authors; will generate 'author_ids'
|
1627
|
-
# has_and_belongs_to_many will act like has_many
|
1628
|
-
#
|
1629
|
-
def generate_association_input_name(method) #:nodoc:
|
1630
|
-
if reflection = reflection_for(method)
|
1631
|
-
if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
|
1632
|
-
"#{method.to_s.singularize}_ids"
|
1633
|
-
elsif reflection.respond_to? :foreign_key
|
1634
|
-
reflection.foreign_key
|
1635
|
-
else
|
1636
|
-
reflection.options[:foreign_key] || "#{method}_id"
|
1637
|
-
end
|
1638
|
-
else
|
1639
|
-
method
|
1640
|
-
end.to_sym
|
1641
|
-
end
|
1642
|
-
|
1643
|
-
# If an association method is passed in (f.input :author) try to find the
|
1644
|
-
# reflection object.
|
1645
|
-
#
|
1646
|
-
def reflection_for(method) #:nodoc:
|
1647
|
-
@object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
|
1648
|
-
end
|
1649
|
-
|
1650
|
-
# Get a column object for a specified attribute method - if possible.
|
1651
|
-
#
|
1652
|
-
def column_for(method) #:nodoc:
|
1653
|
-
@object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
1654
|
-
end
|
1655
|
-
|
1656
|
-
# Returns the active validations for the given method or an empty Array if no validations are
|
1657
|
-
# found for the method.
|
1658
|
-
#
|
1659
|
-
# By default, the if/unless options of the validations are evaluated and only the validations
|
1660
|
-
# that should be run for the current object state are returned. Pass :all to the last
|
1661
|
-
# parameter to return :all validations regardless of if/unless options.
|
1662
|
-
#
|
1663
|
-
# Requires the ValidationReflection plugin to be present or an ActiveModel. Returns an epmty
|
1664
|
-
# Array if neither is the case.
|
1665
|
-
#
|
1666
|
-
def validations_for(method, mode = :active)
|
1667
|
-
# ActiveModel?
|
1668
|
-
validations = if @object && @object.class.respond_to?(:validators_on)
|
1669
|
-
@object.class.validators_on(method)
|
1670
|
-
else
|
1671
|
-
# ValidationReflection plugin?
|
1672
|
-
if @object && @object.class.respond_to?(:reflect_on_validations_for)
|
1673
|
-
@object.class.reflect_on_validations_for(method)
|
1674
|
-
else
|
1675
|
-
[]
|
1676
|
-
end
|
1677
|
-
end
|
1678
|
-
|
1679
|
-
validations = validations.select do |validation|
|
1680
|
-
(validation.options.present? ? options_require_validation?(validation.options) : true)
|
1681
|
-
end unless mode == :all
|
1682
|
-
|
1683
|
-
return validations
|
1684
|
-
end
|
1685
|
-
|
1686
|
-
# Generates default_string_options by retrieving column information from
|
1687
|
-
# the database.
|
1688
|
-
#
|
1689
|
-
def default_string_options(method, type) #:nodoc:
|
1690
|
-
def get_maxlength_for(method)
|
1691
|
-
validation = validations_for(method).find do |validation|
|
1692
|
-
(validation.respond_to?(:macro) && validation.macro == :validates_length_of) || # Rails 2 validation
|
1693
|
-
(validation.respond_to?(:kind) && validation.kind == :length) # Rails 3 validator
|
1694
|
-
end
|
1695
|
-
|
1696
|
-
if validation
|
1697
|
-
validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
|
1698
|
-
else
|
1699
|
-
nil
|
1700
|
-
end
|
1701
|
-
end
|
1702
|
-
|
1703
|
-
validation_max_limit = get_maxlength_for(method)
|
1704
|
-
column = column_for(method)
|
1705
|
-
|
1706
|
-
if type == :text
|
1707
|
-
{ :rows => default_text_area_height,
|
1708
|
-
:cols => default_text_area_width }
|
1709
|
-
elsif type == :numeric || column.nil? || !column.respond_to?(:limit) || column.limit.nil?
|
1710
|
-
{ :maxlength => validation_max_limit,
|
1711
|
-
:size => default_text_field_size }
|
1712
|
-
else
|
1713
|
-
{ :maxlength => validation_max_limit || column.limit,
|
1714
|
-
:size => default_text_field_size }
|
1715
|
-
end
|
1716
|
-
end
|
1717
|
-
|
1718
|
-
# Generate the html id for the li tag.
|
1719
|
-
# It takes into account options[:index] and @auto_index to generate li
|
1720
|
-
# elements with appropriate index scope. It also sanitizes the object
|
1721
|
-
# and method names.
|
1722
|
-
#
|
1723
|
-
def generate_html_id(method_name, value='input') #:nodoc:
|
1724
|
-
index = if options.has_key?(:index)
|
1725
|
-
options[:index]
|
1726
|
-
elsif defined?(@auto_index)
|
1727
|
-
@auto_index
|
1728
|
-
else
|
1729
|
-
""
|
1730
|
-
end
|
1731
|
-
sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
|
1732
|
-
|
1733
|
-
[@@custom_namespace, sanitized_object_name, index, sanitized_method_name, value].reject{|x|x.blank?}.join('_')
|
1734
|
-
end
|
1735
|
-
|
1736
|
-
# Gets the nested_child_index value from the parent builder. In Rails 2.3
|
1737
|
-
# it always returns a fixnum. In next versions it returns a hash with each
|
1738
|
-
# association that the parent builds.
|
1739
|
-
#
|
1740
|
-
def parent_child_index(parent) #:nodoc:
|
1741
|
-
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
1742
|
-
|
1743
|
-
if duck.is_a?(Hash)
|
1744
|
-
child = parent[:for]
|
1745
|
-
child = child.first if child.respond_to?(:first)
|
1746
|
-
duck[child].to_i + 1
|
1747
|
-
else
|
1748
|
-
duck.to_i + 1
|
1749
|
-
end
|
1750
|
-
end
|
1751
|
-
|
1752
|
-
def sanitized_object_name #:nodoc:
|
1753
|
-
@sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
1754
|
-
end
|
1755
|
-
|
1756
|
-
def humanized_attribute_name(method) #:nodoc:
|
1757
|
-
if @object && @object.class.respond_to?(:human_attribute_name)
|
1758
|
-
humanized_name = @object.class.human_attribute_name(method.to_s)
|
1759
|
-
if humanized_name == method.to_s.send(:humanize)
|
1760
|
-
method.to_s.send(label_str_method)
|
1761
|
-
else
|
1762
|
-
humanized_name
|
1763
|
-
end
|
1764
|
-
else
|
1765
|
-
method.to_s.send(label_str_method)
|
1766
|
-
end
|
1767
|
-
end
|
1768
|
-
|
1769
|
-
# Internal generic method for looking up localized values within Formtastic
|
1770
|
-
# using I18n, if no explicit value is set and I18n-lookups are enabled.
|
1771
|
-
#
|
1772
|
-
# Enabled/Disable this by setting:
|
1773
|
-
#
|
1774
|
-
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
|
1775
|
-
#
|
1776
|
-
# Lookup priority:
|
1777
|
-
#
|
1778
|
-
# 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
|
1779
|
-
# 'formtastic.%{type}.%{model}.%{attribute}'
|
1780
|
-
# 'formtastic.%{type}.%{attribute}'
|
1781
|
-
#
|
1782
|
-
# Example:
|
1783
|
-
#
|
1784
|
-
# 'formtastic.labels.post.edit.title'
|
1785
|
-
# 'formtastic.labels.post.title'
|
1786
|
-
# 'formtastic.labels.title'
|
1787
|
-
#
|
1788
|
-
# NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
|
1789
|
-
#
|
1790
|
-
def localized_string(key, value, type, options = {}) #:nodoc:
|
1791
|
-
key = value if value.is_a?(::Symbol)
|
1792
|
-
|
1793
|
-
if value.is_a?(::String)
|
1794
|
-
escape_html_entities(value)
|
1795
|
-
else
|
1796
|
-
use_i18n = value.nil? ? i18n_lookups_by_default : (value != false)
|
1797
|
-
|
1798
|
-
if use_i18n
|
1799
|
-
model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
|
1800
|
-
action_name = template.params[:action].to_s rescue ''
|
1801
|
-
attribute_name = key.to_s
|
1802
|
-
|
1803
|
-
defaults = ::Formtastic::I18n::SCOPES.reject do |i18n_scope|
|
1804
|
-
nested_model_name.nil? && i18n_scope.match(/nested_model/)
|
1805
|
-
end.collect do |i18n_scope|
|
1806
|
-
i18n_path = i18n_scope.dup
|
1807
|
-
i18n_path.gsub!('%{action}', action_name)
|
1808
|
-
i18n_path.gsub!('%{model}', model_name)
|
1809
|
-
i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
|
1810
|
-
i18n_path.gsub!('%{attribute}', attribute_name)
|
1811
|
-
i18n_path.gsub!('..', '.')
|
1812
|
-
i18n_path.to_sym
|
1813
|
-
end
|
1814
|
-
defaults << ''
|
1815
|
-
|
1816
|
-
defaults.uniq!
|
1817
|
-
|
1818
|
-
default_key = defaults.shift
|
1819
|
-
i18n_value = ::Formtastic::I18n.t(default_key,
|
1820
|
-
options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
|
1821
|
-
if i18n_value.blank? && type == :label
|
1822
|
-
# This is effectively what Rails label helper does for i18n lookup
|
1823
|
-
options[:scope] = [:helpers, type]
|
1824
|
-
options[:default] = defaults
|
1825
|
-
i18n_value = ::I18n.t(default_key, options)
|
1826
|
-
end
|
1827
|
-
i18n_value = escape_html_entities(i18n_value) if i18n_value.is_a?(::String)
|
1828
|
-
i18n_value.blank? ? nil : i18n_value
|
1829
|
-
end
|
1830
|
-
end
|
1831
|
-
end
|
1832
|
-
|
1833
|
-
def model_name
|
1834
|
-
@object.present? ? @object.class.name : @object_name.to_s.classify
|
1835
|
-
end
|
1836
|
-
|
1837
|
-
def normalize_model_name(name)
|
1838
|
-
if name =~ /(.+)\[(.+)\]/
|
1839
|
-
[$1, $2]
|
1840
|
-
else
|
1841
|
-
[name]
|
1842
|
-
end
|
1843
|
-
end
|
1844
|
-
|
1845
|
-
def send_or_call(duck, object)
|
1846
|
-
if duck.is_a?(Proc)
|
1847
|
-
duck.call(object)
|
1848
|
-
else
|
1849
|
-
object.send(duck)
|
1850
|
-
end
|
1851
|
-
end
|
1852
|
-
|
1853
|
-
def set_include_blank(options)
|
1854
|
-
unless options.key?(:include_blank) || options.key?(:prompt)
|
1855
|
-
options[:include_blank] = include_blank_for_select_by_default
|
1856
|
-
end
|
1857
|
-
options
|
1858
|
-
end
|
1859
|
-
|
1860
|
-
def escape_html_entities(string) #:nodoc:
|
1861
|
-
if escape_html_entities_in_hints_and_labels
|
1862
|
-
# Acceppt html_safe flag as indicator to skip escaping
|
1863
|
-
string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true
|
1864
|
-
end
|
1865
|
-
string
|
1866
|
-
end
|
1867
|
-
|
2
|
+
require 'formtastic/railtie' if defined?(::Rails)
|
3
|
+
require 'formtastic/engine' if defined?(::Rails)
|
4
|
+
|
5
|
+
module Formtastic
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
autoload :FormBuilder
|
9
|
+
autoload :SemanticFormBuilder
|
10
|
+
autoload :Helpers
|
11
|
+
autoload :HtmlAttributes
|
12
|
+
autoload :I18n
|
13
|
+
autoload :Inputs
|
14
|
+
autoload :LocalizedString
|
15
|
+
autoload :Util
|
16
|
+
|
17
|
+
# @private
|
18
|
+
class UnknownInputError < NameError
|
1868
19
|
end
|
1869
|
-
|
1870
|
-
#
|
1871
|
-
|
1872
|
-
# * semantic_form_for(@post)
|
1873
|
-
# * semantic_fields_for(@post)
|
1874
|
-
# * semantic_form_remote_for(@post)
|
1875
|
-
# * semantic_remote_form_for(@post)
|
1876
|
-
#
|
1877
|
-
# Each of which are the equivalent of:
|
1878
|
-
#
|
1879
|
-
# * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
|
1880
|
-
# * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
|
1881
|
-
# * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
|
1882
|
-
# * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
|
1883
|
-
#
|
1884
|
-
# Example Usage:
|
1885
|
-
#
|
1886
|
-
# <% semantic_form_for @post do |f| %>
|
1887
|
-
# <%= f.input :title %>
|
1888
|
-
# <%= f.input :body %>
|
1889
|
-
# <% end %>
|
1890
|
-
#
|
1891
|
-
# The above examples use a resource-oriented style of form_for() helper where only the @post
|
1892
|
-
# object is given as an argument, but the generic style is also supported, as are forms with
|
1893
|
-
# inline objects (Post.new) rather than objects with instance variables (@post):
|
1894
|
-
#
|
1895
|
-
# <% semantic_form_for :post, @post, :url => posts_path do |f| %>
|
1896
|
-
# ...
|
1897
|
-
# <% end %>
|
1898
|
-
#
|
1899
|
-
# <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
|
1900
|
-
# ...
|
1901
|
-
# <% end %>
|
1902
|
-
module SemanticFormHelper
|
1903
|
-
@@builder = ::Formtastic::SemanticFormBuilder
|
1904
|
-
@@default_form_class = 'formtastic'
|
1905
|
-
mattr_accessor :builder, :default_form_class
|
1906
|
-
|
1907
|
-
# Override the default ActiveRecordHelper behaviour of wrapping the input.
|
1908
|
-
# This gets taken care of semantically by adding an error class to the LI tag
|
1909
|
-
# containing the input.
|
1910
|
-
#
|
1911
|
-
FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
|
1912
|
-
html_tag
|
1913
|
-
end
|
1914
|
-
|
1915
|
-
def with_custom_field_error_proc(&block)
|
1916
|
-
default_field_error_proc = ::ActionView::Base.field_error_proc
|
1917
|
-
::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
|
1918
|
-
yield
|
1919
|
-
ensure
|
1920
|
-
::ActionView::Base.field_error_proc = default_field_error_proc
|
1921
|
-
end
|
1922
|
-
|
1923
|
-
def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
|
1924
|
-
options = args.extract_options!
|
1925
|
-
if respond_to? :remote_form_for
|
1926
|
-
semantic_remote_form_for_real(record_or_name_or_array, *(args << options), &proc)
|
1927
|
-
else
|
1928
|
-
options[:remote] = true
|
1929
|
-
semantic_form_for(record_or_name_or_array, *(args << options), &proc)
|
1930
|
-
end
|
1931
|
-
end
|
1932
|
-
|
1933
|
-
[:form_for, :fields_for, :remote_form_for].each do |meth|
|
1934
|
-
module_eval <<-END_SRC, __FILE__, __LINE__ + 1
|
1935
|
-
def semantic_#{meth}(record_or_name_or_array, *args, &proc)
|
1936
|
-
options = args.extract_options!
|
1937
|
-
options[:builder] ||= @@builder
|
1938
|
-
options[:html] ||= {}
|
1939
|
-
@@builder.custom_namespace = options[:namespace].to_s
|
1940
|
-
|
1941
|
-
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
1942
|
-
|
1943
|
-
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|
1944
|
-
class_names << @@default_form_class
|
1945
|
-
class_names << case record_or_name_or_array
|
1946
|
-
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
1947
|
-
when Array then options[:as] || singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
|
1948
|
-
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
1949
|
-
end
|
1950
|
-
options[:html][:class] = class_names.join(" ")
|
1951
|
-
|
1952
|
-
with_custom_field_error_proc do
|
1953
|
-
#{meth}(record_or_name_or_array, *(args << options), &proc)
|
1954
|
-
end
|
1955
|
-
end
|
1956
|
-
END_SRC
|
1957
|
-
end
|
1958
|
-
alias :semantic_remote_form_for_real :semantic_remote_form_for
|
1959
|
-
alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
|
1960
|
-
alias :semantic_form_remote_for :semantic_remote_form_for
|
1961
|
-
|
20
|
+
|
21
|
+
# @private
|
22
|
+
class PolymorphicInputWithoutCollectionError < ArgumentError
|
1962
23
|
end
|
24
|
+
|
1963
25
|
end
|
1964
|
-
|