formtastic 1.0.1 → 1.1.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +2 -2
- data/Rakefile +33 -8
- data/generators/formtastic/templates/formtastic.css +3 -18
- data/init.rb +5 -0
- data/lib/formtastic.rb +180 -82
- data/lib/formtastic/i18n.rb +8 -9
- data/lib/formtastic/layout_helper.rb +0 -1
- data/lib/formtastic/railtie.rb +12 -0
- data/lib/formtastic/util.rb +7 -0
- data/lib/generators/formtastic/form/form_generator.rb +86 -0
- data/lib/generators/formtastic/install/install_generator.rb +24 -0
- data/rails/init.rb +1 -6
- data/spec/buttons_spec.rb +25 -8
- data/spec/commit_button_spec.rb +67 -29
- data/spec/custom_builder_spec.rb +40 -4
- data/spec/defaults_spec.rb +1 -1
- data/spec/error_proc_spec.rb +1 -1
- data/spec/errors_spec.rb +3 -2
- data/spec/form_helper_spec.rb +33 -17
- data/spec/helpers/layout_helper_spec.rb +21 -0
- data/spec/i18n_spec.rb +13 -9
- data/spec/include_blank_spec.rb +9 -5
- data/spec/input_spec.rb +188 -48
- data/spec/inputs/boolean_input_spec.rb +14 -7
- data/spec/inputs/check_boxes_input_spec.rb +150 -20
- data/spec/inputs/country_input_spec.rb +9 -5
- data/spec/inputs/date_input_spec.rb +21 -9
- data/spec/inputs/datetime_input_spec.rb +33 -14
- data/spec/inputs/file_input_spec.rb +4 -3
- data/spec/inputs/hidden_input_spec.rb +11 -4
- data/spec/inputs/numeric_input_spec.rb +3 -3
- data/spec/inputs/password_input_spec.rb +3 -3
- data/spec/inputs/radio_input_spec.rb +29 -10
- data/spec/inputs/select_input_spec.rb +66 -26
- data/spec/inputs/string_input_spec.rb +5 -4
- data/spec/inputs/text_input_spec.rb +4 -3
- data/spec/inputs/time_input_spec.rb +26 -10
- data/spec/inputs/time_zone_input_spec.rb +11 -5
- data/spec/inputs_spec.rb +103 -39
- data/spec/label_spec.rb +5 -3
- data/spec/semantic_errors_spec.rb +7 -7
- data/spec/semantic_fields_for_spec.rb +5 -4
- data/spec/spec_helper.rb +86 -38
- data/spec/{custom_macros.rb → support/custom_macros.rb} +69 -35
- data/spec/support/output_buffer.rb +4 -0
- data/spec/support/test_environment.rb +45 -0
- metadata +26 -28
- data/spec/layout_helper_spec.rb +0 -29
data/README.textile
CHANGED
@@ -345,7 +345,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
|
|
345
345
|
title: "Choose a good title for you post."
|
346
346
|
body: "Write something inspiring here."
|
347
347
|
actions:
|
348
|
-
create: "Create my {
|
348
|
+
create: "Create my %{model}"
|
349
349
|
update: "Save changes"
|
350
350
|
dummie: "Launch!"
|
351
351
|
</pre>
|
@@ -362,7 +362,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
|
|
362
362
|
<%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
363
363
|
<% end %>
|
364
364
|
<% form.buttons do %>
|
365
|
-
<%= form.commit_button %> # => "Create my {
|
365
|
+
<%= form.commit_button %> # => "Create my %{model}"
|
366
366
|
<% end %>
|
367
367
|
<% end %>
|
368
368
|
</pre>
|
data/Rakefile
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require 'rubygems'
|
2
3
|
require 'rake'
|
3
4
|
require 'rake/rdoctask'
|
4
5
|
|
5
6
|
begin
|
7
|
+
gem 'rspec', '>= 1.2.6'
|
8
|
+
gem 'rspec-rails', '>= 1.2.6'
|
9
|
+
require 'spec'
|
6
10
|
require 'spec/rake/spectask'
|
7
11
|
rescue LoadError
|
8
12
|
begin
|
9
|
-
|
10
|
-
require '
|
13
|
+
require 'rspec/core/rake_task.rb'
|
14
|
+
require 'rspec/core/version'
|
11
15
|
rescue LoadError
|
12
16
|
puts "[formtastic:] RSpec - or one of it's dependencies - is not available. Install it with: sudo gem install rspec-rails"
|
13
17
|
end
|
@@ -25,10 +29,12 @@ begin
|
|
25
29
|
------------------------------------------------------------------------
|
26
30
|
You can now (optionally) run the generator to copy some stylesheets and
|
27
31
|
a config initializer into your application:
|
28
|
-
|
32
|
+
rails generator formastic:install # Rails 3
|
33
|
+
./script/generate formtastic # Rails 2
|
29
34
|
|
30
35
|
To generate some semantic form markup for your existing models, just run:
|
31
|
-
|
36
|
+
rails generate formtastic:form MODEL_NAME # Rails 3
|
37
|
+
./script/generate form MODEL_NAME # Rails 2
|
32
38
|
|
33
39
|
Find out more and get involved:
|
34
40
|
http://github.com/justinfrench/formtastic
|
@@ -49,13 +55,13 @@ begin
|
|
49
55
|
s.post_install_message = INSTALL_MESSAGE
|
50
56
|
|
51
57
|
s.require_path = 'lib'
|
52
|
-
s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{rails,lib,generators,spec}/**/*")
|
58
|
+
s.files = %w(MIT-LICENSE README.textile Rakefile init.rb) + Dir.glob("{rails,lib,generators,spec}/**/*")
|
53
59
|
|
54
60
|
# Runtime dependencies: When installing Formtastic these will be checked if they are installed.
|
55
61
|
# Will be offered to install these if they are not already installed.
|
56
|
-
s.add_dependency 'activesupport', '>= 2.3.0'
|
57
|
-
s.add_dependency 'actionpack', '>= 2.3.0'
|
58
|
-
s.add_dependency 'i18n', '
|
62
|
+
s.add_dependency 'activesupport', '>= 2.3.0'
|
63
|
+
s.add_dependency 'actionpack', '>= 2.3.0'
|
64
|
+
s.add_dependency 'i18n', '>= 0.4.0'
|
59
65
|
|
60
66
|
# Development dependencies. Not installed by default.
|
61
67
|
# Install with: sudo gem install formtastic --development
|
@@ -100,3 +106,22 @@ if defined?(Spec)
|
|
100
106
|
t.rcov_opts = ['--exclude', 'spec,Library']
|
101
107
|
end
|
102
108
|
end
|
109
|
+
|
110
|
+
if defined?(RSpec)
|
111
|
+
desc 'Test the formtastic plugin.'
|
112
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
113
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
114
|
+
end
|
115
|
+
|
116
|
+
desc 'Test the formtastic plugin with specdoc formatting and colors'
|
117
|
+
RSpec::Core::RakeTask.new('specdoc') do |t|
|
118
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
119
|
+
end
|
120
|
+
|
121
|
+
desc "Run all examples with RCov"
|
122
|
+
RSpec::Core::RakeTask.new('examples_with_rcov') do |t|
|
123
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
124
|
+
t.rcov = true
|
125
|
+
t.rcov_opts = ['--exclude', 'spec,Library']
|
126
|
+
end
|
127
|
+
end
|
@@ -30,28 +30,16 @@ form.formtastic ul.errors li { padding:0; border:none; display:list-item; }
|
|
30
30
|
|
31
31
|
/* FIELDSETS & LISTS
|
32
32
|
--------------------------------------------------------------------------------------------------*/
|
33
|
-
form.formtastic fieldset { }
|
33
|
+
form.formtastic fieldset { overflow:auto; } /* clearing contained floats */
|
34
34
|
form.formtastic fieldset.inputs { }
|
35
35
|
form.formtastic fieldset.buttons { padding-left:25%; }
|
36
36
|
form.formtastic fieldset ol { }
|
37
37
|
form.formtastic fieldset.buttons li { float:left; padding-right:0.5em; }
|
38
38
|
|
39
|
-
/* clearfixing the fieldsets */
|
40
|
-
form.formtastic fieldset { display: inline-block; }
|
41
|
-
form.formtastic fieldset:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
|
42
|
-
html[xmlns] form.formtastic fieldset { display: block; }
|
43
|
-
* html form.formtastic fieldset { height: 1%; }
|
44
|
-
|
45
|
-
|
46
39
|
/* INPUT LIs
|
47
40
|
--------------------------------------------------------------------------------------------------*/
|
48
41
|
form.formtastic fieldset > ol > li { margin-bottom:1.5em; }
|
49
|
-
|
50
|
-
/* clearfixing the li's */
|
51
|
-
form.formtastic fieldset > ol > li { display: inline-block; }
|
52
|
-
form.formtastic fieldset > ol > li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
|
53
|
-
html[xmlns] form.formtastic fieldset > ol > li { display: block; }
|
54
|
-
* html form.formtastic fieldset > ol > li { height: 1%; }
|
42
|
+
form.formtastic fieldset > ol > li { overflow:auto; } /* clearing contained floats */
|
55
43
|
|
56
44
|
form.formtastic fieldset > ol > li.required { }
|
57
45
|
form.formtastic fieldset > ol > li.optional { }
|
@@ -100,11 +88,8 @@ form.formtastic fieldset > ol > li.text textarea { width:74%; }
|
|
100
88
|
|
101
89
|
|
102
90
|
/* HIDDEN OVERRIDES
|
103
|
-
The dual declarations are required because of our clearfix display hack on the LIs, which is more
|
104
|
-
specific than the more general rule below. TODO: Revist the clearing hack and this rule.
|
105
91
|
--------------------------------------------------------------------------------------------------*/
|
106
|
-
form.formtastic fieldset ol li.hidden
|
107
|
-
html[xmlns] form.formtastic fieldset ol li.hidden { display:none; }
|
92
|
+
form.formtastic fieldset ol li.hidden { display:none; }
|
108
93
|
|
109
94
|
/* BOOLEAN OVERRIDES
|
110
95
|
--------------------------------------------------------------------------------------------------*/
|
data/init.rb
ADDED
data/lib/formtastic.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
|
3
3
|
require File.join(File.dirname(__FILE__), *%w[formtastic util])
|
4
|
+
require File.join(File.dirname(__FILE__), *%w[formtastic railtie]) if defined?(::Rails::Railtie)
|
4
5
|
|
5
6
|
module Formtastic #:nodoc:
|
6
7
|
|
7
8
|
class SemanticFormBuilder < ActionView::Helpers::FormBuilder
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
9
|
+
class_inheritable_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
|
10
|
+
:required_string, :optional_string, :inline_errors, :label_str_method, :collection_value_methods, :collection_label_methods,
|
11
|
+
:inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels, :default_commit_button_accesskey,
|
12
|
+
:instance_reader => false
|
13
|
+
|
14
|
+
self.default_text_field_size = 50
|
15
|
+
self.default_text_area_height = 20
|
16
|
+
self.all_fields_required_by_default = true
|
17
|
+
self.include_blank_for_select_by_default = true
|
18
|
+
self.required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
|
19
|
+
self.optional_string = ''
|
20
|
+
self.inline_errors = :sentence
|
21
|
+
self.label_str_method = :humanize
|
22
|
+
self.collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
|
23
|
+
self.collection_value_methods = %w[id to_s]
|
24
|
+
self.inline_order = [ :input, :hints, :errors ]
|
25
|
+
self.file_methods = [ :file?, :public_filename, :filename ]
|
26
|
+
self.priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
|
27
|
+
self.i18n_lookups_by_default = false
|
28
|
+
self.escape_html_entities_in_hints_and_labels = true
|
29
|
+
self.default_commit_button_accesskey = nil
|
28
30
|
|
29
31
|
RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
30
32
|
|
@@ -100,7 +102,7 @@ module Formtastic #:nodoc:
|
|
100
102
|
options[:label_html][:for] ||= options[:input_html][:id]
|
101
103
|
end
|
102
104
|
|
103
|
-
input_parts =
|
105
|
+
input_parts = self.class.inline_order.dup
|
104
106
|
input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
|
105
107
|
|
106
108
|
list_item_content = input_parts.map do |type|
|
@@ -327,13 +329,17 @@ module Formtastic #:nodoc:
|
|
327
329
|
# ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
|
328
330
|
# if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
|
329
331
|
# fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
332
|
+
if @object.class.model_name.respond_to?(:human)
|
333
|
+
object_name = @object.class.model_name.human
|
334
|
+
else
|
335
|
+
object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
|
336
|
+
crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
|
337
|
+
decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
|
338
|
+
object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
|
339
|
+
end
|
334
340
|
else
|
335
341
|
key = :submit
|
336
|
-
object_name = @object_name.to_s.send(
|
342
|
+
object_name = @object_name.to_s.send(self.class.label_str_method)
|
337
343
|
end
|
338
344
|
|
339
345
|
text = (self.localized_string(key, text, :action, :model => object_name) ||
|
@@ -342,8 +348,8 @@ module Formtastic #:nodoc:
|
|
342
348
|
button_html = options.delete(:button_html) || {}
|
343
349
|
button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
|
344
350
|
element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
|
345
|
-
accesskey = (options.delete(:accesskey) ||
|
346
|
-
button_html = button_html.merge(:accesskey => accesskey) if accesskey
|
351
|
+
accesskey = (options.delete(:accesskey) || self.class.default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
|
352
|
+
button_html = button_html.merge(:accesskey => accesskey) if accesskey
|
347
353
|
template.content_tag(:li, Formtastic::Util.html_safe(self.submit(text, button_html)), :class => element_class)
|
348
354
|
end
|
349
355
|
|
@@ -368,7 +374,7 @@ module Formtastic #:nodoc:
|
|
368
374
|
#
|
369
375
|
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
370
376
|
opts = args.extract_options!
|
371
|
-
opts[:builder] ||=
|
377
|
+
opts[:builder] ||= self.class
|
372
378
|
args.push(opts)
|
373
379
|
fields_for(record_or_name_or_array, *args, &block)
|
374
380
|
end
|
@@ -431,7 +437,7 @@ module Formtastic #:nodoc:
|
|
431
437
|
errors = [@object.errors[method.to_sym]]
|
432
438
|
errors << [@object.errors[association_primary_key(method)]] if association_macro_for_method(method) == :belongs_to
|
433
439
|
errors = errors.flatten.compact.uniq
|
434
|
-
send(:"error_#{
|
440
|
+
send(:"error_#{self.class.inline_errors}", [*errors]) if errors.any?
|
435
441
|
else
|
436
442
|
nil
|
437
443
|
end
|
@@ -454,7 +460,7 @@ module Formtastic #:nodoc:
|
|
454
460
|
errors = Array(@object.errors[method.to_sym]).to_sentence
|
455
461
|
errors.present? ? array << [attribute, errors].join(" ") : array ||= []
|
456
462
|
end
|
457
|
-
full_errors << @object.errors
|
463
|
+
full_errors << @object.errors[:base]
|
458
464
|
full_errors.flatten!
|
459
465
|
full_errors.compact!
|
460
466
|
return nil if full_errors.blank?
|
@@ -467,7 +473,7 @@ module Formtastic #:nodoc:
|
|
467
473
|
protected
|
468
474
|
|
469
475
|
def render_inline_errors?
|
470
|
-
@object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(
|
476
|
+
@object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(self.class.inline_errors)
|
471
477
|
end
|
472
478
|
|
473
479
|
# Collects content columns (non-relation columns) for the current form object class.
|
@@ -524,9 +530,17 @@ module Formtastic #:nodoc:
|
|
524
530
|
raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
|
525
531
|
'but the block does not accept any argument.' if block.arity <= 0
|
526
532
|
|
527
|
-
lambda
|
533
|
+
lambda do |f|
|
534
|
+
contents = f.inputs(*args){ block.call(f) }
|
535
|
+
template.concat(contents) if ::Formtastic::Util.rails3?
|
536
|
+
contents
|
537
|
+
end
|
528
538
|
else
|
529
|
-
lambda
|
539
|
+
lambda do |f|
|
540
|
+
contents = f.inputs(*args)
|
541
|
+
template.concat(contents) if ::Formtastic::Util.rails3?
|
542
|
+
contents
|
543
|
+
end
|
530
544
|
end
|
531
545
|
|
532
546
|
fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
|
@@ -546,12 +560,13 @@ module Formtastic #:nodoc:
|
|
546
560
|
# returned immediately, allowing the view to override any guesswork that follows:
|
547
561
|
#
|
548
562
|
# * if the :required option isn't provided in the options hash, and the ValidationReflection
|
549
|
-
# plugin is installed (http://github.com/redinger/validation_reflection),
|
563
|
+
# plugin is installed (http://github.com/redinger/validation_reflection), or the object is
|
564
|
+
# an ActiveModel, true is returned
|
550
565
|
# if the validates_presence_of macro has been used in the class for this attribute, or false
|
551
566
|
# otherwise.
|
552
567
|
#
|
553
|
-
# * if the :required option isn't provided, and
|
554
|
-
# configuration option
|
568
|
+
# * if the :required option isn't provided, and validates_presence_of can't be determined, the
|
569
|
+
# configuration option all_fields_required_by_default is used.
|
555
570
|
#
|
556
571
|
def method_required?(attribute) #:nodoc:
|
557
572
|
if @object && @object.class.respond_to?(:reflect_on_validations_for)
|
@@ -563,7 +578,12 @@ module Formtastic #:nodoc:
|
|
563
578
|
(validation.options.present? ? options_require_validation?(validation.options) : true)
|
564
579
|
end
|
565
580
|
else
|
566
|
-
|
581
|
+
if @object && @object.class.respond_to?(:validators_on)
|
582
|
+
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
|
583
|
+
!@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil?
|
584
|
+
else
|
585
|
+
self.class.all_fields_required_by_default
|
586
|
+
end
|
567
587
|
end
|
568
588
|
end
|
569
589
|
|
@@ -1115,6 +1135,15 @@ module Formtastic #:nodoc:
|
|
1115
1135
|
# f.input :authors, :as => :check_boxes, :selected => Author.most_popular.collect(&:id)
|
1116
1136
|
# f.input :authors, :as => :check_boxes, :selected => nil # override any defaults: select none
|
1117
1137
|
#
|
1138
|
+
#
|
1139
|
+
# Formtastic works around a bug in rails handling of check box collections by
|
1140
|
+
# not generating the hidden fields for state checking of the checkboxes
|
1141
|
+
# The :hidden_fields option provides a way to re-enable these hidden inputs by
|
1142
|
+
# setting it to true.
|
1143
|
+
#
|
1144
|
+
# f.input :authors, :as => :check_boxes, :hidden_fields => false
|
1145
|
+
# f.input :authors, :as => :check_boxes, :hidden_fields => true
|
1146
|
+
#
|
1118
1147
|
# Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
|
1119
1148
|
# combination to contain a class with the value of the radio button (useful for applying specific
|
1120
1149
|
# CSS or Javascript to a particular checkbox).
|
@@ -1124,30 +1153,30 @@ module Formtastic #:nodoc:
|
|
1124
1153
|
html_options = options.delete(:input_html) || {}
|
1125
1154
|
|
1126
1155
|
input_name = generate_association_input_name(method)
|
1156
|
+
hidden_fields = options.delete(:hidden_fields)
|
1127
1157
|
value_as_class = options.delete(:value_as_class)
|
1128
1158
|
unchecked_value = options.delete(:unchecked_value) || ''
|
1129
1159
|
html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
|
1130
1160
|
input_ids = []
|
1131
1161
|
|
1132
|
-
|
1133
|
-
selected_values = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
|
1134
|
-
selected_values = [*selected_values].compact
|
1135
|
-
|
1162
|
+
selected_values = find_selected_values_for_column(method, options)
|
1136
1163
|
disabled_option_is_present = options.key?(:disabled)
|
1137
1164
|
disabled_values = [*options[:disabled]] if disabled_option_is_present
|
1138
1165
|
|
1166
|
+
li_options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
|
1167
|
+
|
1139
1168
|
list_item_content = collection.map do |c|
|
1140
1169
|
label = c.is_a?(Array) ? c.first : c
|
1141
1170
|
value = c.is_a?(Array) ? c.last : c
|
1142
1171
|
input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
|
1143
1172
|
input_ids << input_id
|
1144
1173
|
|
1145
|
-
html_options[:checked] = selected_values.include?(value)
|
1174
|
+
html_options[:checked] = selected_values.include?(value)
|
1146
1175
|
html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
|
1147
1176
|
html_options[:id] = input_id
|
1148
1177
|
|
1149
1178
|
li_content = template.content_tag(:label,
|
1150
|
-
Formtastic::Util.html_safe("#{self.
|
1179
|
+
Formtastic::Util.html_safe("#{self.create_check_boxes(input_name, html_options, value, unchecked_value, hidden_fields)} #{escape_html_entities(label)}"),
|
1151
1180
|
:for => input_id
|
1152
1181
|
)
|
1153
1182
|
|
@@ -1156,10 +1185,46 @@ module Formtastic #:nodoc:
|
|
1156
1185
|
end
|
1157
1186
|
|
1158
1187
|
fieldset_content = legend_tag(method, options)
|
1188
|
+
fieldset_content << self.create_hidden_field_for_check_boxes(input_name, value_as_class) unless hidden_fields
|
1159
1189
|
fieldset_content << template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
|
1160
1190
|
template.content_tag(:fieldset, fieldset_content)
|
1161
1191
|
end
|
1162
1192
|
|
1193
|
+
# Used by check_boxes input. The selected values will be set either by:
|
1194
|
+
#
|
1195
|
+
# * Explicitly provided through :selected or :checked
|
1196
|
+
# * Values retrieved through an association
|
1197
|
+
#
|
1198
|
+
# If the collection is not a hash or an array of strings, fixnums or symbols,
|
1199
|
+
# we use value_method to retrieve an array with the values
|
1200
|
+
#
|
1201
|
+
def find_selected_values_for_column(method, options)
|
1202
|
+
selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
|
1203
|
+
if selected_option_is_present
|
1204
|
+
selected_values = (options.key?(:checked) ? options[:checked] : options[:selected])
|
1205
|
+
elsif object.respond_to?(method)
|
1206
|
+
collection = [object.send(method)].compact.flatten
|
1207
|
+
label, value = detect_label_and_value_method!(collection, options)
|
1208
|
+
selected_values = collection.map { |o| send_or_call(value, o) }
|
1209
|
+
end
|
1210
|
+
selected_values = [*selected_values].compact
|
1211
|
+
selected_values
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
# Outputs a custom hidden field for check_boxes
|
1215
|
+
def create_hidden_field_for_check_boxes(method, value_as_class) #:nodoc:
|
1216
|
+
options = value_as_class ? { :class => [method.to_s.singularize, 'default'].join('_') } : {}
|
1217
|
+
input_name = "#{object_name}[#{method.to_s}][]"
|
1218
|
+
template.hidden_field_tag(input_name, '', options)
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
# Outputs a checkbox tag. If called with no_hidden_input = true a plain check_box_tag is returned,
|
1222
|
+
# otherwise the helper uses the output generated by the rails check_box method.
|
1223
|
+
def create_check_boxes(input_name, html_options = {}, checked_value = "1", unchecked_value = "0", hidden_fields = false) #:nodoc:
|
1224
|
+
return template.check_box_tag(input_name, checked_value, html_options[:checked], html_options) unless hidden_fields == true
|
1225
|
+
self.check_box(input_name, html_options, checked_value, unchecked_value)
|
1226
|
+
end
|
1227
|
+
|
1163
1228
|
# Outputs a country select input, wrapping around a regular country_select helper.
|
1164
1229
|
# Rails doesn't come with a country_select helper by default any more, so you'll need to install
|
1165
1230
|
# the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
|
@@ -1176,9 +1241,9 @@ module Formtastic #:nodoc:
|
|
1176
1241
|
#
|
1177
1242
|
def country_input(method, options)
|
1178
1243
|
raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless self.respond_to?(:country_select)
|
1179
|
-
|
1244
|
+
|
1180
1245
|
html_options = options.delete(:input_html) || {}
|
1181
|
-
priority_countries = options.delete(:priority_countries) ||
|
1246
|
+
priority_countries = options.delete(:priority_countries) || self.class.priority_countries
|
1182
1247
|
|
1183
1248
|
self.label(method, options_for_label(options)) <<
|
1184
1249
|
self.country_select(method, priority_countries, strip_formtastic_options(options), html_options)
|
@@ -1252,9 +1317,9 @@ module Formtastic #:nodoc:
|
|
1252
1317
|
def required_or_optional_string(required) #:nodoc:
|
1253
1318
|
string_or_proc = case required
|
1254
1319
|
when true
|
1255
|
-
|
1320
|
+
self.class.required_string
|
1256
1321
|
when false
|
1257
|
-
|
1322
|
+
self.class.optional_string
|
1258
1323
|
else
|
1259
1324
|
required
|
1260
1325
|
end
|
@@ -1307,7 +1372,7 @@ module Formtastic #:nodoc:
|
|
1307
1372
|
html_options.except(:builder, :parent)
|
1308
1373
|
)
|
1309
1374
|
|
1310
|
-
template.concat(fieldset) if block_given? &&
|
1375
|
+
template.concat(fieldset) if block_given? && !Formtastic::Util.rails3?
|
1311
1376
|
fieldset
|
1312
1377
|
end
|
1313
1378
|
|
@@ -1380,7 +1445,7 @@ module Formtastic #:nodoc:
|
|
1380
1445
|
return :select if self.reflection_for(method)
|
1381
1446
|
|
1382
1447
|
file = @object.send(method) if @object.respond_to?(method)
|
1383
|
-
return :file if file &&
|
1448
|
+
return :file if file && self.class.file_methods.any? { |m| file.respond_to?(m) }
|
1384
1449
|
end
|
1385
1450
|
|
1386
1451
|
return :select if options.key?(:collection)
|
@@ -1421,10 +1486,15 @@ module Formtastic #:nodoc:
|
|
1421
1486
|
options[:find_options] ||= {}
|
1422
1487
|
|
1423
1488
|
if conditions = reflection.options[:conditions]
|
1424
|
-
|
1489
|
+
if reflection.klass.respond_to?(:merge_conditions)
|
1490
|
+
options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
|
1491
|
+
reflection.klass.all(options[:find_options])
|
1492
|
+
else
|
1493
|
+
reflection.klass.where(conditions).where(options[:find_options][:conditions])
|
1494
|
+
end
|
1495
|
+
else
|
1496
|
+
reflection.klass.all(options[:find_options])
|
1425
1497
|
end
|
1426
|
-
|
1427
|
-
reflection.klass.find(:all, options[:find_options])
|
1428
1498
|
else
|
1429
1499
|
create_boolean_collection(options)
|
1430
1500
|
end
|
@@ -1433,21 +1503,35 @@ module Formtastic #:nodoc:
|
|
1433
1503
|
collection
|
1434
1504
|
end
|
1435
1505
|
|
1436
|
-
# Detects the label and value methods from a collection
|
1437
|
-
#
|
1438
|
-
#
|
1506
|
+
# Detects the label and value methods from a collection using methods set in
|
1507
|
+
# collection_label_methods and collection_value_methods. For some ruby core
|
1508
|
+
# classes sensible defaults have been defined. It will use and delete the options
|
1509
|
+
# :label_method and :value_methods when present.
|
1439
1510
|
#
|
1440
|
-
def detect_label_and_value_method!(
|
1441
|
-
|
1442
|
-
|
1511
|
+
def detect_label_and_value_method!(collection, options = {})
|
1512
|
+
sample = collection.first || collection.last
|
1513
|
+
|
1514
|
+
case sample
|
1515
|
+
when Array
|
1516
|
+
label, value = :first, :last
|
1517
|
+
when Integer
|
1518
|
+
label, value = :to_s, :to_i
|
1519
|
+
when String, NilClass
|
1520
|
+
label, value = :to_s, :to_s
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
# Order of preference: user supplied method, class defaults, auto-detect
|
1524
|
+
label = options[:label_method] || label || self.class.collection_label_methods.find { |m| sample.respond_to?(m) }
|
1525
|
+
value = options[:value_method] || value || self.class.collection_value_methods.find { |m| sample.respond_to?(m) }
|
1526
|
+
|
1443
1527
|
[label, value]
|
1444
1528
|
end
|
1445
1529
|
|
1446
|
-
#
|
1447
|
-
# values set in
|
1530
|
+
# Return the label collection method when none is supplied using the
|
1531
|
+
# values set in collection_label_methods.
|
1448
1532
|
#
|
1449
1533
|
def detect_label_method(collection) #:nodoc:
|
1450
|
-
|
1534
|
+
detect_label_and_value_method!(collection).first
|
1451
1535
|
end
|
1452
1536
|
|
1453
1537
|
# Detects the method to call for fetching group members from the groups when grouping select options
|
@@ -1542,12 +1626,12 @@ module Formtastic #:nodoc:
|
|
1542
1626
|
column = self.column_for(method)
|
1543
1627
|
|
1544
1628
|
if type == :text
|
1545
|
-
{ :cols =>
|
1629
|
+
{ :cols => self.class.default_text_field_size, :rows => self.class.default_text_area_height }
|
1546
1630
|
elsif type == :numeric || column.nil? || column.limit.nil?
|
1547
|
-
{ :size =>
|
1631
|
+
{ :size => self.class.default_text_field_size }
|
1548
1632
|
else
|
1549
|
-
{ :maxlength => column.limit,
|
1550
|
-
:size =>
|
1633
|
+
{ :maxlength => column.limit,
|
1634
|
+
:size => self.class.default_text_field_size && [column.limit, self.class.default_text_field_size].min }
|
1551
1635
|
end
|
1552
1636
|
end
|
1553
1637
|
|
@@ -1593,27 +1677,27 @@ module Formtastic #:nodoc:
|
|
1593
1677
|
if @object && @object.class.respond_to?(:human_attribute_name)
|
1594
1678
|
humanized_name = @object.class.human_attribute_name(method.to_s)
|
1595
1679
|
if humanized_name == method.to_s.send(:humanize)
|
1596
|
-
method.to_s.send(
|
1680
|
+
method.to_s.send(self.class.label_str_method)
|
1597
1681
|
else
|
1598
1682
|
humanized_name
|
1599
1683
|
end
|
1600
1684
|
else
|
1601
|
-
method.to_s.send(
|
1685
|
+
method.to_s.send(self.class.label_str_method)
|
1602
1686
|
end
|
1603
1687
|
end
|
1604
1688
|
|
1605
1689
|
# Internal generic method for looking up localized values within Formtastic
|
1606
1690
|
# using I18n, if no explicit value is set and I18n-lookups are enabled.
|
1607
|
-
#
|
1691
|
+
#
|
1608
1692
|
# Enabled/Disable this by setting:
|
1609
1693
|
#
|
1610
1694
|
# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
|
1611
1695
|
#
|
1612
1696
|
# Lookup priority:
|
1613
1697
|
#
|
1614
|
-
# 'formtastic
|
1615
|
-
# 'formtastic
|
1616
|
-
# 'formtastic
|
1698
|
+
# 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
|
1699
|
+
# 'formtastic.%{type}.%{model}.%{attribute}'
|
1700
|
+
# 'formtastic.%{type}.%{attribute}'
|
1617
1701
|
#
|
1618
1702
|
# Example:
|
1619
1703
|
#
|
@@ -1629,7 +1713,7 @@ module Formtastic #:nodoc:
|
|
1629
1713
|
if value.is_a?(::String)
|
1630
1714
|
escape_html_entities(value)
|
1631
1715
|
else
|
1632
|
-
use_i18n = value.nil? ?
|
1716
|
+
use_i18n = value.nil? ? self.class.i18n_lookups_by_default : (value != false)
|
1633
1717
|
|
1634
1718
|
if use_i18n
|
1635
1719
|
model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
|
@@ -1638,10 +1722,10 @@ module Formtastic #:nodoc:
|
|
1638
1722
|
|
1639
1723
|
defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
|
1640
1724
|
i18n_path = i18n_scope.dup
|
1641
|
-
i18n_path.gsub!('{
|
1642
|
-
i18n_path.gsub!('{
|
1643
|
-
i18n_path.gsub!('{
|
1644
|
-
i18n_path.gsub!('{
|
1725
|
+
i18n_path.gsub!('%{action}', action_name)
|
1726
|
+
i18n_path.gsub!('%{model}', model_name)
|
1727
|
+
i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
|
1728
|
+
i18n_path.gsub!('%{attribute}', attribute_name)
|
1645
1729
|
i18n_path.gsub!('..', '.')
|
1646
1730
|
i18n_path.to_sym
|
1647
1731
|
end
|
@@ -1677,13 +1761,13 @@ module Formtastic #:nodoc:
|
|
1677
1761
|
|
1678
1762
|
def set_include_blank(options)
|
1679
1763
|
unless options.key?(:include_blank) || options.key?(:prompt)
|
1680
|
-
options[:include_blank] =
|
1764
|
+
options[:include_blank] = self.class.include_blank_for_select_by_default
|
1681
1765
|
end
|
1682
1766
|
options
|
1683
1767
|
end
|
1684
1768
|
|
1685
1769
|
def escape_html_entities(string) #:nodoc:
|
1686
|
-
if
|
1770
|
+
if self.class.escape_html_entities_in_hints_and_labels
|
1687
1771
|
# Acceppt html_safe flag as indicator to skip escaping
|
1688
1772
|
string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true
|
1689
1773
|
end
|
@@ -1743,7 +1827,17 @@ module Formtastic #:nodoc:
|
|
1743
1827
|
ensure
|
1744
1828
|
::ActionView::Base.field_error_proc = default_field_error_proc
|
1745
1829
|
end
|
1746
|
-
|
1830
|
+
|
1831
|
+
def semantic_remote_form_for_wrapper(record_or_name_or_array, *args, &proc)
|
1832
|
+
options = args.extract_options!
|
1833
|
+
if self.respond_to? :remote_form_for
|
1834
|
+
semantic_remote_form_for_real(record_or_name_or_array, *(args << options), &proc)
|
1835
|
+
else
|
1836
|
+
options[:remote] = true
|
1837
|
+
semantic_form_for(record_or_name_or_array, *(args << options), &proc)
|
1838
|
+
end
|
1839
|
+
end
|
1840
|
+
|
1747
1841
|
[:form_for, :fields_for, :remote_form_for].each do |meth|
|
1748
1842
|
module_eval <<-END_SRC, __FILE__, __LINE__ + 1
|
1749
1843
|
def semantic_#{meth}(record_or_name_or_array, *args, &proc)
|
@@ -1751,12 +1845,14 @@ module Formtastic #:nodoc:
|
|
1751
1845
|
options[:builder] ||= @@builder
|
1752
1846
|
options[:html] ||= {}
|
1753
1847
|
|
1848
|
+
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
1849
|
+
|
1754
1850
|
class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
|
1755
1851
|
class_names << "formtastic"
|
1756
1852
|
class_names << case record_or_name_or_array
|
1757
1853
|
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
1758
|
-
when Array then
|
1759
|
-
else
|
1854
|
+
when Array then singularizer.call(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
|
1855
|
+
else singularizer.call(record_or_name_or_array.class) # @post => "post"
|
1760
1856
|
end
|
1761
1857
|
options[:html][:class] = class_names.join(" ")
|
1762
1858
|
|
@@ -1766,6 +1862,8 @@ module Formtastic #:nodoc:
|
|
1766
1862
|
end
|
1767
1863
|
END_SRC
|
1768
1864
|
end
|
1865
|
+
alias :semantic_remote_form_for_real :semantic_remote_form_for
|
1866
|
+
alias :semantic_remote_form_for :semantic_remote_form_for_wrapper
|
1769
1867
|
alias :semantic_form_remote_for :semantic_remote_form_for
|
1770
1868
|
|
1771
1869
|
end
|