formtastic 1.0.1 → 1.1.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +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
|