justinfrench-formtastic 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +127 -11
- data/generators/formtastic_stylesheets/templates/formtastic.css +1 -0
- data/lib/formtastic.rb +112 -37
- data/lib/justin_french/formtastic.rb +3 -3
- data/spec/formtastic_spec.rb +216 -68
- metadata +4 -3
data/README.textile
CHANGED
@@ -72,6 +72,11 @@ h2. Opinions
|
|
72
72
|
* make the common things we do easy, yet still ensure uncommon things are still possible
|
73
73
|
|
74
74
|
|
75
|
+
h2. Documentation
|
76
|
+
|
77
|
+
RDoc documentation _should_ be automatically generated after each commit and made available on the "rdoc.info website":http://rdoc.info/projects/justinfrench/formtastic.
|
78
|
+
|
79
|
+
|
75
80
|
h2. Installation
|
76
81
|
|
77
82
|
You can (and should) get it as a gem:
|
@@ -245,6 +250,123 @@ h2. The Available Inputs
|
|
245
250
|
|
246
251
|
The documentation is pretty good for each of these (what it does, what the output is, what the options are, etc) so go check it out.
|
247
252
|
|
253
|
+
h2. Internationalization (I18n)
|
254
|
+
|
255
|
+
Formtastic got some neat I18n-features. ActiveRecord object names and attributes are, by default, taken from calling @object.human_name and @object.human_attribute_name(attr) respectively. There are a few words specific to Formtastic that can be translated. See lib/locale/en.yml for more information.
|
256
|
+
|
257
|
+
h3. Label/Hint-localization
|
258
|
+
|
259
|
+
Formtastic supports localized *labels* and *hints* using the I18n API for more advanced usage. Your forms can now be DRYer and more flexible than ever, and still fully localized. This is how:
|
260
|
+
|
261
|
+
Basic localization (labels only):
|
262
|
+
|
263
|
+
<pre>
|
264
|
+
<% semantic_form_for @post do |form| %>
|
265
|
+
<%= form.input :title %> # => :label => I18n.t('activerecord.attributes.user.title') or 'Title'
|
266
|
+
<%= form.input :body %> # => :label => I18n.t('activerecord.attributes.user.body') or 'Body'
|
267
|
+
<%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
268
|
+
<% end %>
|
269
|
+
</pre>
|
270
|
+
|
271
|
+
*Note:* This is perfectly fine if you just want your labels to be translated using *ActiveRecord I18n attribute translations*, and you don't use input hints. But what if you do? And what if you don't want same labels in all forms?
|
272
|
+
|
273
|
+
Enhanced localization (labels and hints):
|
274
|
+
|
275
|
+
1. Enable I18n lookups by default (@config/initializers/formtastic.rb@):
|
276
|
+
|
277
|
+
<pre>
|
278
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
279
|
+
</pre>
|
280
|
+
|
281
|
+
2. Add some cool label-translations/variants (@config/locale/en.yml@):
|
282
|
+
|
283
|
+
<pre>
|
284
|
+
en:
|
285
|
+
formtastic:
|
286
|
+
labels:
|
287
|
+
post:
|
288
|
+
title: "Choose a title..."
|
289
|
+
body: "Write something..."
|
290
|
+
hints:
|
291
|
+
post:
|
292
|
+
title: "Choose a good title for you post."
|
293
|
+
body: "Write something inspiring here."
|
294
|
+
</pre>
|
295
|
+
|
296
|
+
*Note:* We are using English here still, but you get the point.
|
297
|
+
|
298
|
+
3. ...and now you'll get:
|
299
|
+
|
300
|
+
<pre>
|
301
|
+
<% semantic_form_for @post do |form| %>
|
302
|
+
<%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
|
303
|
+
<%= form.input :body %> # => :label => "Write something...", :hint => "Write something inspiring here."
|
304
|
+
<%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
305
|
+
<% end %>
|
306
|
+
</pre>
|
307
|
+
|
308
|
+
4. Override I18n settings:
|
309
|
+
|
310
|
+
<pre>
|
311
|
+
<% semantic_form_for @post do |form| %>
|
312
|
+
<%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
|
313
|
+
<%= form.input :body, :hint => false %> # => :label => "Write something..."
|
314
|
+
<%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
315
|
+
<% end %>
|
316
|
+
</pre>
|
317
|
+
|
318
|
+
If I18n-lookups is disabled, i.e.:
|
319
|
+
|
320
|
+
<pre>
|
321
|
+
Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
322
|
+
</pre>
|
323
|
+
|
324
|
+
...then you can enable I18n within the forms instead:
|
325
|
+
|
326
|
+
<pre>
|
327
|
+
<% semantic_form_for @post do |form| %>
|
328
|
+
<%= form.input :title, :label => true %> # => :label => "Choose a title..."
|
329
|
+
<%= form.input :body, :label => true %> # => :label => "Write something..."
|
330
|
+
<%= form.input :section, :label => true %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
331
|
+
<% end %>
|
332
|
+
</pre>
|
333
|
+
|
334
|
+
5. Advanced I18n lookups
|
335
|
+
|
336
|
+
For more flexible forms; Formtastic find translations using a bottom-up approach taking the following variables in account:
|
337
|
+
|
338
|
+
* @model@, e.g. "post"
|
339
|
+
* @action@, e.g. "edit"
|
340
|
+
* @attribute@, e.g. "title"
|
341
|
+
|
342
|
+
...in the following order:
|
343
|
+
|
344
|
+
1. @formtastic.{labels,hints}.MODEL.ACTION.ATTRIBUTE@ # By model and action
|
345
|
+
2. @formtastic.{labels,hints}.MODEL.ATTRIBUTE@ # By model
|
346
|
+
3. @formtastic.{labels,hints}.ATTRIBUTE@ # Global default
|
347
|
+
|
348
|
+
...which means that you can define translations like this:
|
349
|
+
|
350
|
+
<pre>
|
351
|
+
en:
|
352
|
+
formtastic:
|
353
|
+
labels:
|
354
|
+
title: "Title" # Default global value
|
355
|
+
article:
|
356
|
+
body: "Article content"
|
357
|
+
post:
|
358
|
+
new:
|
359
|
+
title: "Choose a title..."
|
360
|
+
body: "Write something..."
|
361
|
+
edit:
|
362
|
+
title: "Edit title"
|
363
|
+
body: "Edit body"
|
364
|
+
...
|
365
|
+
</pre>
|
366
|
+
|
367
|
+
h2. ValidationReflection plugin
|
368
|
+
|
369
|
+
If you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin installed, you won't have to specify the :required option (it checks the validations on the model instead).
|
248
370
|
|
249
371
|
h2. Configuration
|
250
372
|
|
@@ -291,19 +413,13 @@ If you wish, put something like this in config/initializers/formtastic_config.rb
|
|
291
413
|
|
292
414
|
# Set the default "priority countries" to suit your user base when using :as => :country
|
293
415
|
Formtastic::SemanticFormBuilder.priority_countries = ["Australia", "New Zealand"]
|
416
|
+
|
417
|
+
# Specifies if labels/hints for input fields automatically be looked up using I18n.
|
418
|
+
# Default value: false. Overridden for specific fields by setting value to true,
|
419
|
+
# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
|
420
|
+
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
294
421
|
</pre>
|
295
422
|
|
296
|
-
|
297
|
-
h2. Internationalization (I18n)
|
298
|
-
|
299
|
-
Supports I18n! ActiveRecord object names and attributes are, by default, taken from calling @object.human_name and @object.human_attribute_name(attr) respectively. There are a few words specific to Formtastic that can be translated. See lib/locale/en.yml for more information.
|
300
|
-
|
301
|
-
|
302
|
-
h2. ValidationReflection plugin
|
303
|
-
|
304
|
-
If you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin installed, you won't have to specify the :required option (it checks the validations on the model instead).
|
305
|
-
|
306
|
-
|
307
423
|
h2. Status
|
308
424
|
|
309
425
|
*THINGS ARE GOING TO CHANGE A BIT BEFORE WE HIT 1.0.*
|
@@ -83,6 +83,7 @@ form.formtastic fieldset ol li ul.errors li { padding:0; border:none; display:li
|
|
83
83
|
/* STRING & NUMERIC OVERRIDES
|
84
84
|
--------------------------------------------------------------------------------------------------*/
|
85
85
|
form.formtastic fieldset ol li.string input { width:74%; }
|
86
|
+
form.formtastic fieldset ol li.password input { width:74%; }
|
86
87
|
form.formtastic fieldset ol li.numeric input { width:74%; }
|
87
88
|
|
88
89
|
|
data/lib/formtastic.rb
CHANGED
@@ -19,10 +19,15 @@ module Formtastic #:nodoc:
|
|
19
19
|
@@inline_order = [ :input, :hints, :errors ]
|
20
20
|
@@file_methods = [ :file?, :public_filename ]
|
21
21
|
@@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
|
22
|
+
@@i18n_lookups_by_default = false
|
22
23
|
|
23
24
|
cattr_accessor :default_text_field_size, :all_fields_required_by_default, :required_string,
|
24
25
|
:optional_string, :inline_errors, :label_str_method, :collection_label_methods,
|
25
|
-
:inline_order, :file_methods, :priority_countries
|
26
|
+
:inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default
|
27
|
+
|
28
|
+
I18N_SCOPES = [ '{{model}}.{{action}}.{{attribute}}',
|
29
|
+
'{{model}}.{{attribute}}',
|
30
|
+
'{{attribute}}']
|
26
31
|
|
27
32
|
# Keeps simple mappings in a hash
|
28
33
|
INPUT_MAPPINGS = {
|
@@ -85,7 +90,7 @@ module Formtastic #:nodoc:
|
|
85
90
|
options[:as] ||= default_input_type(method)
|
86
91
|
|
87
92
|
html_class = [ options[:as], (options[:required] ? :required : :optional) ]
|
88
|
-
html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors
|
93
|
+
html_class << 'error' if @object && @object.respond_to?(:errors) && @object.errors[method.to_sym]
|
89
94
|
|
90
95
|
wrapper_html = options.delete(:wrapper_html) || {}
|
91
96
|
wrapper_html[:id] ||= generate_html_id(method)
|
@@ -95,7 +100,15 @@ module Formtastic #:nodoc:
|
|
95
100
|
::ActiveSupport::Deprecation.warn(":as => :#{options[:as]} is deprecated, use :as => :#{options[:as].to_s[8..-1]} instead", caller[3..-1])
|
96
101
|
end
|
97
102
|
|
98
|
-
|
103
|
+
if options[:input_html] && options[:input_html][:id]
|
104
|
+
options[:label_html] ||= {}
|
105
|
+
options[:label_html][:for] ||= options[:input_html][:id]
|
106
|
+
end
|
107
|
+
|
108
|
+
input_parts = @@inline_order.dup
|
109
|
+
input_parts.delete(:errors) if options[:as] == :hidden
|
110
|
+
|
111
|
+
list_item_content = input_parts.map do |type|
|
99
112
|
send(:"inline_#{type}_for", method, options)
|
100
113
|
end.compact.join("\n")
|
101
114
|
|
@@ -325,6 +338,8 @@ module Formtastic #:nodoc:
|
|
325
338
|
# * :label - An alternative form to give the label content. Whenever label
|
326
339
|
# is false, a blank string is returned.
|
327
340
|
# * :as_span - When true returns a span tag with class label instead of a label element
|
341
|
+
# * :input_name - Gives the input to match for. This is needed when you want to
|
342
|
+
# to call f.label :authors but it should match :author_ids.
|
328
343
|
#
|
329
344
|
# == Examples
|
330
345
|
#
|
@@ -337,23 +352,23 @@ module Formtastic #:nodoc:
|
|
337
352
|
#
|
338
353
|
def label(method, options_or_text=nil, options=nil)
|
339
354
|
if options_or_text.is_a?(Hash)
|
340
|
-
return if options_or_text[:label] == false
|
341
|
-
|
355
|
+
return "" if options_or_text[:label] == false
|
342
356
|
options = options_or_text
|
343
|
-
text
|
357
|
+
text = options.delete(:label)
|
344
358
|
else
|
345
|
-
text
|
359
|
+
text = options_or_text
|
346
360
|
options ||= {}
|
347
361
|
end
|
348
362
|
|
349
|
-
text
|
350
|
-
text
|
363
|
+
text = localized_attribute_string(method, text, :label) || humanized_attribute_name(method)
|
364
|
+
text += required_or_optional_string(options.delete(:required))
|
351
365
|
|
366
|
+
input_name = options.delete(:input_name) || method
|
352
367
|
if options.delete(:as_span)
|
353
368
|
options[:class] ||= 'label'
|
354
369
|
template.content_tag(:span, text, options)
|
355
370
|
else
|
356
|
-
super(
|
371
|
+
super(input_name, text, options)
|
357
372
|
end
|
358
373
|
end
|
359
374
|
|
@@ -371,13 +386,19 @@ module Formtastic #:nodoc:
|
|
371
386
|
def inline_errors_for(method, options=nil) #:nodoc:
|
372
387
|
return nil unless @object && @object.respond_to?(:errors) && [:sentence, :list].include?(@@inline_errors)
|
373
388
|
|
374
|
-
errors = @object.errors
|
389
|
+
errors = @object.errors[method.to_sym]
|
375
390
|
send("error_#{@@inline_errors}", Array(errors)) unless errors.blank?
|
376
391
|
end
|
377
392
|
alias :errors_on :inline_errors_for
|
378
393
|
|
379
394
|
protected
|
380
395
|
|
396
|
+
# Prepare options to be sent to label
|
397
|
+
#
|
398
|
+
def options_for_label(options)
|
399
|
+
options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
|
400
|
+
end
|
401
|
+
|
381
402
|
# Deals with :for option when it's supplied to inputs methods. Additional
|
382
403
|
# options to be passed down to :for should be supplied using :for_options
|
383
404
|
# key.
|
@@ -457,9 +478,9 @@ module Formtastic #:nodoc:
|
|
457
478
|
#
|
458
479
|
def input_simple(type, method, options)
|
459
480
|
html_options = options.delete(:input_html) || {}
|
460
|
-
html_options = default_string_options(method).merge(html_options) if STRING_MAPPINGS.include?(type)
|
481
|
+
html_options = default_string_options(method, type).merge(html_options) if STRING_MAPPINGS.include?(type)
|
461
482
|
|
462
|
-
self.label(method, options
|
483
|
+
self.label(method, options_for_label(options)) +
|
463
484
|
self.send(INPUT_MAPPINGS[type], method, html_options)
|
464
485
|
end
|
465
486
|
|
@@ -510,10 +531,10 @@ module Formtastic #:nodoc:
|
|
510
531
|
# </select>
|
511
532
|
#
|
512
533
|
#
|
513
|
-
# You can customize the options available in the select by passing in a collection (Array
|
514
|
-
#
|
515
|
-
#
|
516
|
-
#
|
534
|
+
# You can customize the options available in the select by passing in a collection (an Array or
|
535
|
+
# Hash) through the :collection option. If not provided, the choices are found by inferring the
|
536
|
+
# parent's class name from the method name and simply calling find(:all) on it
|
537
|
+
# (VehicleOwner.find(:all) in the example above).
|
517
538
|
#
|
518
539
|
# Examples:
|
519
540
|
#
|
@@ -521,6 +542,7 @@ module Formtastic #:nodoc:
|
|
521
542
|
# f.input :author, :collection => Author.find(:all)
|
522
543
|
# f.input :author, :collection => [@justin, @kate]
|
523
544
|
# f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
|
545
|
+
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
524
546
|
#
|
525
547
|
# Note: This input looks for a label method in the parent association.
|
526
548
|
#
|
@@ -565,12 +587,13 @@ module Formtastic #:nodoc:
|
|
565
587
|
|
566
588
|
reflection = find_reflection(method)
|
567
589
|
if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
|
590
|
+
options[:include_blank] = false
|
568
591
|
html_options[:multiple] ||= true
|
569
592
|
html_options[:size] ||= 5
|
570
593
|
end
|
571
594
|
|
572
595
|
input_name = generate_association_input_name(method)
|
573
|
-
self.label(
|
596
|
+
self.label(method, options_for_label(options).merge(:input_name => input_name)) +
|
574
597
|
self.select(input_name, collection, set_options(options), html_options)
|
575
598
|
end
|
576
599
|
alias :boolean_select_input :select_input
|
@@ -585,7 +608,7 @@ module Formtastic #:nodoc:
|
|
585
608
|
def time_zone_input(method, options)
|
586
609
|
html_options = options.delete(:input_html) || {}
|
587
610
|
|
588
|
-
self.label(method, options
|
611
|
+
self.label(method, options_for_label(options)) +
|
589
612
|
self.time_zone_select(method, options.delete(:priority_zones), set_options(options), html_options)
|
590
613
|
end
|
591
614
|
|
@@ -611,16 +634,17 @@ module Formtastic #:nodoc:
|
|
611
634
|
# </ol>
|
612
635
|
# </fieldset>
|
613
636
|
#
|
614
|
-
# You can customize the options available in the
|
615
|
-
#
|
616
|
-
#
|
617
|
-
#
|
637
|
+
# You can customize the options available in the select by passing in a collection (an Array or
|
638
|
+
# Hash) through the :collection option. If not provided, the choices are found by inferring the
|
639
|
+
# parent's class name from the method name and simply calling find(:all) on it
|
640
|
+
# (Author.find(:all) in the example above).
|
618
641
|
#
|
619
642
|
# Examples:
|
620
643
|
#
|
621
644
|
# f.input :author, :as => :radio, :collection => @authors
|
622
645
|
# f.input :author, :as => :radio, :collection => Author.find(:all)
|
623
646
|
# f.input :author, :as => :radio, :collection => [@justin, @kate]
|
647
|
+
# f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
|
624
648
|
#
|
625
649
|
# You can also customize the text label inside each option tag, by naming the correct method
|
626
650
|
# (:full_name, :display_name, :account_number, etc) to call on each object in the collection
|
@@ -865,7 +889,7 @@ module Formtastic #:nodoc:
|
|
865
889
|
html_options = options.delete(:input_html) || {}
|
866
890
|
priority_countries = options.delete(:priority_countries) || @@priority_countries
|
867
891
|
|
868
|
-
self.label(method, options
|
892
|
+
self.label(method, options_for_label(options)) +
|
869
893
|
self.country_select(method, priority_countries, set_options(options), html_options)
|
870
894
|
end
|
871
895
|
|
@@ -881,7 +905,7 @@ module Formtastic #:nodoc:
|
|
881
905
|
options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
|
882
906
|
|
883
907
|
label = options.delete(:label) || humanized_attribute_name(method)
|
884
|
-
self.label(method, input + label, options
|
908
|
+
self.label(method, input + label, options_for_label(options))
|
885
909
|
end
|
886
910
|
|
887
911
|
# Generates an input for the given method using the type supplied with :as.
|
@@ -904,6 +928,7 @@ module Formtastic #:nodoc:
|
|
904
928
|
# Generates hints for the given method using the text supplied in :hint.
|
905
929
|
#
|
906
930
|
def inline_hints_for(method, options) #:nodoc:
|
931
|
+
options[:hint] = localized_attribute_string(method, options[:hint], :hint)
|
907
932
|
return if options[:hint].blank?
|
908
933
|
template.content_tag(:p, options[:hint], :class => 'inline-hints')
|
909
934
|
end
|
@@ -978,7 +1003,7 @@ module Formtastic #:nodoc:
|
|
978
1003
|
contents = contents.join if contents.respond_to?(:join)
|
979
1004
|
|
980
1005
|
template.content_tag(:fieldset,
|
981
|
-
%{<legend>#{self.label(method, options
|
1006
|
+
%{<legend>#{self.label(method, options_for_label(options).merge!(:as_span => true))}</legend>} +
|
982
1007
|
template.content_tag(:ol, contents)
|
983
1008
|
)
|
984
1009
|
end
|
@@ -992,8 +1017,6 @@ module Formtastic #:nodoc:
|
|
992
1017
|
# default is a :string, a similar behaviour to Rails' scaffolding.
|
993
1018
|
#
|
994
1019
|
def default_input_type(method) #:nodoc:
|
995
|
-
return :string if @object.nil?
|
996
|
-
|
997
1020
|
column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
998
1021
|
|
999
1022
|
if column
|
@@ -1008,10 +1031,13 @@ module Formtastic #:nodoc:
|
|
1008
1031
|
# otherwise assume the input name will be the same as the column type (eg string_input)
|
1009
1032
|
return column.type
|
1010
1033
|
else
|
1011
|
-
|
1034
|
+
if @object
|
1035
|
+
return :select if find_reflection(method)
|
1036
|
+
|
1037
|
+
file = @object.send(method) if @object.respond_to?(method)
|
1038
|
+
return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
|
1039
|
+
end
|
1012
1040
|
|
1013
|
-
return :select if find_reflection(method)
|
1014
|
-
return :file if obj && @@file_methods.any? { |m| obj.respond_to?(m) }
|
1015
1041
|
return :password if method.to_s =~ /password/
|
1016
1042
|
return :string
|
1017
1043
|
end
|
@@ -1073,13 +1099,14 @@ module Formtastic #:nodoc:
|
|
1073
1099
|
options[:false] ||= I18n.t('no', :default => 'No', :scope => [:formtastic])
|
1074
1100
|
options[:value_as_class] = true unless options.key?(:value_as_class)
|
1075
1101
|
|
1076
|
-
|
1102
|
+
[ [ options.delete(:true), true], [ options.delete(:false), false ] ]
|
1077
1103
|
end
|
1078
1104
|
|
1079
1105
|
# Used by association inputs (select, radio) to generate the name that should
|
1080
1106
|
# be used for the input
|
1081
1107
|
#
|
1082
1108
|
# belongs_to :author; f.input :author; will generate 'author_id'
|
1109
|
+
# belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
|
1083
1110
|
# has_many :authors; f.input :authors; will generate 'author_ids'
|
1084
1111
|
# has_and_belongs_to_many will act like has_many
|
1085
1112
|
#
|
@@ -1088,7 +1115,7 @@ module Formtastic #:nodoc:
|
|
1088
1115
|
if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
|
1089
1116
|
"#{method.to_s.singularize}_ids"
|
1090
1117
|
else
|
1091
|
-
"#{method}_id"
|
1118
|
+
reflection.options[:foreign_key] || "#{method}_id"
|
1092
1119
|
end
|
1093
1120
|
else
|
1094
1121
|
method
|
@@ -1105,10 +1132,10 @@ module Formtastic #:nodoc:
|
|
1105
1132
|
# Generates default_string_options by retrieving column information from
|
1106
1133
|
# the database.
|
1107
1134
|
#
|
1108
|
-
def default_string_options(method) #:nodoc:
|
1135
|
+
def default_string_options(method, type) #:nodoc:
|
1109
1136
|
column = @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
|
1110
1137
|
|
1111
|
-
if column.nil? || column.limit.nil?
|
1138
|
+
if type == :numeric || column.nil? || column.limit.nil?
|
1112
1139
|
{ :size => @@default_text_field_size }
|
1113
1140
|
else
|
1114
1141
|
{ :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
|
@@ -1128,8 +1155,8 @@ module Formtastic #:nodoc:
|
|
1128
1155
|
else
|
1129
1156
|
index = ""
|
1130
1157
|
end
|
1131
|
-
sanitized_method_name = method_name.to_s.
|
1132
|
-
|
1158
|
+
sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
|
1159
|
+
|
1133
1160
|
"#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
|
1134
1161
|
end
|
1135
1162
|
|
@@ -1161,6 +1188,54 @@ module Formtastic #:nodoc:
|
|
1161
1188
|
end
|
1162
1189
|
end
|
1163
1190
|
|
1191
|
+
# Internal generic method for looking up localized values within Formtastic
|
1192
|
+
# using I18n, if no explicit value is set and I18n-lookups are enabled.
|
1193
|
+
#
|
1194
|
+
# Enabled/Disable this by setting:
|
1195
|
+
#
|
1196
|
+
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
|
1197
|
+
#
|
1198
|
+
# Lookup priority:
|
1199
|
+
#
|
1200
|
+
# 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
|
1201
|
+
# 'formtastic.{{type}}.{{model}}.{{attribute}}'
|
1202
|
+
# 'formtastic.{{type}}.{{attribute}}'
|
1203
|
+
#
|
1204
|
+
# Example:
|
1205
|
+
#
|
1206
|
+
# 'formtastic.labels.post.edit.title'
|
1207
|
+
# 'formtastic.labels.post.title'
|
1208
|
+
# 'formtastic.labels.title'
|
1209
|
+
#
|
1210
|
+
# NOTE: Generic, but only used for form input labels/hints.
|
1211
|
+
#
|
1212
|
+
def localized_attribute_string(attr_name, attr_value, i18n_key)
|
1213
|
+
if attr_value.is_a?(String)
|
1214
|
+
attr_value
|
1215
|
+
else
|
1216
|
+
use_i18n = attr_value.nil? ? @@i18n_lookups_by_default : attr_value
|
1217
|
+
if use_i18n
|
1218
|
+
model_name = @object.class.name.underscore
|
1219
|
+
action_name = template.params[:action].to_s rescue ''
|
1220
|
+
attribute_name = attr_name.to_s
|
1221
|
+
|
1222
|
+
defaults = I18N_SCOPES.collect do |i18n_scope|
|
1223
|
+
i18n_path = i18n_scope.dup
|
1224
|
+
i18n_path.gsub!('{{action}}', action_name)
|
1225
|
+
i18n_path.gsub!('{{model}}', model_name)
|
1226
|
+
i18n_path.gsub!('{{attribute}}', attribute_name)
|
1227
|
+
i18n_path.gsub!('..', '.')
|
1228
|
+
i18n_path.to_sym
|
1229
|
+
end
|
1230
|
+
defaults << ''
|
1231
|
+
|
1232
|
+
i18n_value = ::I18n.t(defaults.shift, :default => defaults,
|
1233
|
+
:scope => "formtastic.#{i18n_key.to_s.pluralize}")
|
1234
|
+
i18n_value.blank? ? nil : i18n_value
|
1235
|
+
end
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
|
1164
1239
|
end
|
1165
1240
|
|
1166
1241
|
# Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
|
@@ -1,6 +1,6 @@
|
|
1
|
-
module JustinFrench
|
2
|
-
module Formtastic
|
3
|
-
class SemanticFormBuilder < ::Formtastic::SemanticFormBuilder
|
1
|
+
module JustinFrench #:nodoc:
|
2
|
+
module Formtastic #:nodoc:
|
3
|
+
class SemanticFormBuilder < ::Formtastic::SemanticFormBuilder #:nodoc:
|
4
4
|
def initialize(*args)
|
5
5
|
::ActiveSupport::Deprecation.warn("JustinFrench::Formtastic::SemanticFormBuilder is deprecated. User Formtastic::SemanticFormBuilder instead", caller)
|
6
6
|
super
|
data/spec/formtastic_spec.rb
CHANGED
@@ -62,7 +62,7 @@ describe 'Formtastic' do
|
|
62
62
|
@fred.stub!(:login).and_return('fred_smith')
|
63
63
|
@fred.stub!(:id).and_return(37)
|
64
64
|
@fred.stub!(:new_record?).and_return(false)
|
65
|
-
@fred.stub!(:errors).and_return(mock('errors', :
|
65
|
+
@fred.stub!(:errors).and_return(mock('errors', :[] => nil))
|
66
66
|
|
67
67
|
@bob = mock('user')
|
68
68
|
@bob.stub!(:class).and_return(Author)
|
@@ -72,20 +72,20 @@ describe 'Formtastic' do
|
|
72
72
|
@bob.stub!(:posts).and_return([])
|
73
73
|
@bob.stub!(:post_ids).and_return([])
|
74
74
|
@bob.stub!(:new_record?).and_return(false)
|
75
|
-
@bob.stub!(:errors).and_return(mock('errors', :
|
75
|
+
@bob.stub!(:errors).and_return(mock('errors', :[] => nil))
|
76
76
|
|
77
77
|
Author.stub!(:find).and_return([@fred, @bob])
|
78
78
|
Author.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
|
79
79
|
Author.stub!(:human_name).and_return('Author')
|
80
80
|
Author.stub!(:reflect_on_all_validations).and_return([])
|
81
|
-
Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :klass => Post, :macro => :has_many) if column_name == :posts }
|
81
|
+
Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
|
82
82
|
|
83
83
|
# Sometimes we need a mock @post object and some Authors for belongs_to
|
84
84
|
@new_post = mock('post')
|
85
85
|
@new_post.stub!(:class).and_return(Post)
|
86
86
|
@new_post.stub!(:id).and_return(nil)
|
87
87
|
@new_post.stub!(:new_record?).and_return(true)
|
88
|
-
@new_post.stub!(:errors).and_return(mock('errors', :
|
88
|
+
@new_post.stub!(:errors).and_return(mock('errors', :[] => nil))
|
89
89
|
@new_post.stub!(:author).and_return(nil)
|
90
90
|
|
91
91
|
@freds_post = mock('post')
|
@@ -97,7 +97,7 @@ describe 'Formtastic' do
|
|
97
97
|
@freds_post.stub!(:authors).and_return([@fred])
|
98
98
|
@freds_post.stub!(:author_ids).and_return([@fred.id])
|
99
99
|
@freds_post.stub!(:new_record?).and_return(false)
|
100
|
-
@freds_post.stub!(:errors).and_return(mock('errors', :
|
100
|
+
@freds_post.stub!(:errors).and_return(mock('errors', :[] => nil))
|
101
101
|
@fred.stub!(:posts).and_return([@freds_post])
|
102
102
|
@fred.stub!(:post_ids).and_return([@freds_post.id])
|
103
103
|
|
@@ -107,9 +107,9 @@ describe 'Formtastic' do
|
|
107
107
|
Post.stub!(:reflect_on_association).and_return do |column_name|
|
108
108
|
case column_name
|
109
109
|
when :author, :author_status
|
110
|
-
mock('reflection', :klass => Author, :macro => :belongs_to)
|
110
|
+
mock('reflection', :options => {}, :klass => Author, :macro => :belongs_to)
|
111
111
|
when :authors
|
112
|
-
mock('reflection', :klass => Author, :macro => :has_and_belongs_to_many)
|
112
|
+
mock('reflection', :options => {}, :klass => Author, :macro => :has_and_belongs_to_many)
|
113
113
|
end
|
114
114
|
end
|
115
115
|
Post.stub!(:find).and_return([@freds_post])
|
@@ -326,7 +326,7 @@ describe 'Formtastic' do
|
|
326
326
|
|
327
327
|
it 'should return nil if label is false' do
|
328
328
|
semantic_form_for(@new_post) do |builder|
|
329
|
-
builder.label(:login, :label => false).should
|
329
|
+
builder.label(:login, :label => false).should be_blank
|
330
330
|
end
|
331
331
|
end
|
332
332
|
end
|
@@ -336,8 +336,8 @@ describe 'Formtastic' do
|
|
336
336
|
before(:each) do
|
337
337
|
@title_errors = ['must not be blank', 'must be longer than 10 characters', 'must be awesome']
|
338
338
|
@errors = mock('errors')
|
339
|
-
@errors.stub!(:
|
340
|
-
@errors.stub!(:
|
339
|
+
@errors.stub!(:[]).with(:title).and_return(@title_errors)
|
340
|
+
@errors.stub!(:[]).with(:body).and_return(nil)
|
341
341
|
@new_post.stub!(:errors).and_return(@errors)
|
342
342
|
end
|
343
343
|
|
@@ -573,13 +573,22 @@ describe 'Formtastic' do
|
|
573
573
|
|
574
574
|
describe 'when not provided' do
|
575
575
|
|
576
|
-
it 'should default to a string for forms without objects' do
|
576
|
+
it 'should default to a string for forms without objects unless column is password' do
|
577
577
|
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
578
578
|
concat(builder.input(:anything))
|
579
579
|
end
|
580
580
|
output_buffer.should have_tag('form li.string')
|
581
581
|
end
|
582
582
|
|
583
|
+
it 'should default to password for forms without objects if column is password' do
|
584
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
585
|
+
concat(builder.input(:password))
|
586
|
+
concat(builder.input(:password_confirmation))
|
587
|
+
concat(builder.input(:confirm_password))
|
588
|
+
end
|
589
|
+
output_buffer.should have_tag('form li.password', :count => 3)
|
590
|
+
end
|
591
|
+
|
583
592
|
it 'should default to a string for methods on objects that don\'t respond to "column_for_attribute"' do
|
584
593
|
@new_post.stub!(:method_without_a_database_column)
|
585
594
|
@new_post.stub!(:column_for_attribute).and_return(nil)
|
@@ -688,9 +697,8 @@ describe 'Formtastic' do
|
|
688
697
|
end
|
689
698
|
|
690
699
|
describe ':label option' do
|
691
|
-
|
700
|
+
|
692
701
|
describe 'when provided' do
|
693
|
-
|
694
702
|
it 'should be passed down to the label tag' do
|
695
703
|
semantic_form_for(@new_post) do |builder|
|
696
704
|
concat(builder.input(:title, :label => "Kustom"))
|
@@ -698,35 +706,89 @@ describe 'Formtastic' do
|
|
698
706
|
output_buffer.should have_tag("form li label", /Kustom/)
|
699
707
|
end
|
700
708
|
|
709
|
+
it 'should not generate a label if false' do
|
710
|
+
semantic_form_for(@new_post) do |builder|
|
711
|
+
concat(builder.input(:title, :label => false))
|
712
|
+
end
|
713
|
+
output_buffer.should_not have_tag("form li label")
|
714
|
+
end
|
715
|
+
|
716
|
+
it 'should be dupped if frozen' do
|
717
|
+
semantic_form_for(@new_post) do |builder|
|
718
|
+
concat(builder.input(:title, :label => "Kustom".freeze))
|
719
|
+
end
|
720
|
+
output_buffer.should have_tag("form li label", /Kustom/)
|
721
|
+
end
|
701
722
|
end
|
702
723
|
|
703
724
|
describe 'when not provided' do
|
704
|
-
describe '
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
725
|
+
describe 'when localized label is NOT provided' do
|
726
|
+
describe 'and object is not given' do
|
727
|
+
it 'should default the humanized method name, passing it down to the label tag' do
|
728
|
+
Formtastic::SemanticFormBuilder.label_str_method = :humanize
|
729
|
+
|
730
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
731
|
+
concat(builder.input(:meta_description))
|
732
|
+
end
|
733
|
+
|
734
|
+
output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
describe 'and object is given' do
|
739
|
+
it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
|
740
|
+
@new_post.stub!(:meta_description) # a two word method name
|
741
|
+
@new_post.class.should_receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
|
742
|
+
|
743
|
+
semantic_form_for(@new_post) do |builder|
|
744
|
+
concat(builder.input(:meta_description))
|
745
|
+
end
|
746
|
+
|
747
|
+
output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
|
710
748
|
end
|
711
|
-
|
712
|
-
output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
|
713
749
|
end
|
714
750
|
end
|
715
|
-
|
716
|
-
describe '
|
717
|
-
|
718
|
-
@
|
719
|
-
@
|
720
|
-
|
751
|
+
|
752
|
+
describe 'when localized label is provided' do
|
753
|
+
before do
|
754
|
+
@localized_label_text = 'Localized title'
|
755
|
+
@default_localized_label_text = 'Default localized title'
|
756
|
+
::I18n.backend.store_translations :en,
|
757
|
+
:formtastic => {
|
758
|
+
:labels => {
|
759
|
+
:title => @default_localized_label_text,
|
760
|
+
:post => {
|
761
|
+
:title => @localized_label_text
|
762
|
+
}
|
763
|
+
}
|
764
|
+
}
|
765
|
+
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
766
|
+
end
|
767
|
+
|
768
|
+
it 'should render a label with localized label (I18n)' do
|
721
769
|
semantic_form_for(@new_post) do |builder|
|
722
|
-
concat(builder.input(:
|
770
|
+
concat(builder.input(:title, :label => true))
|
723
771
|
end
|
724
|
-
|
725
|
-
|
772
|
+
output_buffer.should have_tag('form li label', @localized_label_text)
|
773
|
+
end
|
774
|
+
|
775
|
+
it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
|
776
|
+
::I18n.backend.store_translations :en,
|
777
|
+
:formtastic => {
|
778
|
+
:labels => {
|
779
|
+
:post => {
|
780
|
+
:title => nil
|
781
|
+
}
|
782
|
+
}
|
783
|
+
}
|
784
|
+
semantic_form_for(@new_post) do |builder|
|
785
|
+
concat(builder.input(:title, :label => true))
|
786
|
+
end
|
787
|
+
output_buffer.should have_tag('form li label', @default_localized_label_text)
|
726
788
|
end
|
727
789
|
end
|
728
790
|
end
|
729
|
-
|
791
|
+
|
730
792
|
end
|
731
793
|
|
732
794
|
describe ':hint option' do
|
@@ -742,12 +804,63 @@ describe 'Formtastic' do
|
|
742
804
|
end
|
743
805
|
|
744
806
|
describe 'when not provided' do
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
807
|
+
describe 'when localized hint (I18n) is provided' do
|
808
|
+
before do
|
809
|
+
@localized_hint_text = "This is the localized hint."
|
810
|
+
@default_localized_hint_text = "This is the default localized hint."
|
811
|
+
::I18n.backend.store_translations :en,
|
812
|
+
:formtastic => {
|
813
|
+
:hints => {
|
814
|
+
:title => @default_localized_hint_text,
|
815
|
+
:post => {
|
816
|
+
:title => @localized_hint_text
|
817
|
+
}
|
818
|
+
}
|
819
|
+
}
|
820
|
+
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
821
|
+
end
|
822
|
+
|
823
|
+
describe 'when provided value (hint value) is set to TRUE' do
|
824
|
+
it 'should render a hint paragraph containing a localized hint (I18n)' do
|
825
|
+
semantic_form_for(@new_post) do |builder|
|
826
|
+
concat(builder.input(:title, :hint => true))
|
827
|
+
end
|
828
|
+
output_buffer.should have_tag('form li p.inline-hints', @localized_hint_text)
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
|
832
|
+
::I18n.backend.store_translations :en,
|
833
|
+
:formtastic => {
|
834
|
+
:hints => {
|
835
|
+
:post => {
|
836
|
+
:title => nil
|
837
|
+
}
|
838
|
+
}
|
839
|
+
}
|
840
|
+
semantic_form_for(@new_post) do |builder|
|
841
|
+
concat(builder.input(:title, :hint => true))
|
842
|
+
end
|
843
|
+
output_buffer.should have_tag('form li p.inline-hints', @default_localized_hint_text)
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
describe 'when provided value (label value) is set to FALSE' do
|
848
|
+
it 'should not render a hint paragraph' do
|
849
|
+
semantic_form_for(@new_post) do |builder|
|
850
|
+
concat(builder.input(:title, :hint => false))
|
851
|
+
end
|
852
|
+
output_buffer.should_not have_tag('form li p.inline-hints', @localized_hint_text)
|
853
|
+
end
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
describe 'when localized hint (I18n) is not provided' do
|
858
|
+
it 'should not render a hint paragraph' do
|
859
|
+
semantic_form_for(@new_post) do |builder|
|
860
|
+
concat(builder.input(:title))
|
861
|
+
end
|
862
|
+
output_buffer.should_not have_tag('form li p.inline-hints')
|
749
863
|
end
|
750
|
-
output_buffer.should_not have_tag("form li p.inline-hints")
|
751
864
|
end
|
752
865
|
end
|
753
866
|
|
@@ -809,7 +922,7 @@ describe 'Formtastic' do
|
|
809
922
|
before do
|
810
923
|
@title_errors = ['must not be blank', 'must be longer than 10 characters', 'must be awesome']
|
811
924
|
@errors = mock('errors')
|
812
|
-
@errors.stub!(:
|
925
|
+
@errors.stub!(:[]).with(:title).and_return(@title_errors)
|
813
926
|
@new_post.stub!(:errors).and_return(@errors)
|
814
927
|
end
|
815
928
|
|
@@ -922,31 +1035,33 @@ describe 'Formtastic' do
|
|
922
1035
|
output_buffer.should have_tag("form li input[@name=\"post[title]\"]")
|
923
1036
|
end
|
924
1037
|
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
1038
|
+
unless type == :numeric
|
1039
|
+
it 'should have a maxlength matching the column limit' do
|
1040
|
+
@new_post.column_for_attribute(:title).limit.should == 50
|
1041
|
+
output_buffer.should have_tag("form li input[@maxlength='50']")
|
1042
|
+
end
|
929
1043
|
|
930
|
-
|
931
|
-
|
932
|
-
|
1044
|
+
it 'should use default_text_field_size for columns longer than default_text_field_size' do
|
1045
|
+
default_size = Formtastic::SemanticFormBuilder.default_text_field_size
|
1046
|
+
@new_post.stub!(:column_for_attribute).and_return(mock('column', :type => type, :limit => default_size * 2))
|
933
1047
|
|
934
|
-
|
935
|
-
|
1048
|
+
semantic_form_for(@new_post) do |builder|
|
1049
|
+
concat(builder.input(:title, :as => type))
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
output_buffer.should have_tag("form li input[@size='#{default_size}']")
|
936
1053
|
end
|
937
1054
|
|
938
|
-
|
939
|
-
|
1055
|
+
it 'should use the column size for columns shorter than default_text_field_size' do
|
1056
|
+
column_limit_shorted_than_default = 1
|
1057
|
+
@new_post.stub!(:column_for_attribute).and_return(mock('column', :type => type, :limit => column_limit_shorted_than_default))
|
940
1058
|
|
941
|
-
|
942
|
-
|
943
|
-
|
1059
|
+
semantic_form_for(@new_post) do |builder|
|
1060
|
+
concat(builder.input(:title, :as => type))
|
1061
|
+
end
|
944
1062
|
|
945
|
-
|
946
|
-
concat(builder.input(:title, :as => type))
|
1063
|
+
output_buffer.should have_tag("form li input[@size='#{column_limit_shorted_than_default}']")
|
947
1064
|
end
|
948
|
-
|
949
|
-
output_buffer.should have_tag("form li input[@size='#{column_limit_shorted_than_default}']")
|
950
1065
|
end
|
951
1066
|
|
952
1067
|
it 'should use default_text_field_size for methods without database columns' do
|
@@ -967,6 +1082,13 @@ describe 'Formtastic' do
|
|
967
1082
|
output_buffer.should have_tag("form li input.myclass")
|
968
1083
|
end
|
969
1084
|
|
1085
|
+
it 'should consider input_html :id in labels' do
|
1086
|
+
semantic_form_for(@new_post) do |builder|
|
1087
|
+
concat(builder.input(:title, :as => type, :input_html => { :id => 'myid' }))
|
1088
|
+
end
|
1089
|
+
output_buffer.should have_tag('form li label[@for="myid"]')
|
1090
|
+
end
|
1091
|
+
|
970
1092
|
it 'should generate input and labels even if no object is given' do
|
971
1093
|
semantic_form_for(:project, :url => 'http://test.host/') do |builder|
|
972
1094
|
concat(builder.input(:title, :as => type))
|
@@ -1080,11 +1202,11 @@ describe 'Formtastic' do
|
|
1080
1202
|
|
1081
1203
|
describe ":as => :hidden" do
|
1082
1204
|
before do
|
1083
|
-
@new_post.stub!(:
|
1205
|
+
@new_post.stub!(:secret)
|
1084
1206
|
@new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string))
|
1085
1207
|
|
1086
1208
|
semantic_form_for(@new_post) do |builder|
|
1087
|
-
concat(builder.input(:
|
1209
|
+
concat(builder.input(:secret, :as => :hidden))
|
1088
1210
|
end
|
1089
1211
|
end
|
1090
1212
|
|
@@ -1093,7 +1215,7 @@ describe 'Formtastic' do
|
|
1093
1215
|
end
|
1094
1216
|
|
1095
1217
|
it 'should have a post_hidden_input id on the wrapper' do
|
1096
|
-
output_buffer.should have_tag('form li#
|
1218
|
+
output_buffer.should have_tag('form li#post_secret_input')
|
1097
1219
|
end
|
1098
1220
|
|
1099
1221
|
it 'should not generate a label for the input' do
|
@@ -1101,10 +1223,24 @@ describe 'Formtastic' do
|
|
1101
1223
|
end
|
1102
1224
|
|
1103
1225
|
it "should generate a input field" do
|
1104
|
-
output_buffer.should have_tag("form li input#
|
1226
|
+
output_buffer.should have_tag("form li input#post_secret")
|
1105
1227
|
output_buffer.should have_tag("form li input[@type=\"hidden\"]")
|
1106
|
-
output_buffer.should have_tag("form li input[@name=\"post[
|
1228
|
+
output_buffer.should have_tag("form li input[@name=\"post[secret]\"]")
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
it "should not render inline errors" do
|
1232
|
+
@errors = mock('errors')
|
1233
|
+
@errors.stub!(:[]).with(:secret).and_return(["foo", "bah"])
|
1234
|
+
@new_post.stub!(:errors).and_return(@errors)
|
1235
|
+
|
1236
|
+
semantic_form_for(@new_post) do |builder|
|
1237
|
+
concat(builder.input(:secret, :as => :hidden))
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
output_buffer.should_not have_tag("form li p.inline-errors")
|
1241
|
+
output_buffer.should_not have_tag("form li ul.errors")
|
1107
1242
|
end
|
1243
|
+
|
1108
1244
|
end
|
1109
1245
|
|
1110
1246
|
describe ":as => :time_zone" do
|
@@ -1247,7 +1383,7 @@ describe 'Formtastic' do
|
|
1247
1383
|
before do
|
1248
1384
|
@new_post.stub!(:author).and_return(@bob)
|
1249
1385
|
@new_post.stub!(:author_id).and_return(@bob.id)
|
1250
|
-
Post.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :klass => Author, :macro => :belongs_to) }
|
1386
|
+
Post.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Author, :macro => :belongs_to) }
|
1251
1387
|
end
|
1252
1388
|
|
1253
1389
|
describe 'for belongs_to association' do
|
@@ -1393,7 +1529,11 @@ describe 'Formtastic' do
|
|
1393
1529
|
it 'should create a select without size' do
|
1394
1530
|
output_buffer.should_not have_tag('form li select[@size]')
|
1395
1531
|
end
|
1396
|
-
|
1532
|
+
|
1533
|
+
it 'should have a blank option' do
|
1534
|
+
output_buffer.should have_tag("form li select option[@value='']")
|
1535
|
+
end
|
1536
|
+
|
1397
1537
|
it 'should have a select option for each Author' do
|
1398
1538
|
output_buffer.should have_tag('form li select option', :count => Author.find(:all).size + 1)
|
1399
1539
|
Author.find(:all).each do |author|
|
@@ -1435,7 +1575,7 @@ describe 'Formtastic' do
|
|
1435
1575
|
|
1436
1576
|
it 'should have a label inside the wrapper' do
|
1437
1577
|
output_buffer.should have_tag('form li label')
|
1438
|
-
output_buffer.should have_tag('form li label', /Post
|
1578
|
+
output_buffer.should have_tag('form li label', /Post/)
|
1439
1579
|
output_buffer.should have_tag("form li label[@for='author_post_ids']")
|
1440
1580
|
end
|
1441
1581
|
|
@@ -1449,11 +1589,15 @@ describe 'Formtastic' do
|
|
1449
1589
|
end
|
1450
1590
|
|
1451
1591
|
it 'should have a select option for each Post' do
|
1452
|
-
output_buffer.should have_tag('form li select option', :count => Post.find(:all).size
|
1592
|
+
output_buffer.should have_tag('form li select option', :count => Post.find(:all).size)
|
1453
1593
|
Post.find(:all).each do |post|
|
1454
1594
|
output_buffer.should have_tag("form li select option[@value='#{post.id}']", /#{post.to_label}/)
|
1455
1595
|
end
|
1456
1596
|
end
|
1597
|
+
|
1598
|
+
it 'should not have a blank option' do
|
1599
|
+
output_buffer.should_not have_tag("form li select option[@value='']")
|
1600
|
+
end
|
1457
1601
|
|
1458
1602
|
it 'should have one option with a "selected" attribute' do
|
1459
1603
|
output_buffer.should have_tag('form li select option[@selected]', :count => 1)
|
@@ -1477,7 +1621,7 @@ describe 'Formtastic' do
|
|
1477
1621
|
|
1478
1622
|
it 'should have a label inside the wrapper' do
|
1479
1623
|
output_buffer.should have_tag('form li label')
|
1480
|
-
output_buffer.should have_tag('form li label', /Author
|
1624
|
+
output_buffer.should have_tag('form li label', /Author/)
|
1481
1625
|
output_buffer.should have_tag("form li label[@for='post_author_ids']")
|
1482
1626
|
end
|
1483
1627
|
|
@@ -1491,11 +1635,15 @@ describe 'Formtastic' do
|
|
1491
1635
|
end
|
1492
1636
|
|
1493
1637
|
it 'should have a select option for each Author' do
|
1494
|
-
output_buffer.should have_tag('form li select option', :count => Author.find(:all).size
|
1638
|
+
output_buffer.should have_tag('form li select option', :count => Author.find(:all).size)
|
1495
1639
|
Author.find(:all).each do |author|
|
1496
1640
|
output_buffer.should have_tag("form li select option[@value='#{author.id}']", /#{author.to_label}/)
|
1497
1641
|
end
|
1498
1642
|
end
|
1643
|
+
|
1644
|
+
it 'should not have a blank option' do
|
1645
|
+
output_buffer.should_not have_tag("form li select option[@value='']")
|
1646
|
+
end
|
1499
1647
|
|
1500
1648
|
it 'should have one option with a "selected" attribute' do
|
1501
1649
|
output_buffer.should have_tag('form li select option[@selected]', :count => 1)
|
@@ -2463,8 +2611,8 @@ describe 'Formtastic' do
|
|
2463
2611
|
describe 'without a block' do
|
2464
2612
|
|
2465
2613
|
before do
|
2466
|
-
Post.stub!(:reflections).and_return({:author => mock('reflection', :macro => :belongs_to),
|
2467
|
-
:comments => mock('reflection', :macro => :has_many) })
|
2614
|
+
Post.stub!(:reflections).and_return({:author => mock('reflection', :options => {}, :macro => :belongs_to),
|
2615
|
+
:comments => mock('reflection', :options => {}, :macro => :has_many) })
|
2468
2616
|
Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
|
2469
2617
|
Author.stub!(:find).and_return([@fred, @bob])
|
2470
2618
|
|
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.2.
|
4
|
+
version: 0.2.2
|
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-
|
12
|
+
date: 2009-08-30 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- spec/test_helper.rb
|
37
37
|
has_rdoc: false
|
38
38
|
homepage: http://github.com/justinfrench/formtastic/tree/master
|
39
|
+
licenses:
|
39
40
|
post_install_message:
|
40
41
|
rdoc_options:
|
41
42
|
- --charset=UTF-8
|
@@ -56,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
57
|
requirements: []
|
57
58
|
|
58
59
|
rubyforge_project:
|
59
|
-
rubygems_version: 1.
|
60
|
+
rubygems_version: 1.3.5
|
60
61
|
signing_key:
|
61
62
|
specification_version: 3
|
62
63
|
summary: A Rails form builder plugin/gem with semantically rich and accessible markup
|