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.
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