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.
Files changed (134) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +1 -0
  4. data/CHANGELOG +279 -0
  5. data/Gemfile +3 -0
  6. data/README.textile +155 -172
  7. data/RELEASE_PROCESS +7 -0
  8. data/Rakefile +52 -0
  9. data/app/assets/stylesheets/formtastic.css +275 -0
  10. data/app/assets/stylesheets/formtastic_ie6.css +27 -0
  11. data/app/assets/stylesheets/formtastic_ie7.css +17 -0
  12. data/formtastic.gemspec +51 -0
  13. data/lib/formtastic.rb +21 -1960
  14. data/lib/formtastic/engine.rb +7 -0
  15. data/lib/formtastic/form_builder.rb +83 -0
  16. data/lib/formtastic/helpers.rb +16 -0
  17. data/lib/formtastic/helpers/buttons_helper.rb +277 -0
  18. data/lib/formtastic/helpers/errors_helper.rb +113 -0
  19. data/lib/formtastic/helpers/fieldset_wrapper.rb +75 -0
  20. data/lib/formtastic/helpers/file_column_detection.rb +16 -0
  21. data/lib/formtastic/helpers/form_helper.rb +198 -0
  22. data/lib/formtastic/helpers/input_helper.rb +366 -0
  23. data/lib/formtastic/helpers/inputs_helper.rb +392 -0
  24. data/lib/formtastic/helpers/reflection.rb +33 -0
  25. data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
  26. data/lib/formtastic/html_attributes.rb +21 -0
  27. data/lib/formtastic/i18n.rb +1 -0
  28. data/lib/formtastic/inputs.rb +31 -0
  29. data/lib/formtastic/inputs/base.rb +61 -0
  30. data/lib/formtastic/inputs/base/associations.rb +31 -0
  31. data/lib/formtastic/inputs/base/choices.rb +103 -0
  32. data/lib/formtastic/inputs/base/collections.rb +94 -0
  33. data/lib/formtastic/inputs/base/database.rb +17 -0
  34. data/lib/formtastic/inputs/base/errors.rb +58 -0
  35. data/lib/formtastic/inputs/base/fileish.rb +23 -0
  36. data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
  37. data/lib/formtastic/inputs/base/hints.rb +31 -0
  38. data/lib/formtastic/inputs/base/html.rb +52 -0
  39. data/lib/formtastic/inputs/base/labelling.rb +55 -0
  40. data/lib/formtastic/inputs/base/naming.rb +42 -0
  41. data/lib/formtastic/inputs/base/options.rb +18 -0
  42. data/lib/formtastic/inputs/base/stringish.rb +35 -0
  43. data/lib/formtastic/inputs/base/timeish.rb +128 -0
  44. data/lib/formtastic/inputs/base/validations.rb +166 -0
  45. data/lib/formtastic/inputs/base/wrapping.rb +40 -0
  46. data/lib/formtastic/inputs/boolean_input.rb +96 -0
  47. data/lib/formtastic/inputs/check_boxes_input.rb +179 -0
  48. data/lib/formtastic/inputs/country_input.rb +66 -0
  49. data/lib/formtastic/inputs/date_input.rb +14 -0
  50. data/lib/formtastic/inputs/datetime_input.rb +9 -0
  51. data/lib/formtastic/inputs/email_input.rb +40 -0
  52. data/lib/formtastic/inputs/file_input.rb +42 -0
  53. data/lib/formtastic/inputs/hidden_input.rb +66 -0
  54. data/lib/formtastic/inputs/number_input.rb +118 -0
  55. data/lib/formtastic/inputs/numeric_input.rb +21 -0
  56. data/lib/formtastic/inputs/password_input.rb +40 -0
  57. data/lib/formtastic/inputs/phone_input.rb +41 -0
  58. data/lib/formtastic/inputs/radio_input.rb +157 -0
  59. data/lib/formtastic/inputs/range_input.rb +119 -0
  60. data/lib/formtastic/inputs/search_input.rb +40 -0
  61. data/lib/formtastic/inputs/select_input.rb +210 -0
  62. data/lib/formtastic/inputs/string_input.rb +34 -0
  63. data/lib/formtastic/inputs/text_input.rb +47 -0
  64. data/lib/formtastic/inputs/time_input.rb +14 -0
  65. data/lib/formtastic/inputs/time_zone_input.rb +48 -0
  66. data/lib/formtastic/inputs/url_input.rb +40 -0
  67. data/lib/formtastic/localized_string.rb +105 -0
  68. data/lib/formtastic/railtie.rb +5 -7
  69. data/lib/formtastic/semantic_form_builder.rb +11 -0
  70. data/lib/formtastic/util.rb +6 -19
  71. data/lib/formtastic/version.rb +3 -0
  72. data/lib/generators/formtastic/install/install_generator.rb +28 -6
  73. data/lib/generators/templates/_form.html.erb +10 -4
  74. data/lib/generators/templates/_form.html.haml +8 -4
  75. data/lib/generators/templates/formtastic.rb +25 -32
  76. data/lib/locale/en.yml +1 -0
  77. data/lib/tasks/verify_rcov.rb +44 -0
  78. data/sample/basic_inputs.html +182 -0
  79. data/sample/config.ru +69 -0
  80. data/sample/index.html +14 -0
  81. data/spec/builder/custom_builder_spec.rb +109 -0
  82. data/spec/builder/error_proc_spec.rb +27 -0
  83. data/spec/builder/errors_spec.rb +193 -0
  84. data/spec/builder/semantic_fields_for_spec.rb +88 -0
  85. data/spec/helpers/buttons_helper_spec.rb +150 -0
  86. data/spec/helpers/commit_button_helper_spec.rb +470 -0
  87. data/spec/helpers/form_helper_spec.rb +135 -0
  88. data/spec/helpers/input_helper_spec.rb +837 -0
  89. data/spec/helpers/inputs_helper_spec.rb +562 -0
  90. data/spec/helpers/semantic_errors_helper_spec.rb +112 -0
  91. data/spec/i18n_spec.rb +199 -0
  92. data/spec/inputs/boolean_input_spec.rb +184 -0
  93. data/spec/inputs/check_boxes_input_spec.rb +375 -0
  94. data/spec/inputs/country_input_spec.rb +133 -0
  95. data/spec/inputs/custom_input_spec.rb +52 -0
  96. data/spec/inputs/date_input_spec.rb +110 -0
  97. data/spec/inputs/datetime_input_spec.rb +115 -0
  98. data/spec/inputs/email_input_spec.rb +55 -0
  99. data/spec/inputs/file_input_spec.rb +59 -0
  100. data/spec/inputs/hidden_input_spec.rb +120 -0
  101. data/spec/inputs/include_blank_spec.rb +70 -0
  102. data/spec/inputs/label_spec.rb +104 -0
  103. data/spec/inputs/number_input_spec.rb +487 -0
  104. data/spec/inputs/numeric_input_spec.rb +41 -0
  105. data/spec/inputs/password_input_spec.rb +69 -0
  106. data/spec/inputs/phone_input_spec.rb +55 -0
  107. data/spec/inputs/placeholder_spec.rb +71 -0
  108. data/spec/inputs/radio_input_spec.rb +234 -0
  109. data/spec/inputs/range_input_spec.rb +477 -0
  110. data/spec/inputs/search_input_spec.rb +55 -0
  111. data/spec/inputs/select_input_spec.rb +545 -0
  112. data/spec/inputs/string_input_spec.rb +163 -0
  113. data/spec/inputs/text_input_spec.rb +158 -0
  114. data/spec/inputs/time_input_spec.rb +155 -0
  115. data/spec/inputs/time_zone_input_spec.rb +87 -0
  116. data/spec/inputs/url_input_spec.rb +55 -0
  117. data/spec/spec.opts +2 -0
  118. data/spec/spec_helper.rb +361 -0
  119. data/spec/support/custom_macros.rb +656 -0
  120. data/spec/support/deferred_garbage_collection.rb +21 -0
  121. data/spec/support/deprecation.rb +6 -0
  122. data/spec/support/test_environment.rb +30 -0
  123. metadata +306 -88
  124. data/generators/form/USAGE +0 -16
  125. data/generators/form/form_generator.rb +0 -111
  126. data/generators/formtastic/formtastic_generator.rb +0 -26
  127. data/init.rb +0 -5
  128. data/lib/formtastic/layout_helper.rb +0 -12
  129. data/lib/generators/formtastic/form/form_generator.rb +0 -84
  130. data/lib/generators/templates/formtastic.css +0 -145
  131. data/lib/generators/templates/formtastic_changes.css +0 -14
  132. data/lib/generators/templates/rails2/_form.html.erb +0 -5
  133. data/lib/generators/templates/rails2/_form.html.haml +0 -4
  134. 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
+ }
@@ -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 File.join(File.dirname(__FILE__), *%w[formtastic i18n])
3
- require File.join(File.dirname(__FILE__), *%w[formtastic util])
4
- require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(::Rails::Railtie)
5
-
6
- module Formtastic #:nodoc:
7
-
8
- class SemanticFormBuilder < ActionView::Helpers::FormBuilder
9
-
10
- configurables = [
11
- :default_text_field_size, :default_text_area_height, :default_text_area_width, :all_fields_required_by_default, :include_blank_for_select_by_default,
12
- :required_string, :optional_string, :inline_errors, :label_str_method, :collection_value_methods, :collection_label_methods, :file_metadata_suffixes,
13
- :inline_order, :custom_inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels,
14
- :default_commit_button_accesskey, :default_inline_error_class, :default_hint_class, :default_error_list_class
15
- ]
16
-
17
- if respond_to?(:class_attribute)
18
- class_attribute *configurables
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
- # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
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
-