justinfrench-formtastic 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +20 -16
- data/lib/formtastic.rb +126 -83
- data/spec/formtastic_spec.rb +117 -4
- metadata +2 -2
data/README.textile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
h1. Formtastic 0.1.
|
|
1
|
+
h1. Formtastic 0.1.5
|
|
2
2
|
|
|
3
3
|
Formtastic is a Rails FormBuilder DSL (with some other goodies) to make it far easier to create beautiful, semantically rich, syntactically awesome, readily stylable and wonderfully accessible HTML forms in your Rails applications.
|
|
4
4
|
|
|
@@ -86,7 +86,7 @@ And then add it as a dependency in your environment.rb file:
|
|
|
86
86
|
config.gem "justinfrench-formtastic",
|
|
87
87
|
:lib => 'formtastic',
|
|
88
88
|
:source => 'http://gems.github.com',
|
|
89
|
-
:version => '0.1.
|
|
89
|
+
:version => '0.1.5'
|
|
90
90
|
</pre>
|
|
91
91
|
|
|
92
92
|
If you're a little more old school, install it as a plugin:
|
|
@@ -156,18 +156,20 @@ If you want to customize the label text, or render some hint text below the fiel
|
|
|
156
156
|
<% end %>
|
|
157
157
|
</pre>
|
|
158
158
|
|
|
159
|
-
If you want to customize html elements for any non button inputs
|
|
160
|
-
to specify the :input_html options hash.
|
|
159
|
+
If you want to customize html elements for any non button inputs and the li
|
|
160
|
+
wrapper you just need to specify the :input_html and :wrapper_html options hash.
|
|
161
161
|
|
|
162
162
|
<pre>
|
|
163
163
|
<% semantic_form_for @post do |form| %>
|
|
164
164
|
<%= form.input :title, :input_html => {:size => 60} %>
|
|
165
|
-
<%= form.input :body %>
|
|
165
|
+
<%= form.input :body, :wrapper_html => { :class => 'body_wrapper' } %>
|
|
166
166
|
<%= form.input :created_at, :input_html => {:disabled => true} %>
|
|
167
167
|
<%= form.buttons %>
|
|
168
168
|
<% end %>
|
|
169
169
|
</pre>
|
|
170
170
|
|
|
171
|
+
To customize buttons, :button_html is available.
|
|
172
|
+
|
|
171
173
|
Nested forms (Rails 2.3) are also supported. You can do it in the Rails way:
|
|
172
174
|
|
|
173
175
|
<pre>
|
|
@@ -212,13 +214,13 @@ h2. The Available Inputs
|
|
|
212
214
|
|
|
213
215
|
* :select (a select menu) - default for ActiveRecord associations (belongs_to, has_many, has_and_belongs_to_many)
|
|
214
216
|
* :radio (a set of radio inputs) - alternative to :select for ActiveRecord belongs_to associations
|
|
217
|
+
* :time_zone (a select input) - default for :string column types with 'time_zone' in the method name
|
|
215
218
|
* :password (a password input) - default for :string column types with 'password' in the method name
|
|
216
219
|
* :text (a textarea) - default for :text column types
|
|
217
220
|
* :date (a date select) - default for :date column types
|
|
218
221
|
* :datetime (a date and time select) - default for :datetime and :timestamp column types
|
|
219
222
|
* :time (a time select) - default for :time column types
|
|
220
223
|
* :boolean (a checkbox) - default for :boolean column types
|
|
221
|
-
* :boolean_select (a yes/no select box)
|
|
222
224
|
* :string (a text field) - default for :string column types
|
|
223
225
|
* :numeric (a text field, like string) - default for :integer, :float and :decimal column types
|
|
224
226
|
* :file (a file field) - default for paperclip or attachment_fu attributes
|
|
@@ -329,21 +331,23 @@ h2. Contributors
|
|
|
329
331
|
Formtastic wouldn't be as awesome as it is today if it weren't for the wonderful contributions of these fine, fine coders. An extra huge thanks goes out to "José Valim":http://github.com/josevalim for nearly 50 patches.
|
|
330
332
|
|
|
331
333
|
* "Justin French":http://justinfrench.com
|
|
334
|
+
* "José Valim":http://github.com/josevalim
|
|
335
|
+
* "Jeff Smick":http://github.com/sprsquish
|
|
336
|
+
* "Tien Dung":http://github.com/tiendung
|
|
337
|
+
* "Mark Mansour":http://stateofflux.com
|
|
338
|
+
* "Andy Pearson":http://github.com/andypearson
|
|
339
|
+
* "negonicrac":http://github.com/negonicrac
|
|
332
340
|
* "Xavier Shay":http://rhnh.net
|
|
333
|
-
* "Bin Dong":http://github.com/dongbin
|
|
334
|
-
* "Ben Hamill":http://blog.benhamill.com/
|
|
335
341
|
* "Pat Allan":http://github.com/freelancing-god
|
|
336
|
-
* "negonicrac":http://github.com/negonicrac
|
|
337
|
-
* "Andy Pearson":http://github.com/andypearson
|
|
338
|
-
* "Mark Mansour":http://stateofflux.com
|
|
339
|
-
* "Tien Dung":http://github.com/tiendung
|
|
340
|
-
* "Sascha Hoellger":http://github.com/mitnal
|
|
341
|
-
* "Jeff Smick":http://github.com/sprsquish
|
|
342
|
-
* "José Valim":http://github.com/josevalim
|
|
343
|
-
* "Greg Fitzgerald":http://github.com/gregf/
|
|
344
342
|
* "Gareth Townsend":http://github.com/quamen
|
|
343
|
+
* "Sascha Hoellger":http://github.com/mitnal
|
|
344
|
+
* "Andrew Carpenter":http://github.com/andrewcarpenter
|
|
345
345
|
* "Jack Dempsey":http://github.com/jackdempsey/
|
|
346
|
+
* "Greg Fitzgerald":http://github.com/gregf/
|
|
347
|
+
* "Hector E. Gomez Morales":http://github.com/hectoregm
|
|
348
|
+
* "Ben Hamill":http://blog.benhamill.com/
|
|
346
349
|
* "Simon Chiu":http://github.com/tolatomeow
|
|
350
|
+
* "Bin Dong":http://github.com/dongbin
|
|
347
351
|
|
|
348
352
|
|
|
349
353
|
h2. Hey, join the Google group!
|
data/lib/formtastic.rb
CHANGED
|
@@ -35,7 +35,6 @@ module Formtastic #:nodoc:
|
|
|
35
35
|
STRING_MAPPINGS = [ :string, :password, :numeric ]
|
|
36
36
|
|
|
37
37
|
attr_accessor :template
|
|
38
|
-
attr_writer :nested_child_index
|
|
39
38
|
|
|
40
39
|
# Returns a suitable form input for the given +method+, using the database column information
|
|
41
40
|
# and other factors (like the method name) to figure out what you probably want.
|
|
@@ -47,6 +46,7 @@ module Formtastic #:nodoc:
|
|
|
47
46
|
# * :required - specify if the column is required (true) or not (false)
|
|
48
47
|
# * :hint - provide some text to hint or help the user provide the correct information for a field
|
|
49
48
|
# * :input_html - provide options that will be passed down to the generated input
|
|
49
|
+
# * :wrapper_html - provide options that will be passed down to the li wrapper
|
|
50
50
|
#
|
|
51
51
|
# Input Types:
|
|
52
52
|
#
|
|
@@ -55,6 +55,7 @@ module Formtastic #:nodoc:
|
|
|
55
55
|
# columns all map to a single numeric_input, for example).
|
|
56
56
|
#
|
|
57
57
|
# * :select (a select menu for associations) - default to association names
|
|
58
|
+
# * :time_zone (a select menu with time zones)
|
|
58
59
|
# * :radio (a set of radio inputs for associations) - default to association names
|
|
59
60
|
# * :password (a password input) - default for :string column types with 'password' in the method name
|
|
60
61
|
# * :text (a textarea) - default for :text column types
|
|
@@ -77,29 +78,25 @@ module Formtastic #:nodoc:
|
|
|
77
78
|
# <% end %>
|
|
78
79
|
#
|
|
79
80
|
def input(method, options = {})
|
|
80
|
-
options[:required] = method_required?(method
|
|
81
|
+
options[:required] = method_required?(method) unless options.key?(:required)
|
|
81
82
|
options[:as] ||= default_input_type(method)
|
|
82
83
|
|
|
83
|
-
options[:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
html_class = [ options[:as], (options[:required] ? :required : :optional) ]
|
|
85
|
+
html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors.on(method.to_s)
|
|
86
|
+
|
|
87
|
+
wrapper_html = options.delete(:wrapper_html) || {}
|
|
88
|
+
wrapper_html[:id] ||= generate_html_id(method)
|
|
89
|
+
wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
|
|
88
90
|
|
|
89
91
|
if [:boolean_select, :boolean_radio].include?(options[:as])
|
|
90
92
|
::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
|
|
91
93
|
end
|
|
92
94
|
|
|
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
95
|
list_item_content = @@inline_order.map do |type|
|
|
99
96
|
send(:"inline_#{type}_for", method, options)
|
|
100
97
|
end.compact.join("\n")
|
|
101
98
|
|
|
102
|
-
return template.content_tag(:li, list_item_content,
|
|
99
|
+
return template.content_tag(:li, list_item_content, wrapper_html)
|
|
103
100
|
end
|
|
104
101
|
|
|
105
102
|
# Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
|
|
@@ -233,21 +230,15 @@ module Formtastic #:nodoc:
|
|
|
233
230
|
html_options = args.extract_options!
|
|
234
231
|
html_options[:class] ||= "inputs"
|
|
235
232
|
|
|
236
|
-
if
|
|
237
|
-
|
|
238
|
-
inputs_for_nested_attributes(fields_for_object, args << html_options,
|
|
239
|
-
html_options.delete(:for_options) || {}, &block)
|
|
233
|
+
if html_options[:for]
|
|
234
|
+
inputs_for_nested_attributes(args, html_options, &block)
|
|
240
235
|
elsif block_given?
|
|
241
236
|
field_set_and_list_wrapping(html_options, &block)
|
|
242
237
|
else
|
|
243
238
|
if @object && args.empty?
|
|
244
|
-
# Get all belongs_to association
|
|
245
239
|
args = @object.class.reflections.map { |n,_| n if _.macro == :belongs_to }
|
|
246
|
-
|
|
247
|
-
# Get content columns and remove timestamps columns from it
|
|
248
240
|
args += @object.class.content_columns.map(&:name)
|
|
249
241
|
args -= %w[created_at updated_at created_on updated_on]
|
|
250
|
-
|
|
251
242
|
args.compact!
|
|
252
243
|
end
|
|
253
244
|
contents = args.map { |method| input(method.to_sym) }
|
|
@@ -285,9 +276,10 @@ module Formtastic #:nodoc:
|
|
|
285
276
|
#
|
|
286
277
|
# <%= form.commit_button "Go" %> => <input name="commit" type="submit" value="Go" />
|
|
287
278
|
#
|
|
288
|
-
def commit_button(value=nil, options
|
|
289
|
-
value
|
|
290
|
-
|
|
279
|
+
def commit_button(value=nil, options={})
|
|
280
|
+
value ||= save_or_create_button_text
|
|
281
|
+
button_html = options.delete(:button_html) || {}
|
|
282
|
+
template.content_tag(:li, self.submit(value, button_html), :class => "commit")
|
|
291
283
|
end
|
|
292
284
|
|
|
293
285
|
# A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
|
|
@@ -316,6 +308,25 @@ module Formtastic #:nodoc:
|
|
|
316
308
|
fields_for(record_or_name_or_array, *args, &block)
|
|
317
309
|
end
|
|
318
310
|
|
|
311
|
+
# Generates the label for the input. Accepts the same arguments as Rails
|
|
312
|
+
# label method and a fourth one that allows the label to be generated
|
|
313
|
+
# as span tag with class label.
|
|
314
|
+
#
|
|
315
|
+
# :required can be also sent as option. When true, marks a filed as required,
|
|
316
|
+
# when false marks it as optional. When nil, does nothing.
|
|
317
|
+
#
|
|
318
|
+
def label(method, text, options={}, as_span=false)
|
|
319
|
+
text ||= humanized_attribute_name(method)
|
|
320
|
+
text << required_or_optional_string(options.delete(:required))
|
|
321
|
+
|
|
322
|
+
if as_span
|
|
323
|
+
options[:class] ||= 'label'
|
|
324
|
+
template.content_tag(:span, text, options)
|
|
325
|
+
else
|
|
326
|
+
super(method, text, options)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
319
330
|
protected
|
|
320
331
|
|
|
321
332
|
# Deals with :for option when it's supplied to inputs methods. Additional
|
|
@@ -324,17 +335,20 @@ module Formtastic #:nodoc:
|
|
|
324
335
|
#
|
|
325
336
|
# It should raise an error if a block with arity zero is given.
|
|
326
337
|
#
|
|
327
|
-
def inputs_for_nested_attributes(
|
|
338
|
+
def inputs_for_nested_attributes(args, options, &block)
|
|
339
|
+
args << options.merge!(:parent => { :builder => self, :for => options[:for] })
|
|
340
|
+
|
|
328
341
|
fields_for_block = if block_given?
|
|
329
342
|
raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
|
|
330
343
|
'but the block does not accept any argument.' if block.arity <= 0
|
|
331
344
|
|
|
332
|
-
proc { |f| f.inputs(*
|
|
345
|
+
proc { |f| f.inputs(*args){ block.call(f) } }
|
|
333
346
|
else
|
|
334
|
-
proc { |f| f.inputs(*
|
|
347
|
+
proc { |f| f.inputs(*args) }
|
|
335
348
|
end
|
|
336
349
|
|
|
337
|
-
|
|
350
|
+
fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
|
|
351
|
+
semantic_fields_for(*fields_for_args, &fields_for_block)
|
|
338
352
|
end
|
|
339
353
|
|
|
340
354
|
# Remove any Formtastic-specific options before passing the down options.
|
|
@@ -374,9 +388,7 @@ module Formtastic #:nodoc:
|
|
|
374
388
|
# * if the :required option isn't provided, and the plugin isn't available, the value of the
|
|
375
389
|
# configuration option @@all_fields_required_by_default is used.
|
|
376
390
|
#
|
|
377
|
-
def method_required?(attribute
|
|
378
|
-
return required_option unless required_option.nil?
|
|
379
|
-
|
|
391
|
+
def method_required?(attribute) #:nodoc:
|
|
380
392
|
if @object && @object.class.respond_to?(:reflect_on_all_validations)
|
|
381
393
|
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
|
|
382
394
|
|
|
@@ -398,7 +410,8 @@ module Formtastic #:nodoc:
|
|
|
398
410
|
html_options = options.delete(:input_html) || {}
|
|
399
411
|
html_options = default_string_options(method).merge(html_options) if STRING_MAPPINGS.include?(type)
|
|
400
412
|
|
|
401
|
-
|
|
413
|
+
self.label(method, options.delete(:label), options.slice(:required)) +
|
|
414
|
+
self.send(INPUT_MAPPINGS[type], method, html_options)
|
|
402
415
|
end
|
|
403
416
|
|
|
404
417
|
# Outputs a label and a select box containing options from the parent
|
|
@@ -490,11 +503,25 @@ module Formtastic #:nodoc:
|
|
|
490
503
|
end
|
|
491
504
|
|
|
492
505
|
input_name = generate_association_input_name(method)
|
|
493
|
-
|
|
506
|
+
self.label(input_name, options.delete(:label), options.slice(:required)) +
|
|
494
507
|
self.select(input_name, collection, set_options(options), html_options)
|
|
495
508
|
end
|
|
496
509
|
alias :boolean_select_input :select_input
|
|
497
510
|
|
|
511
|
+
# Outputs a timezone select input as Rails' time_zone_select helper. You
|
|
512
|
+
# can give priority zones as option.
|
|
513
|
+
#
|
|
514
|
+
# Examples:
|
|
515
|
+
#
|
|
516
|
+
# f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
|
|
517
|
+
#
|
|
518
|
+
def time_zone_input(method, options)
|
|
519
|
+
html_options = options.delete(:input_html) || {}
|
|
520
|
+
|
|
521
|
+
self.label(method, options.delete(:label), options.slice(:required)) +
|
|
522
|
+
self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
|
|
523
|
+
end
|
|
524
|
+
|
|
498
525
|
# Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
|
|
499
526
|
# items, one for each possible choice in the belongs_to association. Each li contains a
|
|
500
527
|
# label and a radio input.
|
|
@@ -668,18 +695,16 @@ module Formtastic #:nodoc:
|
|
|
668
695
|
|
|
669
696
|
# Outputs a label containing a checkbox and the label text. The label defaults
|
|
670
697
|
# 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
698
|
# :checked_value and :unchecked_value options are also available.
|
|
674
699
|
#
|
|
675
700
|
def boolean_input(method, options)
|
|
676
701
|
html_options = options.delete(:input_html) || {}
|
|
677
702
|
|
|
678
|
-
|
|
679
|
-
|
|
703
|
+
input = self.check_box(method, set_options(options).merge(html_options),
|
|
704
|
+
options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
|
|
680
705
|
|
|
681
|
-
|
|
682
|
-
|
|
706
|
+
label = options.delete(:label) || humanized_attribute_name(method)
|
|
707
|
+
self.label(method, input + label, options.slice(:required))
|
|
683
708
|
end
|
|
684
709
|
|
|
685
710
|
# Generates an input for the given method using the type supplied with :as.
|
|
@@ -703,16 +728,24 @@ module Formtastic #:nodoc:
|
|
|
703
728
|
# or as sentence. If :none is set, no error is shown.
|
|
704
729
|
#
|
|
705
730
|
def inline_errors_for(method, options) #:nodoc:
|
|
706
|
-
return nil unless @object && [:sentence, :list].include?(@@inline_errors)
|
|
731
|
+
return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
|
|
732
|
+
|
|
733
|
+
# Ruby 1.9: Strings are not Enumerable, ie no String#to_a
|
|
734
|
+
errors = @object.errors.on(method.to_s)
|
|
735
|
+
unless errors.respond_to?(:to_a)
|
|
736
|
+
errors = [errors]
|
|
737
|
+
else
|
|
738
|
+
errors = errors.to_a
|
|
739
|
+
end
|
|
707
740
|
|
|
708
|
-
errors = @object.errors.on(method.to_s).to_a
|
|
709
741
|
send("error_#{@@inline_errors}", errors) unless errors.empty?
|
|
710
742
|
end
|
|
711
743
|
|
|
712
744
|
# Generates hints for the given method using the text supplied in :hint.
|
|
713
745
|
#
|
|
714
746
|
def inline_hints_for(method, options) #:nodoc:
|
|
715
|
-
options[:hint].blank?
|
|
747
|
+
return if options[:hint].blank?
|
|
748
|
+
template.content_tag(:p, options[:hint], :class => 'inline-hints')
|
|
716
749
|
end
|
|
717
750
|
|
|
718
751
|
# Creates an error sentence by calling to_sentence on the errors array.
|
|
@@ -731,31 +764,23 @@ module Formtastic #:nodoc:
|
|
|
731
764
|
template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
|
|
732
765
|
end
|
|
733
766
|
|
|
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
767
|
# Generates the required or optional string. If the value set is a proc,
|
|
750
768
|
# it evaluates the proc first.
|
|
751
769
|
#
|
|
752
770
|
def required_or_optional_string(required) #:nodoc:
|
|
753
|
-
string_or_proc = required
|
|
771
|
+
string_or_proc = case required
|
|
772
|
+
when true
|
|
773
|
+
@@required_string
|
|
774
|
+
when false
|
|
775
|
+
@@optional_string
|
|
776
|
+
else
|
|
777
|
+
required
|
|
778
|
+
end
|
|
754
779
|
|
|
755
|
-
if string_or_proc.is_a?
|
|
780
|
+
if string_or_proc.is_a?(Proc)
|
|
756
781
|
string_or_proc.call
|
|
757
782
|
else
|
|
758
|
-
string_or_proc
|
|
783
|
+
string_or_proc.to_s
|
|
759
784
|
end
|
|
760
785
|
end
|
|
761
786
|
|
|
@@ -768,23 +793,18 @@ module Formtastic #:nodoc:
|
|
|
768
793
|
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
|
|
769
794
|
# 'Task #3' and so on.
|
|
770
795
|
#
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
796
|
+
def field_set_and_list_wrapping(html_options, contents='', &block) #:nodoc:
|
|
797
|
+
legend = html_options.delete(:name).to_s
|
|
798
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
|
|
799
|
+
legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
|
|
781
800
|
|
|
782
|
-
legend = legend_text.blank? ? "" : template.content_tag(:legend, template.content_tag(:span, legend_text))
|
|
783
801
|
contents = template.capture(&block) if block_given?
|
|
784
802
|
|
|
803
|
+
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
|
804
|
+
contents = contents.join if contents.respond_to?(:join)
|
|
785
805
|
fieldset = template.content_tag(:fieldset,
|
|
786
806
|
legend + template.content_tag(:ol, contents),
|
|
787
|
-
html_options.except(:builder, :
|
|
807
|
+
html_options.except(:builder, :parent)
|
|
788
808
|
)
|
|
789
809
|
|
|
790
810
|
template.concat(fieldset) if block_given?
|
|
@@ -796,12 +816,12 @@ module Formtastic #:nodoc:
|
|
|
796
816
|
#
|
|
797
817
|
def field_set_and_list_wrapping_for_method(method, options, contents)
|
|
798
818
|
template.content_tag(:fieldset,
|
|
799
|
-
%{<legend>#{
|
|
819
|
+
%{<legend>#{self.label(method, options.delete(:label), options.slice(:required), true)}</legend>} +
|
|
800
820
|
template.content_tag(:ol, contents)
|
|
801
821
|
)
|
|
802
822
|
end
|
|
803
823
|
|
|
804
|
-
# For methods that have a database column, take a best guess as to what the
|
|
824
|
+
# For methods that have a database column, take a best guess as to what the input method
|
|
805
825
|
# should be. In most cases, it will just return the column type (eg :string), but for special
|
|
806
826
|
# cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
|
|
807
827
|
# something different (like :password and :select).
|
|
@@ -812,23 +832,22 @@ module Formtastic #:nodoc:
|
|
|
812
832
|
def default_input_type(method) #:nodoc:
|
|
813
833
|
return :string if @object.nil?
|
|
814
834
|
|
|
815
|
-
# Find the column object by attribute
|
|
816
835
|
column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
|
817
836
|
|
|
818
|
-
# Associations map by default to a select
|
|
819
|
-
return :select if column.nil? && find_reflection(method)
|
|
820
|
-
|
|
821
837
|
if column
|
|
822
838
|
# handle the special cases where the column type doesn't map to an input method
|
|
823
|
-
return :
|
|
824
|
-
return :
|
|
825
|
-
return :
|
|
826
|
-
return :
|
|
839
|
+
return :time_zone if column.type == :string && method.to_s =~ /time_zone/
|
|
840
|
+
return :select if column.type == :integer && method.to_s =~ /_id$/
|
|
841
|
+
return :datetime if column.type == :timestamp
|
|
842
|
+
return :numeric if [:integer, :float, :decimal].include?(column.type)
|
|
843
|
+
return :password if column.type == :string && method.to_s =~ /password/
|
|
844
|
+
|
|
827
845
|
# otherwise assume the input name will be the same as the column type (eg string_input)
|
|
828
846
|
return column.type
|
|
829
847
|
else
|
|
830
848
|
obj = @object.send(method) if @object.respond_to?(method)
|
|
831
849
|
|
|
850
|
+
return :select if find_reflection(method)
|
|
832
851
|
return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
|
|
833
852
|
return :password if method.to_s =~ /password/
|
|
834
853
|
return :string
|
|
@@ -913,7 +932,7 @@ module Formtastic #:nodoc:
|
|
|
913
932
|
# reflection object.
|
|
914
933
|
#
|
|
915
934
|
def find_reflection(method)
|
|
916
|
-
object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
|
|
935
|
+
@object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
|
|
917
936
|
end
|
|
918
937
|
|
|
919
938
|
# Generates default_string_options by retrieving column information from
|
|
@@ -947,10 +966,34 @@ module Formtastic #:nodoc:
|
|
|
947
966
|
"#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
|
|
948
967
|
end
|
|
949
968
|
|
|
969
|
+
# Gets the nested_child_index value from the parent builder. In Rails 2.3
|
|
970
|
+
# it always returns a fixnum. In next versions it returns a hash with each
|
|
971
|
+
# association that the parent builds.
|
|
972
|
+
#
|
|
973
|
+
def parent_child_index(parent)
|
|
974
|
+
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
|
975
|
+
|
|
976
|
+
if duck.is_a?(Hash)
|
|
977
|
+
child = parent[:for]
|
|
978
|
+
child = child.first if child.respond_to?(:first)
|
|
979
|
+
duck[child].to_i + 1
|
|
980
|
+
else
|
|
981
|
+
duck.to_i + 1
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
|
|
950
985
|
def sanitized_object_name
|
|
951
986
|
@sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
|
952
987
|
end
|
|
953
988
|
|
|
989
|
+
def humanized_attribute_name(method)
|
|
990
|
+
if @object && @object.class.respond_to?(:human_attribute_name)
|
|
991
|
+
@object.class.human_attribute_name(method.to_s)
|
|
992
|
+
else
|
|
993
|
+
method.to_s.send(@@label_str_method)
|
|
994
|
+
end
|
|
995
|
+
end
|
|
996
|
+
|
|
954
997
|
end
|
|
955
998
|
|
|
956
999
|
# Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
|
data/spec/formtastic_spec.rb
CHANGED
|
@@ -561,7 +561,7 @@ describe 'Formtastic' do
|
|
|
561
561
|
end
|
|
562
562
|
|
|
563
563
|
it 'should call the corresponding input method' do
|
|
564
|
-
[:select, :radio, :date, :datetime, :time, :boolean].each do |input_style|
|
|
564
|
+
[:select, :time_zone, :radio, :date, :datetime, :time, :boolean].each do |input_style|
|
|
565
565
|
@new_post.stub!(:generic_column_name)
|
|
566
566
|
@new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
|
|
567
567
|
semantic_form_for(@new_post) do |builder|
|
|
@@ -647,6 +647,47 @@ describe 'Formtastic' do
|
|
|
647
647
|
end
|
|
648
648
|
|
|
649
649
|
end
|
|
650
|
+
|
|
651
|
+
describe ':wrapper_html option' do
|
|
652
|
+
|
|
653
|
+
describe 'when provided' do
|
|
654
|
+
it 'should be passed down to the li tag' do
|
|
655
|
+
semantic_form_for(@new_post) do |builder|
|
|
656
|
+
concat(builder.input(:title, :wrapper_html => {:id => :another_id}))
|
|
657
|
+
end
|
|
658
|
+
output_buffer.should have_tag("form li#another_id")
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
it 'should append given classes to li default classes' do
|
|
662
|
+
semantic_form_for(@new_post) do |builder|
|
|
663
|
+
concat(builder.input(:title, :wrapper_html => {:class => :another_class}, :required => true))
|
|
664
|
+
end
|
|
665
|
+
output_buffer.should have_tag("form li.string")
|
|
666
|
+
output_buffer.should have_tag("form li.required")
|
|
667
|
+
output_buffer.should have_tag("form li.another_class")
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
it 'should allow classes to be an array' do
|
|
671
|
+
semantic_form_for(@new_post) do |builder|
|
|
672
|
+
concat(builder.input(:title, :wrapper_html => {:class => [ :my_class, :another_class ]}))
|
|
673
|
+
end
|
|
674
|
+
output_buffer.should have_tag("form li.string")
|
|
675
|
+
output_buffer.should have_tag("form li.my_class")
|
|
676
|
+
output_buffer.should have_tag("form li.another_class")
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
describe 'when not provided' do
|
|
681
|
+
it 'should use default id and class' do
|
|
682
|
+
semantic_form_for(@new_post) do |builder|
|
|
683
|
+
concat(builder.input(:title))
|
|
684
|
+
end
|
|
685
|
+
output_buffer.should have_tag("form li#post_title_input")
|
|
686
|
+
output_buffer.should have_tag("form li.string")
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
end
|
|
650
691
|
end
|
|
651
692
|
|
|
652
693
|
describe ':as any type of input' do
|
|
@@ -971,6 +1012,57 @@ describe 'Formtastic' do
|
|
|
971
1012
|
end
|
|
972
1013
|
end
|
|
973
1014
|
|
|
1015
|
+
describe ":as => :time_zone" do
|
|
1016
|
+
before do
|
|
1017
|
+
@new_post.stub!(:time_zone)
|
|
1018
|
+
@new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string))
|
|
1019
|
+
|
|
1020
|
+
semantic_form_for(@new_post) do |builder|
|
|
1021
|
+
concat(builder.input(:time_zone))
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
it "should have a time_zone class on the wrapper" do
|
|
1026
|
+
output_buffer.should have_tag('form li.time_zone')
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
it 'should have a post_title_input id on the wrapper' do
|
|
1030
|
+
output_buffer.should have_tag('form li#post_time_zone_input')
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
it 'should generate a label for the input' do
|
|
1034
|
+
output_buffer.should have_tag('form li label')
|
|
1035
|
+
output_buffer.should have_tag('form li label[@for="post_time_zone"')
|
|
1036
|
+
output_buffer.should have_tag('form li label', /Time zone/)
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
it "should generate a select" do
|
|
1040
|
+
output_buffer.should have_tag("form li select")
|
|
1041
|
+
output_buffer.should have_tag("form li select#post_time_zone")
|
|
1042
|
+
output_buffer.should have_tag("form li select[@name=\"post[time_zone]\"]")
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
it 'should use input_html to style inputs' do
|
|
1046
|
+
semantic_form_for(@new_post) do |builder|
|
|
1047
|
+
concat(builder.input(:time_zone, :input_html => { :class => 'myclass' }))
|
|
1048
|
+
end
|
|
1049
|
+
output_buffer.should have_tag("form li select.myclass")
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
it 'should generate input and labels even if no object is given' do
|
|
1053
|
+
semantic_form_for(:project, :url => 'http://test.host/') do |builder|
|
|
1054
|
+
concat(builder.input(:time_zone, :as => :time_zone))
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
output_buffer.should have_tag('form li label')
|
|
1058
|
+
output_buffer.should have_tag('form li label[@for="project_time_zone"')
|
|
1059
|
+
output_buffer.should have_tag('form li label', /Time zone/)
|
|
1060
|
+
|
|
1061
|
+
output_buffer.should have_tag("form li select")
|
|
1062
|
+
output_buffer.should have_tag("form li select#project_time_zone")
|
|
1063
|
+
output_buffer.should have_tag("form li select[@name=\"project[time_zone]\"]")
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
974
1066
|
|
|
975
1067
|
describe ':as => :radio' do
|
|
976
1068
|
|
|
@@ -1145,7 +1237,7 @@ describe 'Formtastic' do
|
|
|
1145
1237
|
|
|
1146
1238
|
it 'should have a label inside the wrapper' do
|
|
1147
1239
|
output_buffer.should have_tag('form li label')
|
|
1148
|
-
output_buffer.should have_tag('form li label', /
|
|
1240
|
+
output_buffer.should have_tag('form li label', /Post ids/)
|
|
1149
1241
|
output_buffer.should have_tag("form li label[@for='author_post_ids']")
|
|
1150
1242
|
end
|
|
1151
1243
|
|
|
@@ -1187,7 +1279,7 @@ describe 'Formtastic' do
|
|
|
1187
1279
|
|
|
1188
1280
|
it 'should have a label inside the wrapper' do
|
|
1189
1281
|
output_buffer.should have_tag('form li label')
|
|
1190
|
-
output_buffer.should have_tag('form li label', /
|
|
1282
|
+
output_buffer.should have_tag('form li label', /Author ids/)
|
|
1191
1283
|
output_buffer.should have_tag("form li label[@for='post_author_ids']")
|
|
1192
1284
|
end
|
|
1193
1285
|
|
|
@@ -1967,7 +2059,7 @@ describe 'Formtastic' do
|
|
|
1967
2059
|
|
|
1968
2060
|
it 'should send parent_builder as an option to allow child index interpolation' do
|
|
1969
2061
|
semantic_form_for(@new_post) do |builder|
|
|
1970
|
-
builder.
|
|
2062
|
+
builder.instance_variable_set('@nested_child_index', 0)
|
|
1971
2063
|
builder.inputs :for => [:author, @bob], :name => 'Author #%i' do |bob_builder|
|
|
1972
2064
|
concat('input')
|
|
1973
2065
|
end
|
|
@@ -1975,6 +2067,17 @@ describe 'Formtastic' do
|
|
|
1975
2067
|
|
|
1976
2068
|
output_buffer.should have_tag('fieldset legend', 'Author #1')
|
|
1977
2069
|
end
|
|
2070
|
+
|
|
2071
|
+
it 'should also provide child index interpolation when nested child index is a hash' do
|
|
2072
|
+
semantic_form_for(@new_post) do |builder|
|
|
2073
|
+
builder.instance_variable_set('@nested_child_index', :author => 10)
|
|
2074
|
+
builder.inputs :for => [:author, @bob], :name => 'Author #%i' do |bob_builder|
|
|
2075
|
+
concat('input')
|
|
2076
|
+
end
|
|
2077
|
+
end
|
|
2078
|
+
|
|
2079
|
+
output_buffer.should have_tag('fieldset legend', 'Author #11')
|
|
2080
|
+
end
|
|
1978
2081
|
end
|
|
1979
2082
|
|
|
1980
2083
|
describe 'when a :name option is provided' do
|
|
@@ -2311,6 +2414,16 @@ describe 'Formtastic' do
|
|
|
2311
2414
|
output_buffer.should have_tag('li.commit input[@name="commit"]')
|
|
2312
2415
|
end
|
|
2313
2416
|
|
|
2417
|
+
it 'should pass options given in :button_html to the button' do
|
|
2418
|
+
@new_post.stub!(:new_record?).and_return(false)
|
|
2419
|
+
semantic_form_for(@new_post) do |builder|
|
|
2420
|
+
concat(builder.commit_button('text', :button_html => {:class => 'my_class', :id => 'my_id'}))
|
|
2421
|
+
end
|
|
2422
|
+
|
|
2423
|
+
output_buffer.should have_tag('li.commit input#my_id')
|
|
2424
|
+
output_buffer.should have_tag('li.commit input.my_class')
|
|
2425
|
+
end
|
|
2426
|
+
|
|
2314
2427
|
end
|
|
2315
2428
|
|
|
2316
2429
|
describe 'when used on an existing record' do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: justinfrench-formtastic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin French
|
|
@@ -9,7 +9,7 @@ autorequire: formtastic
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-04-
|
|
12
|
+
date: 2009-04-19 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|