lperichon-jintastic 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.document +5 -0
  2. data/.gitignore +6 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +95 -0
  5. data/Rakefile +58 -0
  6. data/VERSION.yml +4 -0
  7. data/assets/app/views/jintastic/_in_place_editor.html.erb +16 -0
  8. data/assets/public/javascripts/jintastic.js +36 -0
  9. data/init.rb +2 -0
  10. data/jintastic.gemspec +47 -0
  11. data/lib/jintastic.rb +70 -0
  12. data/lperichon-jintastic.gemspec +75 -0
  13. data/test/jintastic_test.rb +7 -0
  14. data/test/test_helper.rb +10 -0
  15. data/vendor/plugins/formtastic/MIT-LICENSE +20 -0
  16. data/vendor/plugins/formtastic/README.textile +359 -0
  17. data/vendor/plugins/formtastic/Rakefile +58 -0
  18. data/vendor/plugins/formtastic/VERSION.yml +4 -0
  19. data/vendor/plugins/formtastic/formtastic.gemspec +31 -0
  20. data/vendor/plugins/formtastic/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +21 -0
  21. data/vendor/plugins/formtastic/generators/formtastic_stylesheets/templates/formtastic.css +120 -0
  22. data/vendor/plugins/formtastic/generators/formtastic_stylesheets/templates/formtastic_changes.css +10 -0
  23. data/vendor/plugins/formtastic/install.rb +1 -0
  24. data/vendor/plugins/formtastic/lib/formtastic.rb +1026 -0
  25. data/vendor/plugins/formtastic/lib/justin_french/formtastic.rb +10 -0
  26. data/vendor/plugins/formtastic/lib/locale/en.yml +8 -0
  27. data/vendor/plugins/formtastic/rails/init.rb +3 -0
  28. data/vendor/plugins/formtastic/spec/formtastic_spec.rb +2407 -0
  29. data/vendor/plugins/formtastic/spec/test_helper.rb +14 -0
  30. data/vendor/plugins/formtastic/tasks/formtastic_tasks.rake +1 -0
  31. data/vendor/plugins/formtastic/uninstall.rb +1 -0
  32. metadata +96 -0
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 4
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{formtastic}
5
+ s.version = "0.1.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Justin French"]
9
+ s.autorequire = %q{formtastic}
10
+ s.date = %q{2009-04-10}
11
+ s.description = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
12
+ s.email = %q{justin@indent.com.au}
13
+ s.extra_rdoc_files = ["README.textile"]
14
+ s.files = ["MIT-LICENSE", "README.textile", "Rakefile", "rails/init.rb", "lib/formtastic.rb", "lib/justin_french", "lib/justin_french/formtastic.rb", "lib/locale", "lib/locale/en.yml", "generators/formtastic_stylesheets", "generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb", "generators/formtastic_stylesheets/templates", "generators/formtastic_stylesheets/templates/formtastic.css", "generators/formtastic_stylesheets/templates/formtastic_changes.css", "spec/formtastic_spec.rb", "spec/test_helper.rb"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://github.com/justinfrench/formtastic/tree/master}
17
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ class FormtasticStylesheetsGenerator < Rails::Generator::Base
2
+
3
+ def initialize(*runtime_args)
4
+ super
5
+ end
6
+
7
+ def manifest
8
+ record do |m|
9
+ m.directory File.join('public', 'stylesheets')
10
+ m.template 'formtastic.css', File.join('public', 'stylesheets', 'formtastic.css')
11
+ m.template 'formtastic_changes.css', File.join('public', 'stylesheets', 'formtastic_changes.css')
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def banner
18
+ %{Usage: #{$0} #{spec.name}\nCopies formtastic.css and formtastic_changes.css to public/}
19
+ end
20
+
21
+ end
@@ -0,0 +1,120 @@
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 Justin French
9
+
10
+ --------------------------------------------------------------------------------------------------*/
11
+
12
+
13
+ /* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just form.formtastic
14
+ --------------------------------------------------------------------------------------------------*/
15
+ form.formtastic, form.formtastic ul, form.formtastic ol, form.formtastic li, form.formtastic fieldset, form.formtastic legend, form.formtastic input, form.formtastic textarea, form.formtastic select, form.formtastic p { margin:0; padding:0; }
16
+ form.formtastic fieldset { border:0; }
17
+ form.formtastic em, form.formtastic strong { font-style:normal; font-weight:normal; }
18
+ form.formtastic ol, form.formtastic ul { list-style:none; }
19
+ form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; }
20
+ form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; }
21
+ form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; }
22
+ form.formtastic legend { color:#000; }
23
+
24
+
25
+ /* FIELDSETS & LISTS
26
+ --------------------------------------------------------------------------------------------------*/
27
+ form.formtastic fieldset { }
28
+ form.formtastic fieldset.inputs { }
29
+ form.formtastic fieldset.buttons { padding-left:25%; }
30
+ form.formtastic fieldset ol { }
31
+
32
+ /* clearfixing the fieldsets */
33
+ form.formtastic fieldset { display: inline-block; }
34
+ form.formtastic fieldset:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
35
+ html[xmlns] form.formtastic fieldset { display: block; }
36
+ * html form.formtastic fieldset { height: 1%; }
37
+
38
+
39
+ /* INPUT LIs
40
+ --------------------------------------------------------------------------------------------------*/
41
+ form.formtastic fieldset ol li { margin-bottom:1.5em; }
42
+
43
+ /* clearfixing the li's */
44
+ form.formtastic fieldset ol li { display: inline-block; }
45
+ form.formtastic fieldset ol li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
46
+ html[xmlns] form.formtastic fieldset ol li { display: block; }
47
+ * html form.formtastic fieldset ol li { height: 1%; }
48
+
49
+ form.formtastic fieldset ol li.required { }
50
+ form.formtastic fieldset ol li.optional { }
51
+ form.formtastic fieldset ol li.error { }
52
+
53
+
54
+ /* LABELS
55
+ --------------------------------------------------------------------------------------------------*/
56
+ form.formtastic fieldset ol li label { display:block; width:25%; float:left; padding-top:.2em; }
57
+ form.formtastic fieldset ol li li label { line-height:100%; padding-top:0; }
58
+ form.formtastic fieldset ol li li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;}
59
+
60
+
61
+ /* NESTED FIELDSETS AND LEGENDS (radio and date/time inputs use nested fieldsets)
62
+ --------------------------------------------------------------------------------------------------*/
63
+ form.formtastic fieldset ol li fieldset { position:relative; }
64
+ form.formtastic fieldset ol li fieldset legend { position:absolute; width:25%; padding-top:0.1em; }
65
+ form.formtastic fieldset ol li fieldset legend span { position:absolute; }
66
+ form.formtastic fieldset ol li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
67
+ form.formtastic fieldset ol li fieldset ol li { padding:0; border:0; }
68
+
69
+
70
+ /* INLINE HINTS
71
+ --------------------------------------------------------------------------------------------------*/
72
+ form.formtastic fieldset ol li p.inline-hints { color:#666; margin:0.5em 0 0 25%; }
73
+
74
+
75
+ /* INLINE ERRORS
76
+ --------------------------------------------------------------------------------------------------*/
77
+ form.formtastic fieldset ol li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; }
78
+ form.formtastic fieldset ol li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; }
79
+ form.formtastic fieldset ol li ul.errors li { padding:0; border:none; display:list-item; }
80
+
81
+
82
+ /* STRING & NUMERIC OVERRIDES
83
+ --------------------------------------------------------------------------------------------------*/
84
+ form.formtastic fieldset ol li.string input { width:74%; }
85
+ form.formtastic fieldset ol li.numeric input { width:74%; }
86
+
87
+
88
+ /* TEXTAREA OVERRIDES
89
+ --------------------------------------------------------------------------------------------------*/
90
+ form.formtastic fieldset ol li.text textarea { width:74%; }
91
+
92
+
93
+ /* CHECKBOX OVERRIDES
94
+ --------------------------------------------------------------------------------------------------*/
95
+ form.formtastic fieldset ol li.boolean label { padding-left:25%; width:auto; }
96
+ form.formtastic fieldset ol li.boolean label input { margin:0 0.5em 0 0.2em; }
97
+
98
+
99
+ /* RADIO OVERRIDES
100
+ --------------------------------------------------------------------------------------------------*/
101
+ form.formtastic fieldset ol li.radio { }
102
+ form.formtastic fieldset ol li.radio fieldset ol { margin-bottom:-0.6em; }
103
+ form.formtastic fieldset ol li.radio fieldset ol li { margin:0.1em 0 0.5em 0; }
104
+ form.formtastic fieldset ol li.radio fieldset ol li label { float:none; width:100%; }
105
+ form.formtastic fieldset ol li.radio fieldset ol li label input { margin-right:0.2em; }
106
+
107
+
108
+ /* DATE & TIME OVERRIDES
109
+ --------------------------------------------------------------------------------------------------*/
110
+ form.formtastic fieldset ol li.date fieldset ol li,
111
+ form.formtastic fieldset ol li.time fieldset ol li,
112
+ form.formtastic fieldset ol li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; }
113
+
114
+ form.formtastic fieldset ol li.date fieldset ol li label,
115
+ form.formtastic fieldset ol li.time fieldset ol li label,
116
+ form.formtastic fieldset ol li.datetime fieldset ol li label { display:none; }
117
+
118
+ form.formtastic fieldset ol li.date fieldset ol li label input,
119
+ form.formtastic fieldset ol li.time fieldset ol li label input,
120
+ form.formtastic fieldset ol li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; }
@@ -0,0 +1,10 @@
1
+ /* -------------------------------------------------------------------------------------------------
2
+
3
+ Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs.
4
+ This will allow you to update formtastic.css with new releases without clobbering your own changes.
5
+
6
+ For example, to make the inline hint paragraphs a little darker in color than the standard #666:
7
+
8
+ form.formtastic fieldset ol li p.inline-hints { color:#333; }
9
+
10
+ --------------------------------------------------------------------------------------------------*/
@@ -0,0 +1 @@
1
+ puts IO.read(File.join(File.dirname(__FILE__), 'README.textile'))
@@ -0,0 +1,1026 @@
1
+ # Override the default ActiveRecordHelper behaviour of wrapping the input.
2
+ # This gets taken care of semantically by adding an error class to the LI tag
3
+ # containing the input.
4
+ ActionView::Base.field_error_proc = proc do |html_tag, instance_tag|
5
+ html_tag
6
+ end
7
+
8
+ module Formtastic #:nodoc:
9
+
10
+ class SemanticFormBuilder < ActionView::Helpers::FormBuilder
11
+
12
+ @@default_text_field_size = 50
13
+ @@all_fields_required_by_default = true
14
+ @@required_string = proc { %{<abbr title="#{I18n.t 'formtastic.required', :default => 'required'}">*</abbr>} }
15
+ @@optional_string = ''
16
+ @@inline_errors = :sentence
17
+ @@label_str_method = :humanize
18
+ @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
19
+ @@inline_order = [ :input, :hints, :errors ]
20
+ @@file_methods = [ :file?, :public_filename ]
21
+
22
+ cattr_accessor :default_text_field_size, :all_fields_required_by_default, :required_string,
23
+ :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
24
+ :inline_order, :file_methods
25
+
26
+ # Keeps simple mappings in a hash
27
+ #
28
+ INPUT_MAPPINGS = {
29
+ :string => :text_field,
30
+ :password => :password_field,
31
+ :numeric => :text_field,
32
+ :text => :text_area,
33
+ :file => :file_field
34
+ }
35
+ STRING_MAPPINGS = [ :string, :password, :numeric ]
36
+
37
+ attr_accessor :template
38
+ attr_writer :nested_child_index
39
+
40
+ # Returns a suitable form input for the given +method+, using the database column information
41
+ # and other factors (like the method name) to figure out what you probably want.
42
+ #
43
+ # Options:
44
+ #
45
+ # * :as - override the input type (eg force a :string to render as a :password field)
46
+ # * :label - use something other than the method name as the label (or fieldset legend) text
47
+ # * :required - specify if the column is required (true) or not (false)
48
+ # * :hint - provide some text to hint or help the user provide the correct information for a field
49
+ # * :input_html - provide options that will be passed down to the generated input
50
+ #
51
+ # Input Types:
52
+ #
53
+ # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
54
+ # but there are a few special cases and some simplification (:integer, :float and :decimal
55
+ # columns all map to a single numeric_input, for example).
56
+ #
57
+ # * :select (a select menu for associations) - default to association names
58
+ # * :radio (a set of radio inputs for associations) - default to association names
59
+ # * :password (a password input) - default for :string column types with 'password' in the method name
60
+ # * :text (a textarea) - default for :text column types
61
+ # * :date (a date select) - default for :date column types
62
+ # * :datetime (a date and time select) - default for :datetime and :timestamp column types
63
+ # * :time (a time select) - default for :time column types
64
+ # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
65
+ # * :string (a text field) - default for :string column types
66
+ # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
67
+ #
68
+ # Example:
69
+ #
70
+ # <% semantic_form_for @employee do |form| %>
71
+ # <% form.inputs do -%>
72
+ # <%= form.input :name, :label => "Full Name"%>
73
+ # <%= form.input :manager_id, :as => :radio %>
74
+ # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
75
+ # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
76
+ # <% end %>
77
+ # <% end %>
78
+ #
79
+ def input(method, options = {})
80
+ options[:required] = method_required?(method, options[:required])
81
+ options[:as] ||= default_input_type(method)
82
+
83
+ options[:label] ||= if @object
84
+ @object.class.human_attribute_name(method.to_s)
85
+ else
86
+ method.to_s.send(@@label_str_method)
87
+ end
88
+
89
+ if [:boolean_select, :boolean_radio].include?(options[:as])
90
+ ::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
91
+ end
92
+
93
+ html_class = [ options[:as], (options[:required] ? :required : :optional) ].join(' ')
94
+ html_class << ' error' if @object && @object.errors.on(method.to_s)
95
+
96
+ html_id = generate_html_id(method)
97
+
98
+ list_item_content = @@inline_order.map do |type|
99
+ send(:"inline_#{type}_for", method, options)
100
+ end.compact.join("\n")
101
+
102
+ return template.content_tag(:li, list_item_content, { :id => html_id, :class => html_class })
103
+ end
104
+
105
+ # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
106
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
107
+ # or with a list of fields. These two examples are functionally equivalent:
108
+ #
109
+ # # With a block:
110
+ # <% semantic_form_for @post do |form| %>
111
+ # <% form.inputs do %>
112
+ # <%= form.input :title %>
113
+ # <%= form.input :body %>
114
+ # <% end %>
115
+ # <% end %>
116
+ #
117
+ # # With a list of fields:
118
+ # <% semantic_form_for @post do |form| %>
119
+ # <%= form.inputs :title, :body %>
120
+ # <% end %>
121
+ #
122
+ # # Output:
123
+ # <form ...>
124
+ # <fieldset class="inputs">
125
+ # <ol>
126
+ # <li class="string">...</li>
127
+ # <li class="text">...</li>
128
+ # </ol>
129
+ # </fieldset>
130
+ # </form>
131
+ #
132
+ # === Quick Forms
133
+ #
134
+ # When called without a block or a field list, an input is rendered for each column in the
135
+ # model's database table, just like Rails' scaffolding. You'll obviously want more control
136
+ # than this in a production application, but it's a great way to get started, then come back
137
+ # later to customise the form with a field list or a block of inputs. Example:
138
+ #
139
+ # <% semantic_form_for @post do |form| %>
140
+ # <%= form.inputs %>
141
+ # <% end %>
142
+ #
143
+ # === Options
144
+ #
145
+ # All options (with the exception of :name) are passed down to the fieldset as HTML
146
+ # attributes (id, class, style, etc). If provided, the :name option is passed into a
147
+ # legend tag inside the fieldset (otherwise a legend is not generated).
148
+ #
149
+ # # With a block:
150
+ # <% semantic_form_for @post do |form| %>
151
+ # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
152
+ # ...
153
+ # <% end %>
154
+ # <% end %>
155
+ #
156
+ # # With a list (the options must come after the field list):
157
+ # <% semantic_form_for @post do |form| %>
158
+ # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
159
+ # <% end %>
160
+ #
161
+ # === It's basically a fieldset!
162
+ #
163
+ # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
164
+ # use inputs:
165
+ #
166
+ # <% semantic_form_for @post do |f| %>
167
+ # <% f.inputs do %>
168
+ # <%= f.input :title %>
169
+ # <%= f.input :body %>
170
+ # <% end %>
171
+ # <% f.inputs :name => "Advanced", :id => "advanced" do %>
172
+ # <%= f.input :created_at %>
173
+ # <%= f.input :user_id, :label => "Author" %>
174
+ # <% end %>
175
+ # <% end %>
176
+ #
177
+ # # Output:
178
+ # <form ...>
179
+ # <fieldset class="inputs">
180
+ # <ol>
181
+ # <li class="string">...</li>
182
+ # <li class="text">...</li>
183
+ # </ol>
184
+ # </fieldset>
185
+ # <fieldset class="inputs" id="advanced">
186
+ # <legend><span>Advanced</span></legend>
187
+ # <ol>
188
+ # <li class="datetime">...</li>
189
+ # <li class="select">...</li>
190
+ # </ol>
191
+ # </fieldset>
192
+ # </form>
193
+ #
194
+ # === Nested attributes
195
+ #
196
+ # As in Rails, you can use semantic_fields_for to nest attributes:
197
+ #
198
+ # <% semantic_form_for @post do |form| %>
199
+ # <%= form.inputs :title, :body %>
200
+ #
201
+ # <% form.semantic_fields_for :author, @bob do |author_form| %>
202
+ # <% author_form.inputs do %>
203
+ # <%= author_form.input :first_name, :required => false %>
204
+ # <%= author_form.input :last_name %>
205
+ # <% end %>
206
+ # <% end %>
207
+ # <% end %>
208
+ #
209
+ # But this does not look formtastic! This is equivalent:
210
+ #
211
+ # <% semantic_form_for @post do |form| %>
212
+ # <%= form.inputs :title, :body %>
213
+ # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
214
+ # <%= author_form.input :first_name, :required => false %>
215
+ # <%= author_form.input :last_name %>
216
+ # <% end %>
217
+ # <% end %>
218
+ #
219
+ # And if you don't need to give options to your input call, you could do it
220
+ # in just one line:
221
+ #
222
+ # <% semantic_form_for @post do |form| %>
223
+ # <%= form.inputs :title, :body %>
224
+ # <%= form.inputs :first_name, :last_name, :for => @bob %>
225
+ # <% end %>
226
+ #
227
+ # Just remember that calling inputs generates a new fieldset to wrap your
228
+ # inputs. If you have two separate models, but, semantically, on the page
229
+ # they are part of the same fieldset, you should use semantic_fields_for
230
+ # instead (just as you would do with Rails' form builder).
231
+ #
232
+ def inputs(*args, &block)
233
+ html_options = args.extract_options!
234
+ html_options[:class] ||= "inputs"
235
+
236
+ if fields_for_object = html_options.delete(:for)
237
+ html_options.merge!(:parent_builder => self)
238
+ inputs_for_nested_attributes(fields_for_object, args << html_options,
239
+ html_options.delete(:for_options) || {}, &block)
240
+ elsif block_given?
241
+ field_set_and_list_wrapping(html_options, &block)
242
+ else
243
+ if @object && args.empty?
244
+ # Get all belongs_to association
245
+ args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
246
+
247
+ # Get content columns and remove timestamps columns from it
248
+ args += @object.class.content_columns.map(&:name)
249
+ args -= %w[created_at updated_at created_on updated_on]
250
+
251
+ args.compact!
252
+ end
253
+ contents = args.map { |method| input(method.to_sym) }
254
+
255
+ field_set_and_list_wrapping(html_options, contents)
256
+ end
257
+ end
258
+ alias :input_field_set :inputs
259
+
260
+ # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
261
+ # See inputs documentation for a full example. The fieldset's default class attriute
262
+ # is set to "buttons".
263
+ #
264
+ # See inputs for html attributes and special options.
265
+ def buttons(*args, &block)
266
+ html_options = args.extract_options!
267
+ html_options[:class] ||= "buttons"
268
+
269
+ if block_given?
270
+ field_set_and_list_wrapping(html_options, &block)
271
+ else
272
+ args = [:commit] if args.empty?
273
+ contents = args.map { |button_name| send(:"#{button_name}_button") }
274
+ field_set_and_list_wrapping(html_options, contents)
275
+ end
276
+ end
277
+ alias :button_field_set :buttons
278
+
279
+ # Creates a submit input tag with the value "Save [model name]" (for existing records) or
280
+ # "Create [model name]" (for new records) by default:
281
+ #
282
+ # <%= form.commit_button %> => <input name="commit" type="submit" value="Save Post" />
283
+ #
284
+ # The value of the button text can be overridden:
285
+ #
286
+ # <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
287
+ #
288
+ def commit_button(value=nil, options = {})
289
+ value ||= save_or_create_button_text
290
+ template.content_tag(:li, template.submit_tag(value), :class => "commit")
291
+ end
292
+
293
+ # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
294
+ # for nesting forms:
295
+ #
296
+ # # Example:
297
+ # <% semantic_form_for @post do |post| %>
298
+ # <% post.semantic_fields_for :author do |author| %>
299
+ # <% author.inputs :name %>
300
+ # <% end %>
301
+ # <% end %>
302
+ #
303
+ # # Output:
304
+ # <form ...>
305
+ # <fieldset class="inputs">
306
+ # <ol>
307
+ # <li class="string"><input type='text' name='post[author][name]' id='post_author_name' /></li>
308
+ # </ol>
309
+ # </fieldset>
310
+ # </form>
311
+ #
312
+ def semantic_fields_for(record_or_name_or_array, *args, &block)
313
+ opts = args.extract_options!
314
+ opts.merge!(:builder => Formtastic::SemanticFormBuilder)
315
+ args.push(opts)
316
+ fields_for(record_or_name_or_array, *args, &block)
317
+ end
318
+
319
+ protected
320
+
321
+ # Deals with :for option when it's supplied to inputs methods. Additional
322
+ # options to be passed down to :for should be supplied using :for_options
323
+ # key.
324
+ #
325
+ # It should raise an error if a block with arity zero is given.
326
+ #
327
+ def inputs_for_nested_attributes(fields_for_object, inputs, options, &block)
328
+ fields_for_block = if block_given?
329
+ raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
330
+ 'but the block does not accept any argument.' if block.arity <= 0
331
+
332
+ proc { |f| f.inputs(*inputs){ block.call(f) } }
333
+ else
334
+ proc { |f| f.inputs(*inputs) }
335
+ end
336
+
337
+ semantic_fields_for(*(Array(fields_for_object) << options), &fields_for_block)
338
+ end
339
+
340
+ # Remove any Formtastic-specific options before passing the down options.
341
+ #
342
+ def set_options(options)
343
+ options.except(:value_method, :label_method, :collection, :required, :label,
344
+ :as, :hint, :input_html, :label_html, :value_as_class)
345
+ end
346
+
347
+ # Create a default button text. If the form is working with a object, it
348
+ # defaults to "Create model" or "Save model" depending if we are working
349
+ # with a new_record or not.
350
+ #
351
+ # When not working with models, it defaults to "Submit object".
352
+ #
353
+ def save_or_create_button_text(prefix='Submit') #:nodoc:
354
+ if @object
355
+ prefix = @object.new_record? ? 'Create' : 'Save'
356
+ object_name = @object.class.human_name
357
+ else
358
+ object_name = @object_name.to_s.send(@@label_str_method)
359
+ end
360
+
361
+ I18n.t(prefix.downcase, :default => prefix, :scope => [:formtastic]) << ' ' << object_name
362
+ end
363
+
364
+ # Determins if the attribute (eg :title) should be considered required or not.
365
+ #
366
+ # * if the :required option was provided in the options hash, the true/false value will be
367
+ # returned immediately, allowing the view to override any guesswork that follows:
368
+ #
369
+ # * if the :required option isn't provided in the options hash, and the ValidationReflection
370
+ # plugin is installed (http://github.com/redinger/validation_reflection), true is returned
371
+ # if the validates_presence_of macro has been used in the class for this attribute, or false
372
+ # otherwise.
373
+ #
374
+ # * if the :required option isn't provided, and the plugin isn't available, the value of the
375
+ # configuration option @@all_fields_required_by_default is used.
376
+ #
377
+ def method_required?(attribute, required_option) #:nodoc:
378
+ return required_option unless required_option.nil?
379
+
380
+ if @object && @object.class.respond_to?(:reflect_on_all_validations)
381
+ attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
382
+
383
+ @object.class.reflect_on_all_validations.any? do |validation|
384
+ validation.macro == :validates_presence_of && validation.name == attribute_sym
385
+ end
386
+ else
387
+ @@all_fields_required_by_default
388
+ end
389
+ end
390
+
391
+ # A method that deals with most of inputs (:string, :password, :file,
392
+ # :textarea and :numeric). :select, :radio, :boolean and :datetime inputs
393
+ # are not handled by this method, since they need more detailed approach.
394
+ #
395
+ # If input_html is given as option, it's passed down to the input.
396
+ #
397
+ def input_simple(type, method, options)
398
+ html_options = options.delete(:input_html) || {}
399
+ html_options = default_string_options(method).merge(html_options) if STRING_MAPPINGS.include?(type)
400
+
401
+ input_label(method, options.delete(:label), options.slice(:required)) + send(INPUT_MAPPINGS[type], method, html_options)
402
+ end
403
+
404
+ # Outputs a label and a select box containing options from the parent
405
+ # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
406
+ # is has_many or has_and_belongs_to_many the select box will be set as multi-select
407
+ # and size = 5
408
+ #
409
+ # Example (belongs_to):
410
+ #
411
+ # f.input :author
412
+ #
413
+ # <label for="book_author_id">Author</label>
414
+ # <select id="book_author_id" name="book[author_id]">
415
+ # <option value="1">Justin French</option>
416
+ # <option value="2">Jane Doe</option>
417
+ # </select>
418
+ #
419
+ # Example (has_many):
420
+ #
421
+ # f.input :chapters
422
+ #
423
+ # <label for="book_chapter_ids">Chapters</label>
424
+ # <select id="book_chapter_ids" name="book[chapter_ids]">
425
+ # <option value="1">Chapter 1</option>
426
+ # <option value="2">Chapter 2</option>
427
+ # </select>
428
+ #
429
+ # Example (has_and_belongs_to_many):
430
+ #
431
+ # f.input :authors
432
+ #
433
+ # <label for="book_author_ids">Authors</label>
434
+ # <select id="book_author_ids" name="book[author_ids]">
435
+ # <option value="1">Justin French</option>
436
+ # <option value="2">Jane Doe</option>
437
+ # </select>
438
+ #
439
+ #
440
+ # You can customize the options available in the select by passing in a collection (Array) of
441
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
442
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
443
+ # it (VehicleOwner.find(:all) in the example above).
444
+ #
445
+ # Examples:
446
+ #
447
+ # f.input :author, :collection => @authors
448
+ # f.input :author, :collection => Author.find(:all)
449
+ # f.input :author, :collection => [@justin, @kate]
450
+ # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
451
+ #
452
+ # Note: This input looks for a label method in the parent association.
453
+ #
454
+ # You can customize the text label inside each option tag, by naming the correct method
455
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
456
+ # by passing in the :label_method option. By default the :label_method is whichever element of
457
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
458
+ #
459
+ # Examples:
460
+ #
461
+ # f.input :author, :label_method => :full_name
462
+ # f.input :author, :label_method => :display_name
463
+ # f.input :author, :label_method => :to_s
464
+ # f.input :author, :label_method => :label
465
+ #
466
+ # You can also customize the value inside each option tag, by passing in the :value_method option.
467
+ # Usage is the same as the :label_method option
468
+ #
469
+ # Examples:
470
+ #
471
+ # f.input :author, :value_method => :full_name
472
+ # f.input :author, :value_method => :display_name
473
+ # f.input :author, :value_method => :to_s
474
+ # f.input :author, :value_method => :value
475
+ #
476
+ # You can pass html_options to the select tag using :input_html => {}
477
+ #
478
+ # Examples:
479
+ #
480
+ # f.input :authors, :input_html => {:size => 20, :multiple => true}
481
+ #
482
+ def select_input(method, options)
483
+ collection = find_collection_for_column(method, options)
484
+ html_options = options.delete(:input_html) || {}
485
+
486
+ reflection = find_reflection(method)
487
+ if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
488
+ html_options[:multiple] ||= true
489
+ html_options[:size] ||= 5
490
+ end
491
+
492
+ input_name = generate_association_input_name(method)
493
+ input_label(input_name, options.delete(:label), options.slice(:required)) +
494
+ self.select(input_name, collection, set_options(options), html_options)
495
+ end
496
+ alias :boolean_select_input :select_input
497
+
498
+ # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
499
+ # items, one for each possible choice in the belongs_to association. Each li contains a
500
+ # label and a radio input.
501
+ #
502
+ # Example:
503
+ #
504
+ # f.input :author, :as => :radio
505
+ #
506
+ # Output:
507
+ #
508
+ # <fieldset>
509
+ # <legend><span>Author</span></legend>
510
+ # <ol>
511
+ # <li>
512
+ # <label for="book_author_id_1"><input id="book_author_id_1" name="book[author_id]" type="radio" value="1" /> Justin French</label>
513
+ # </li>
514
+ # <li>
515
+ # <label for="book_author_id_2"><input id="book_author_id_2" name="book[owner_id]" type="radio" value="2" /> Kate French</label>
516
+ # </li>
517
+ # </ol>
518
+ # </fieldset>
519
+ #
520
+ # You can customize the options available in the set by passing in a collection (Array) of
521
+ # ActiveRecord objects through the :collection option. If not provided, the choices are found
522
+ # by inferring the parent's class name from the method name and simply calling find(:all) on
523
+ # it (VehicleOwner.find(:all) in the example above).
524
+ #
525
+ # Examples:
526
+ #
527
+ # f.input :author, :as => :radio, :collection => @authors
528
+ # f.input :author, :as => :radio, :collection => Author.find(:all)
529
+ # f.input :author, :as => :radio, :collection => [@justin, @kate]
530
+ #
531
+ # You can also customize the text label inside each option tag, by naming the correct method
532
+ # (:full_name, :display_name, :account_number, etc) to call on each object in the collection
533
+ # by passing in the :label_method option. By default the :label_method is whichever element of
534
+ # Formtastic::SemanticFormBuilder.collection_label_methods is found first.
535
+ #
536
+ # Examples:
537
+ #
538
+ # f.input :author, :as => :radio, :label_method => :full_name
539
+ # f.input :author, :as => :radio, :label_method => :display_name
540
+ # f.input :author, :as => :radio, :label_method => :to_s
541
+ # f.input :author, :as => :radio, :label_method => :label
542
+ #
543
+ # Finally, you can set :value_as_class => true if you want that LI wrappers
544
+ # contains a class with the wrapped radio input value.
545
+ #
546
+ def radio_input(method, options)
547
+ collection = find_collection_for_column(method, options)
548
+ html_options = set_options(options).merge(options.delete(:input_html) || {})
549
+
550
+ input_name = generate_association_input_name(method)
551
+ value_as_class = options.delete(:value_as_class)
552
+
553
+ list_item_content = collection.map do |c|
554
+ label = c.is_a?(Array) ? c.first : c
555
+ value = c.is_a?(Array) ? c.last : c
556
+
557
+ li_content = template.content_tag(:label,
558
+ "#{self.radio_button(input_name, value, html_options)} #{label}",
559
+ :for => generate_html_id(input_name, value.to_s.downcase)
560
+ )
561
+
562
+ li_options = value_as_class ? { :class => value.to_s.downcase } : {}
563
+ template.content_tag(:li, li_content, li_options)
564
+ end
565
+
566
+ field_set_and_list_wrapping_for_method(method, options, list_item_content)
567
+ end
568
+ alias :boolean_radio_input :radio_input
569
+
570
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
571
+ # items (li), one for each fragment for the date (year, month, day). Each li contains a label
572
+ # (eg "Year") and a select box. See date_or_datetime_input for a more detailed output example.
573
+ #
574
+ # Some of Rails' options for select_date are supported, but not everything yet.
575
+ def date_input(method, options)
576
+ date_or_datetime_input(method, options.merge(:discard_hour => true))
577
+ end
578
+
579
+
580
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
581
+ # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
582
+ # contains a label (eg "Year") and a select box. See date_or_datetime_input for a more
583
+ # detailed output example.
584
+ #
585
+ # Some of Rails' options for select_date are supported, but not everything yet.
586
+ def datetime_input(method, options)
587
+ date_or_datetime_input(method, options)
588
+ end
589
+
590
+
591
+ # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
592
+ # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
593
+ # (eg "Hour") and a select box. See date_or_datetime_input for a more detailed output example.
594
+ #
595
+ # Some of Rails' options for select_time are supported, but not everything yet.
596
+ def time_input(method, options)
597
+ date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
598
+ end
599
+
600
+
601
+ # <fieldset>
602
+ # <legend>Created At</legend>
603
+ # <ol>
604
+ # <li>
605
+ # <label for="user_created_at_1i">Year</label>
606
+ # <select id="user_created_at_1i" name="user[created_at(1i)]">
607
+ # <option value="2003">2003</option>
608
+ # ...
609
+ # <option value="2013">2013</option>
610
+ # </select>
611
+ # </li>
612
+ # <li>
613
+ # <label for="user_created_at_2i">Month</label>
614
+ # <select id="user_created_at_2i" name="user[created_at(2i)]">
615
+ # <option value="1">January</option>
616
+ # ...
617
+ # <option value="12">December</option>
618
+ # </select>
619
+ # </li>
620
+ # <li>
621
+ # <label for="user_created_at_3i">Day</label>
622
+ # <select id="user_created_at_3i" name="user[created_at(3i)]">
623
+ # <option value="1">1</option>
624
+ # ...
625
+ # <option value="31">31</option>
626
+ # </select>
627
+ # </li>
628
+ # </ol>
629
+ # </fieldset>
630
+ #
631
+ # This is an absolute abomination, but so is the official Rails select_date().
632
+ #
633
+ def date_or_datetime_input(method, options)
634
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
635
+ inputs = options.delete(:order) || I18n.translate(:'date.order') || [:year, :month, :day]
636
+
637
+ time_inputs = [:hour, :minute]
638
+ time_inputs << [:second] if options[:include_seconds]
639
+
640
+ list_items_capture = ""
641
+
642
+ # Gets the datetime object. It can be a Fixnum, Date or Time, or nil.
643
+ datetime = @object ? @object.send(method) : nil
644
+ html_options = options.delete(:input_html) || {}
645
+
646
+ (inputs + time_inputs).each do |input|
647
+ html_id = generate_html_id(method, "#{position[input]}i")
648
+ field_name = "#{method}(#{position[input]}i)"
649
+
650
+ list_items_capture << if options["discard_#{input}".intern]
651
+ break if time_inputs.include?(input)
652
+
653
+ hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
654
+ template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => html_id)
655
+ else
656
+ opts = set_options(options).merge(:prefix => @object_name, :field_name => field_name)
657
+ item_label_text = I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
658
+
659
+ template.content_tag(:li,
660
+ template.content_tag(:label, item_label_text, :for => html_id) +
661
+ template.send("select_#{input}".intern, datetime, opts, html_options.merge(:id => html_id))
662
+ )
663
+ end
664
+ end
665
+
666
+ field_set_and_list_wrapping_for_method(method, options, list_items_capture)
667
+ end
668
+
669
+ # Outputs a label containing a checkbox and the label text. The label defaults
670
+ # to the column name (method name) and can be altered with the :label option.
671
+ #
672
+ # Different from other inputs, :required options has no effect here and
673
+ # :checked_value and :unchecked_value options are also available.
674
+ #
675
+ def boolean_input(method, options)
676
+ html_options = options.delete(:input_html) || {}
677
+
678
+ content = self.check_box(method, set_options(options).merge(html_options),
679
+ options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
680
+
681
+ # required does not make sense in check box
682
+ input_label(method, content + options.delete(:label), :skip_required => true)
683
+ end
684
+
685
+ # Generates an input for the given method using the type supplied with :as.
686
+ #
687
+ # If the input is included in INPUT_MAPPINGS, it uses input_simple
688
+ # implementation which maps most of the inputs. All others have specific
689
+ # code and then a proper handler should be called (like radio_input) for
690
+ # :radio types.
691
+ #
692
+ def inline_input_for(method, options)
693
+ input_type = options.delete(:as)
694
+
695
+ if INPUT_MAPPINGS.key?(input_type)
696
+ input_simple(input_type, method, options)
697
+ else
698
+ send("#{input_type}_input", method, options)
699
+ end
700
+ end
701
+
702
+ # Generates error messages for the given method. Errors can be shown as list
703
+ # or as sentence. If :none is set, no error is shown.
704
+ #
705
+ def inline_errors_for(method, options) #:nodoc:
706
+ return nil unless @object && [:sentence, :list].include?(@@inline_errors)
707
+
708
+ errors = @object.errors.on(method.to_s).to_a
709
+ send("error_#{@@inline_errors}", errors) unless errors.empty?
710
+ end
711
+
712
+ # Generates hints for the given method using the text supplied in :hint.
713
+ #
714
+ def inline_hints_for(method, options) #:nodoc:
715
+ options[:hint].blank? ? '' : template.content_tag(:p, options[:hint], :class => 'inline-hints')
716
+ end
717
+
718
+ # Creates an error sentence by calling to_sentence on the errors array.
719
+ #
720
+ def error_sentence(errors) #:nodoc:
721
+ template.content_tag(:p, errors.to_sentence, :class => 'inline-errors')
722
+ end
723
+
724
+ # Creates an error li list.
725
+ #
726
+ def error_list(errors) #:nodoc:
727
+ list_elements = []
728
+ errors.each do |error|
729
+ list_elements << template.content_tag(:li, error)
730
+ end
731
+ template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
732
+ end
733
+
734
+ # Generates the label for the input. Accepts the same options as Rails label
735
+ # method and a fourth option that allows the label to be generated as span
736
+ # with class label.
737
+ #
738
+ def input_label(method, text, options={}, as_span=false) #:nodoc:
739
+ text << required_or_optional_string(options.delete(:required)) unless options.delete(:skip_required)
740
+
741
+ if as_span
742
+ options[:class] ||= 'label'
743
+ template.content_tag(:span, text, options)
744
+ else
745
+ self.label(method, text, options)
746
+ end
747
+ end
748
+
749
+ # Generates the required or optional string. If the value set is a proc,
750
+ # it evaluates the proc first.
751
+ #
752
+ def required_or_optional_string(required) #:nodoc:
753
+ string_or_proc = required ? @@required_string : @@optional_string
754
+
755
+ if string_or_proc.is_a? Proc
756
+ string_or_proc.call
757
+ else
758
+ string_or_proc
759
+ end
760
+ end
761
+
762
+ # Generates a fieldset and wraps the content in an ordered list. When working
763
+ # with nested attributes (in Rails 2.3), it allows %i as interpolation option
764
+ # in :name. So you can do:
765
+ #
766
+ # f.inputs :name => 'Task #%i', :for => :tasks
767
+ #
768
+ # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
769
+ # 'Task #3' and so on.
770
+ #
771
+ # If you are using inputs :for, for more than one association in the same
772
+ # form builder, you might want to set the nested_child_index as well. You
773
+ # can do that doing:
774
+ #
775
+ # f.nested_child_index = -1
776
+ #
777
+ def field_set_and_list_wrapping(html_options, contents = '', &block) #:nodoc:
778
+ # Generates the legend text allowing nested_child_index support for interpolation
779
+ legend_text = html_options.delete(:name).to_s
780
+ legend_text %= html_options[:parent_builder].instance_variable_get('@nested_child_index').to_i + 1
781
+
782
+ legend = legend_text.blank? ? "" : template.content_tag(:legend, template.content_tag(:span, legend_text))
783
+ contents = template.capture(&block) if block_given?
784
+
785
+ fieldset = template.content_tag(:fieldset,
786
+ legend + template.content_tag(:ol, contents),
787
+ html_options.except(:builder, :parent_builder)
788
+ )
789
+
790
+ template.concat(fieldset) if block_given?
791
+ fieldset
792
+ end
793
+
794
+ # Also generates a fieldset and an ordered list but with label based in
795
+ # method. This methods is currently used by radio and datetime inputs.
796
+ #
797
+ def field_set_and_list_wrapping_for_method(method, options, contents)
798
+ template.content_tag(:fieldset,
799
+ %{<legend>#{input_label(method, options.delete(:label), options.slice(:required), true)}</legend>} +
800
+ template.content_tag(:ol, contents)
801
+ )
802
+ end
803
+
804
+ # For methods that have a database column, take a best guess as to what the inout method
805
+ # should be. In most cases, it will just return the column type (eg :string), but for special
806
+ # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
807
+ # something different (like :password and :select).
808
+ #
809
+ # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
810
+ # default is a :string, a similar behaviour to Rails' scaffolding.
811
+ #
812
+ def default_input_type(method) #:nodoc:
813
+ return :string if @object.nil?
814
+
815
+ # Find the column object by attribute
816
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
817
+
818
+ # Associations map by default to a select
819
+ return :select if column.nil? && find_reflection(method)
820
+
821
+ if column
822
+ # handle the special cases where the column type doesn't map to an input method
823
+ return :select if column.type == :integer && method.to_s =~ /_id$/
824
+ return :datetime if column.type == :timestamp
825
+ return :numeric if [:integer, :float, :decimal].include?(column.type)
826
+ return :password if column.type == :string && method.to_s =~ /password/
827
+ # otherwise assume the input name will be the same as the column type (eg string_input)
828
+ return column.type
829
+ else
830
+ obj = @object.send(method) if @object.respond_to?(method)
831
+
832
+ return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
833
+ return :password if method.to_s =~ /password/
834
+ return :string
835
+ end
836
+ end
837
+
838
+ # Used by select and radio inputs. The collection can be retrieved by
839
+ # three ways:
840
+ #
841
+ # * Explicitly provided through :collection
842
+ # * Retrivied through an association
843
+ # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
844
+ #
845
+ # If the collection is not a hash or an array of strings, fixnums or arrays,
846
+ # we use label_method and value_method to retreive an array with the
847
+ # appropriate label and value.
848
+ #
849
+ def find_collection_for_column(column, options)
850
+ reflection = find_reflection(column)
851
+
852
+ collection = if options[:collection]
853
+ options.delete(:collection)
854
+ elsif reflection || column.to_s =~ /_id$/
855
+ parent_class = if reflection
856
+ reflection.klass
857
+ else
858
+ ::ActiveSupport::Deprecation.warn("The _id way of doing things is deprecated. Please use the association method (#{column.to_s.sub(/_id$/,'')})", caller[3..-1])
859
+ column.to_s.sub(/_id$/,'').camelize.constantize
860
+ end
861
+
862
+ parent_class.find(:all)
863
+ else
864
+ create_boolean_collection(options)
865
+ end
866
+
867
+ collection = collection.to_a if collection.instance_of?(Hash)
868
+
869
+ # Return if we have an Array of strings, fixnums or arrays
870
+ return collection if collection.instance_of?(Array) &&
871
+ [Array, Fixnum, String, Symbol].include?(collection.first.class)
872
+
873
+ label = options.delete(:label_method) || detect_label_method(collection)
874
+ value = options.delete(:value_method) || :id
875
+
876
+ collection.map { |o| [o.send(label), o.send(value)] }
877
+ end
878
+
879
+ # Detected the label collection method when none is supplied using the
880
+ # values set in @@collection_label_methods.
881
+ #
882
+ def detect_label_method(collection) #:nodoc:
883
+ @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
884
+ end
885
+
886
+ # Returns a hash to be used by radio and select inputs when a boolean field
887
+ # is provided.
888
+ #
889
+ def create_boolean_collection(options)
890
+ options[:true] ||= I18n.t('yes', :default => 'Yes', :scope => [:formtastic])
891
+ options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
892
+ options[:value_as_class] = true unless options.key?(:value_as_class)
893
+
894
+ { options.delete(:true) => true, options.delete(:false) => false }
895
+ end
896
+
897
+ # Used by association inputs (select, radio) to generate the name that should
898
+ # be used for the input
899
+ #
900
+ # belongs_to :author; f.input :author; will generate 'author_id'
901
+ # has_many :authors; f.input :authors; will generate 'author_ids'
902
+ # has_and_belongs_to_many will act like has_many
903
+ #
904
+ def generate_association_input_name(method)
905
+ if reflection = find_reflection(method)
906
+ method = "#{method.to_s.singularize}_id"
907
+ method = method.pluralize if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
908
+ end
909
+ method
910
+ end
911
+
912
+ # If an association method is passed in (f.input :author) try to find the
913
+ # reflection object.
914
+ #
915
+ def find_reflection(method)
916
+ object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
917
+ end
918
+
919
+ # Generates default_string_options by retrieving column information from
920
+ # the database.
921
+ #
922
+ def default_string_options(method) #:nodoc:
923
+ column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
924
+
925
+ if column.nil? || column.limit.nil?
926
+ { :size => @@default_text_field_size }
927
+ else
928
+ { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
929
+ end
930
+ end
931
+
932
+ # Generate the html id for the li tag.
933
+ # It takes into account options[:index] and @auto_index to generate li
934
+ # elements with appropriate index scope. It also sanitizes the object
935
+ # and method names.
936
+ #
937
+ def generate_html_id(method_name, value='input')
938
+ if options.has_key?(:index)
939
+ index = "_#{options[:index]}"
940
+ elsif defined?(@auto_index)
941
+ index = "_#{@auto_index}"
942
+ else
943
+ index = ""
944
+ end
945
+ sanitized_method_name = method_name.to_s.sub(/\?$/,"")
946
+
947
+ "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
948
+ end
949
+
950
+ def sanitized_object_name
951
+ @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
952
+ end
953
+
954
+ end
955
+
956
+ # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
957
+ #
958
+ # * semantic_form_for(@post)
959
+ # * semantic_fields_for(@post)
960
+ # * semantic_form_remote_for(@post)
961
+ # * semantic_remote_form_for(@post)
962
+ #
963
+ # Each of which are the equivalent of:
964
+ #
965
+ # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
966
+ # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
967
+ # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
968
+ # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
969
+ #
970
+ # Example Usage:
971
+ #
972
+ # <% semantic_form_for @post do |f| %>
973
+ # <%= f.input :title %>
974
+ # <%= f.input :body %>
975
+ # <% end %>
976
+ #
977
+ # The above examples use a resource-oriented style of form_for() helper where only the @post
978
+ # object is given as an argument, but the generic style is also supported if you really want it,
979
+ # as is forms with inline objects (Post.new) rather than objects with instance variables (@post):
980
+ #
981
+ # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
982
+ # ...
983
+ # <% end %>
984
+ #
985
+ # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
986
+ # ...
987
+ # <% end %>
988
+ #
989
+ # The shorter, resource-oriented style is most definitely preferred, and has recieved the most
990
+ # testing to date.
991
+ #
992
+ # Please note: Although it's possible to call Rails' built-in form_for() helper without an
993
+ # object, all semantic forms *must* have an object (either Post.new or @post), as Formtastic
994
+ # has too many dependencies on an ActiveRecord object being present.
995
+ #
996
+ module SemanticFormHelper
997
+ @@builder = Formtastic::SemanticFormBuilder
998
+
999
+ # cattr_accessor :builder
1000
+ def self.builder=(val)
1001
+ @@builder = val
1002
+ end
1003
+
1004
+ [:form_for, :fields_for, :form_remote_for, :remote_form_for].each do |meth|
1005
+ src = <<-END_SRC
1006
+ def semantic_#{meth}(record_or_name_or_array, *args, &proc)
1007
+ options = args.extract_options!
1008
+ options[:builder] = @@builder
1009
+ options[:html] ||= {}
1010
+
1011
+ class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
1012
+ class_names << "formtastic"
1013
+ class_names << case record_or_name_or_array
1014
+ when String, Symbol then record_or_name_or_array.to_s # :post => "post"
1015
+ when Array then record_or_name_or_array.last.class.to_s.underscore # [@post, @comment] # => "comment"
1016
+ else record_or_name_or_array.class.to_s.underscore # @post => "post"
1017
+ end
1018
+ options[:html][:class] = class_names.join(" ")
1019
+
1020
+ #{meth}(record_or_name_or_array, *(args << options), &proc)
1021
+ end
1022
+ END_SRC
1023
+ module_eval src, __FILE__, __LINE__
1024
+ end
1025
+ end
1026
+ end