justinfrench-formtastic 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|