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.
Files changed (48) hide show
  1. data/README.textile +2 -2
  2. data/Rakefile +33 -8
  3. data/generators/formtastic/templates/formtastic.css +3 -18
  4. data/init.rb +5 -0
  5. data/lib/formtastic.rb +180 -82
  6. data/lib/formtastic/i18n.rb +8 -9
  7. data/lib/formtastic/layout_helper.rb +0 -1
  8. data/lib/formtastic/railtie.rb +12 -0
  9. data/lib/formtastic/util.rb +7 -0
  10. data/lib/generators/formtastic/form/form_generator.rb +86 -0
  11. data/lib/generators/formtastic/install/install_generator.rb +24 -0
  12. data/rails/init.rb +1 -6
  13. data/spec/buttons_spec.rb +25 -8
  14. data/spec/commit_button_spec.rb +67 -29
  15. data/spec/custom_builder_spec.rb +40 -4
  16. data/spec/defaults_spec.rb +1 -1
  17. data/spec/error_proc_spec.rb +1 -1
  18. data/spec/errors_spec.rb +3 -2
  19. data/spec/form_helper_spec.rb +33 -17
  20. data/spec/helpers/layout_helper_spec.rb +21 -0
  21. data/spec/i18n_spec.rb +13 -9
  22. data/spec/include_blank_spec.rb +9 -5
  23. data/spec/input_spec.rb +188 -48
  24. data/spec/inputs/boolean_input_spec.rb +14 -7
  25. data/spec/inputs/check_boxes_input_spec.rb +150 -20
  26. data/spec/inputs/country_input_spec.rb +9 -5
  27. data/spec/inputs/date_input_spec.rb +21 -9
  28. data/spec/inputs/datetime_input_spec.rb +33 -14
  29. data/spec/inputs/file_input_spec.rb +4 -3
  30. data/spec/inputs/hidden_input_spec.rb +11 -4
  31. data/spec/inputs/numeric_input_spec.rb +3 -3
  32. data/spec/inputs/password_input_spec.rb +3 -3
  33. data/spec/inputs/radio_input_spec.rb +29 -10
  34. data/spec/inputs/select_input_spec.rb +66 -26
  35. data/spec/inputs/string_input_spec.rb +5 -4
  36. data/spec/inputs/text_input_spec.rb +4 -3
  37. data/spec/inputs/time_input_spec.rb +26 -10
  38. data/spec/inputs/time_zone_input_spec.rb +11 -5
  39. data/spec/inputs_spec.rb +103 -39
  40. data/spec/label_spec.rb +5 -3
  41. data/spec/semantic_errors_spec.rb +7 -7
  42. data/spec/semantic_fields_for_spec.rb +5 -4
  43. data/spec/spec_helper.rb +86 -38
  44. data/spec/{custom_macros.rb → support/custom_macros.rb} +69 -35
  45. data/spec/support/output_buffer.rb +4 -0
  46. data/spec/support/test_environment.rb +45 -0
  47. metadata +26 -28
  48. 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 {{model}}"
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 {{model}}"
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
- gem 'rspec-rails', '>= 1.0.0'
10
- require 'spec/rake/spectask'
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
- ./script/generate formtastic
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
- ./script/generate form MODEL_NAME
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', '< 3.0.0'
57
- s.add_dependency 'actionpack', '>= 2.3.0', '< 3.0.0'
58
- s.add_dependency 'i18n', '< 0.4'
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
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+ require 'formtastic'
3
+ require 'formtastic/layout_helper'
4
+ ActionView::Base.send :include, Formtastic::SemanticFormHelper
5
+ ActionView::Base.send :include, Formtastic::LayoutHelper
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
- @@default_text_field_size = 50
10
- @@default_text_area_height = 20
11
- @@all_fields_required_by_default = true
12
- @@include_blank_for_select_by_default = true
13
- @@required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
14
- @@optional_string = ''
15
- @@inline_errors = :sentence
16
- @@label_str_method = :humanize
17
- @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
18
- @@inline_order = [ :input, :hints, :errors ]
19
- @@file_methods = [ :file?, :public_filename, :filename ]
20
- @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
21
- @@i18n_lookups_by_default = false
22
- @@escape_html_entities_in_hints_and_labels = true
23
- @@default_commit_button_accesskey = nil
24
-
25
- cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
26
- :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
27
- :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels, :default_commit_button_accesskey
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 = @@inline_order.dup
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
- object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
331
- crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
332
- decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
333
- object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
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(@@label_str_method)
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) || @@default_commit_button_accesskey) unless button_html.has_key?(: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] ||= Formtastic::SemanticFormHelper.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_#{@@inline_errors}", [*errors]) if errors.any?
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.on_base
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?(@@inline_errors)
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 { |f| f.inputs(*args){ block.call(f) } }
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 { |f| f.inputs(*args) }
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), true is returned
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 the plugin isn't available, the value of the
554
- # configuration option @@all_fields_required_by_default is used.
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
- @@all_fields_required_by_default
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
- selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
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) if selected_option_is_present
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.check_box(input_name, html_options, value, unchecked_value)} #{escape_html_entities(label)}"),
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) || @@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
- @@required_string
1320
+ self.class.required_string
1256
1321
  when false
1257
- @@optional_string
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? && (!defined?(Rails::VERSION) || Rails::VERSION::MAJOR == 2)
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 && @@file_methods.any? { |m| file.respond_to?(m) }
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
- options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
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 values set in
1437
- # @@collection_label_methods. It will use and delete
1438
- # the options :label_method and :value_methods when present
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!(collection_or_instance, options = {}) #:nodoc
1441
- label = options.delete(:label_method) || detect_label_method(collection_or_instance)
1442
- value = options.delete(:value_method) || :id
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
- # Detected the label collection method when none is supplied using the
1447
- # values set in @@collection_label_methods.
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
- @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
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 => @@default_text_field_size, :rows => @@default_text_area_height }
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 => @@default_text_field_size }
1631
+ { :size => self.class.default_text_field_size }
1548
1632
  else
1549
- { :maxlength => column.limit,
1550
- :size => @@default_text_field_size && [column.limit, @@default_text_field_size].min }
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(@@label_str_method)
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(@@label_str_method)
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.{{type}}.{{model}}.{{action}}.{{attribute}}'
1615
- # 'formtastic.{{type}}.{{model}}.{{attribute}}'
1616
- # 'formtastic.{{type}}.{{attribute}}'
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? ? @@i18n_lookups_by_default : (value != false)
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!('{{action}}', action_name)
1642
- i18n_path.gsub!('{{model}}', model_name)
1643
- i18n_path.gsub!('{{nested_model}}', nested_model_name) unless nested_model_name.nil?
1644
- i18n_path.gsub!('{{attribute}}', attribute_name)
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] = @@include_blank_for_select_by_default
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 @@escape_html_entities_in_hints_and_labels
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 ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
1759
- else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class) # @post => "post"
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