nuatt-formtastic 0.2.2 → 0.2.3

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 (68) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +635 -0
  3. data/lib/formtastic.rb +24 -0
  4. data/lib/formtastic/form_builder.rb +75 -0
  5. data/lib/formtastic/helpers.rb +15 -0
  6. data/lib/formtastic/helpers/buttons_helper.rb +277 -0
  7. data/lib/formtastic/helpers/errors_helper.rb +124 -0
  8. data/lib/formtastic/helpers/fieldset_wrapper.rb +62 -0
  9. data/lib/formtastic/helpers/file_column_detection.rb +16 -0
  10. data/lib/formtastic/helpers/form_helper.rb +221 -0
  11. data/lib/formtastic/helpers/input_helper.rb +357 -0
  12. data/lib/formtastic/helpers/inputs_helper.rb +381 -0
  13. data/lib/formtastic/helpers/reflection.rb +12 -0
  14. data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
  15. data/lib/formtastic/html_attributes.rb +21 -0
  16. data/lib/formtastic/i18n.rb +32 -0
  17. data/lib/formtastic/inputs.rb +29 -0
  18. data/lib/formtastic/inputs/base.rb +50 -0
  19. data/lib/formtastic/inputs/base/associations.rb +33 -0
  20. data/lib/formtastic/inputs/base/choices.rb +88 -0
  21. data/lib/formtastic/inputs/base/collections.rb +94 -0
  22. data/lib/formtastic/inputs/base/database.rb +17 -0
  23. data/lib/formtastic/inputs/base/errors.rb +58 -0
  24. data/lib/formtastic/inputs/base/fileish.rb +23 -0
  25. data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
  26. data/lib/formtastic/inputs/base/hints.rb +31 -0
  27. data/lib/formtastic/inputs/base/html.rb +51 -0
  28. data/lib/formtastic/inputs/base/labelling.rb +53 -0
  29. data/lib/formtastic/inputs/base/naming.rb +54 -0
  30. data/lib/formtastic/inputs/base/options.rb +18 -0
  31. data/lib/formtastic/inputs/base/stringish.rb +30 -0
  32. data/lib/formtastic/inputs/base/timeish.rb +125 -0
  33. data/lib/formtastic/inputs/base/validations.rb +125 -0
  34. data/lib/formtastic/inputs/base/wrapping.rb +38 -0
  35. data/lib/formtastic/inputs/boolean_input.rb +87 -0
  36. data/lib/formtastic/inputs/check_boxes_input.rb +169 -0
  37. data/lib/formtastic/inputs/country_input.rb +66 -0
  38. data/lib/formtastic/inputs/date_input.rb +14 -0
  39. data/lib/formtastic/inputs/datetime_input.rb +9 -0
  40. data/lib/formtastic/inputs/email_input.rb +40 -0
  41. data/lib/formtastic/inputs/file_input.rb +42 -0
  42. data/lib/formtastic/inputs/hidden_input.rb +66 -0
  43. data/lib/formtastic/inputs/number_input.rb +72 -0
  44. data/lib/formtastic/inputs/numeric_input.rb +20 -0
  45. data/lib/formtastic/inputs/password_input.rb +40 -0
  46. data/lib/formtastic/inputs/phone_input.rb +41 -0
  47. data/lib/formtastic/inputs/radio_input.rb +146 -0
  48. data/lib/formtastic/inputs/search_input.rb +40 -0
  49. data/lib/formtastic/inputs/select_input.rb +208 -0
  50. data/lib/formtastic/inputs/string_input.rb +34 -0
  51. data/lib/formtastic/inputs/text_input.rb +47 -0
  52. data/lib/formtastic/inputs/time_input.rb +14 -0
  53. data/lib/formtastic/inputs/time_zone_input.rb +48 -0
  54. data/lib/formtastic/inputs/url_input.rb +40 -0
  55. data/lib/formtastic/localized_string.rb +96 -0
  56. data/lib/formtastic/railtie.rb +12 -0
  57. data/lib/formtastic/semantic_form_builder.rb +11 -0
  58. data/lib/formtastic/util.rb +25 -0
  59. data/lib/generators/formtastic/form/form_generator.rb +95 -0
  60. data/lib/generators/formtastic/install/install_generator.rb +23 -0
  61. data/lib/generators/templates/_form.html.erb +7 -0
  62. data/lib/generators/templates/_form.html.haml +5 -0
  63. data/lib/generators/templates/formtastic.css +145 -0
  64. data/lib/generators/templates/formtastic.rb +74 -0
  65. data/lib/generators/templates/formtastic_changes.css +14 -0
  66. data/lib/locale/en.yml +7 -0
  67. data/lib/tasks/verify_rcov.rb +44 -0
  68. metadata +206 -19
@@ -0,0 +1,29 @@
1
+ module Formtastic
2
+ module Inputs
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Base
6
+ autoload :Basic
7
+ autoload :BooleanInput
8
+ autoload :CheckBoxesInput
9
+ autoload :CountryInput
10
+ autoload :DateInput
11
+ autoload :DatetimeInput
12
+ autoload :EmailInput
13
+ autoload :FileInput
14
+ autoload :HiddenInput
15
+ autoload :NumberInput
16
+ autoload :NumericInput
17
+ autoload :PasswordInput
18
+ autoload :PhoneInput
19
+ autoload :RadioInput
20
+ autoload :SearchInput
21
+ autoload :SelectInput
22
+ autoload :StringInput
23
+ autoload :TextInput
24
+ autoload :TimeInput
25
+ autoload :TimeZoneInput
26
+ autoload :Timeish
27
+ autoload :UrlInput
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+
5
+ attr_accessor :builder, :template, :object, :object_name, :method, :options
6
+
7
+ def initialize(builder, template, object, object_name, method, options)
8
+ @builder = builder
9
+ @template = template
10
+ @object = object
11
+ @object_name = object_name
12
+ @method = method
13
+ @options = options.dup
14
+ end
15
+
16
+ extend ActiveSupport::Autoload
17
+
18
+ autoload :Associations
19
+ autoload :Collections
20
+ autoload :Choices
21
+ autoload :Database
22
+ autoload :Errors
23
+ autoload :Fileish
24
+ autoload :GroupedCollections
25
+ autoload :Hints
26
+ autoload :Html
27
+ autoload :Labelling
28
+ autoload :Naming
29
+ autoload :Options
30
+ autoload :Stringish
31
+ autoload :Timeish
32
+ autoload :Validations
33
+ autoload :Wrapping
34
+
35
+ include Html
36
+ include Options
37
+ include Database
38
+ include Errors
39
+ include Hints
40
+ include Naming
41
+ include Validations
42
+ include Fileish
43
+ include Associations
44
+ include Labelling
45
+ include Wrapping
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,33 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Associations
5
+
6
+ # :belongs_to, etc
7
+ def association
8
+ @association ||= reflection.macro if reflection
9
+ end
10
+
11
+ def reflection
12
+ @reflection ||= object.class.reflect_on_association(method) if object.class.respond_to?(:reflect_on_association)
13
+ end
14
+
15
+ def belongs_to?
16
+ association == :belongs_to
17
+ end
18
+
19
+ def association_primary_key
20
+ if association
21
+ return reflection.options[:foreign_key] unless reflection.options[:foreign_key].blank?
22
+ return reflection.foreign_key if reflection.respond_to?(:foreign_key)
23
+ return :"#{method}_id" if belongs_to?
24
+ return "#{method.to_s.singularize}_id".pluralize.to_sym
25
+ else
26
+ return method.to_s
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Choices
5
+
6
+ def choices_wrapping(&block)
7
+ template.content_tag(:fieldset,
8
+ template.capture(&block),
9
+ choices_wrapping_html_options
10
+ )
11
+ end
12
+
13
+ def choices_wrapping_html_options
14
+ {}
15
+ end
16
+
17
+ def choices_group_wrapping(&block)
18
+ template.content_tag(:ol,
19
+ template.capture(&block),
20
+ choices_group_wrapping_html_options
21
+ )
22
+ end
23
+
24
+ def choices_group_wrapping_html_options
25
+ {}
26
+ end
27
+
28
+ def choice_wrapping(html_options, &block)
29
+ template.content_tag(:li,
30
+ template.capture(&block),
31
+ html_options
32
+ )
33
+ end
34
+
35
+ def choice_wrapping_html_options(choice)
36
+ { :class => value_as_class? ? "#{sanitized_method_name.singularize}_#{choice_html_safe_value(choice)}" : '' }
37
+ end
38
+
39
+ def choice_html(choice)
40
+ raise "choice_html() needs to be implemented when including Formtastic::Inputs::Base::Choices"
41
+ end
42
+
43
+ def choice_label(choice)
44
+ choice.is_a?(Array) ? choice.first : choice
45
+ end
46
+
47
+ def choice_value(choice)
48
+ choice.is_a?(Array) ? choice.last : choice
49
+ end
50
+
51
+ def choice_html_safe_value(choice)
52
+ choice_value(choice).to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase
53
+ end
54
+
55
+ def choice_input_dom_id(choice)
56
+ [
57
+ builder.custom_namespace,
58
+ sanitized_object_name,
59
+ association_primary_key || method,
60
+ choice_html_safe_value(choice)
61
+ ].compact.reject { |i| i.blank? }.join("_")
62
+ end
63
+
64
+ def value_as_class?
65
+ options[:value_as_class]
66
+ end
67
+
68
+ def legend_html
69
+ if render_label?
70
+ template.content_tag(:legend,
71
+ template.content_tag(:label, label_text),
72
+ label_html_options.merge(:class => "label")
73
+ )
74
+ else
75
+ "".html_safe
76
+ end
77
+ end
78
+
79
+ # Override to remove the for attribute since this isn't associated with any element, as it's
80
+ # nested inside the legend.
81
+ def label_html_options
82
+ super.merge(:for => nil)
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,94 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Collections
5
+
6
+ def label_method
7
+ label_and_value_method(raw_collection).first
8
+ end
9
+
10
+ def value_method
11
+ label_and_value_method(raw_collection).last
12
+ end
13
+
14
+ def label_and_value_method(_collection, grouped=false)
15
+ sample = _collection.first || _collection.last
16
+
17
+ case sample
18
+ when Array
19
+ label, value = :first, :last
20
+ when Integer
21
+ label, value = :to_s, :to_i
22
+ when String, NilClass
23
+ label, value = :to_s, :to_s
24
+ end
25
+
26
+ # Order of preference: user supplied method, class defaults, auto-detect
27
+ label = (grouped ? options[:grouped_label_method] : options[:label_method]) || label || builder.collection_label_methods.find { |m| sample.respond_to?(m) }
28
+ value = (grouped ? options[:grouped_value_method] : options[:value_method]) || value || builder.collection_value_methods.find { |m| sample.respond_to?(m) }
29
+
30
+ [label, value]
31
+ end
32
+
33
+ def raw_collection
34
+ @raw_collection ||= (collection_from_options || collection_from_association || collection_for_boolean)
35
+ end
36
+
37
+ def collection
38
+ # Return if we have a plain string
39
+ return raw_collection if raw_collection.instance_of?(String) || raw_collection.instance_of?(ActiveSupport::SafeBuffer)
40
+
41
+ # Return if we have an Array of strings, fixnums or arrays
42
+ return raw_collection if (raw_collection.instance_of?(Array) || raw_collection.instance_of?(Range)) &&
43
+ [Array, Fixnum, String, Symbol].include?(raw_collection.first.class) &&
44
+ !(options.include?(:label_method) || options.include?(:value_method))
45
+
46
+ raw_collection.map { |o| [send_or_call(label_method, o), send_or_call(value_method, o)] }
47
+ end
48
+
49
+ def collection_from_options
50
+ items = options[:collection]
51
+ items = items.to_a if items.is_a?(Hash)
52
+ items
53
+ end
54
+
55
+ def collection_from_association
56
+ if reflection
57
+ raise PolymorphicInputWithoutCollectionError.new("A collection must be supplied for #{method} input. Collections cannot be guessed for polymorphic associations.") if reflection.options[:polymorphic] == true
58
+
59
+ find_options_from_options = options[:find_options] || {}
60
+ conditions_from_options = find_options_from_options[:conditions] || {}
61
+ conditions_from_reflection = reflection.options[:conditions] || {}
62
+
63
+ if conditions_from_options.any?
64
+ reflection.klass.where(
65
+ conditions_from_reflection.merge(conditions_from_options)
66
+ )
67
+ else
68
+ find_options_from_options.merge!(:include => group_by) if self.respond_to?(:group_by) && group_by
69
+ reflection.klass.where(conditions_from_reflection.merge(find_options_from_options))
70
+ end
71
+ end
72
+ end
73
+
74
+ def collection_for_boolean
75
+ true_text = options[:true] || Formtastic::I18n.t(:yes)
76
+ false_text = options[:false] || Formtastic::I18n.t(:no)
77
+
78
+ # TODO options[:value_as_class] = true unless options.key?(:value_as_class)
79
+
80
+ [ [true_text, true], [false_text, false] ]
81
+ end
82
+
83
+ def send_or_call(duck, object)
84
+ if duck.is_a?(Proc)
85
+ duck.call(object)
86
+ else
87
+ object.send(duck)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,17 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Database
5
+
6
+ def column
7
+ object.column_for_attribute(method) if object.respond_to?(:column_for_attribute)
8
+ end
9
+
10
+ def column?
11
+ !column.nil?
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Errors
5
+
6
+ def error_html
7
+ errors? ? send(:"error_#{builder.inline_errors}_html") : ""
8
+ end
9
+
10
+ def error_sentence_html
11
+ error_class = options[:error_class] || builder.default_inline_error_class
12
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.html_safe), :class => error_class)
13
+ end
14
+
15
+ def error_list_html
16
+ error_class = options[:error_class] || builder.default_error_list_class
17
+ list_elements = []
18
+ errors.each do |error|
19
+ list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.html_safe))
20
+ end
21
+ template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => error_class)
22
+ end
23
+
24
+ def error_first_html
25
+ error_class = options[:error_class] || builder.default_inline_error_class
26
+ template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => error_class)
27
+ end
28
+
29
+ def error_none_html
30
+ ""
31
+ end
32
+
33
+ def errors?
34
+ !errors.blank?
35
+ end
36
+
37
+ def errors
38
+ errors = []
39
+ if object && object.respond_to?(:errors)
40
+ error_keys.each do |key|
41
+ errors << object.errors[key] unless object.errors[key].blank?
42
+ end
43
+ end
44
+ errors.flatten.compact.uniq
45
+ end
46
+
47
+ def error_keys
48
+ keys = [method.to_sym]
49
+ keys << builder.file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if file?
50
+ keys << association_primary_key if belongs_to?
51
+ keys.flatten.compact.uniq
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,23 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Fileish
5
+
6
+ def file?
7
+ @file ||= begin
8
+ # TODO return true if self.is_a?(Formtastic::Inputs::FileInput::Woo)
9
+ object && object.respond_to?(method) && builder.file_methods.any? { |m| object.send(method).respond_to?(m) }
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+
20
+
21
+
22
+
23
+
@@ -0,0 +1,77 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module GroupedCollections
5
+
6
+ def raw_grouped_collection
7
+ @raw_grouped_collection ||= raw_collection.map { |option| option.send(options[:group_by]) }.uniq
8
+ end
9
+
10
+ def grouped_collection
11
+ @grouped_collection ||= raw_grouped_collection.sort_by { |group_item| group_item.send(group_label_method) }
12
+ end
13
+
14
+ def group_label_method
15
+ @group_label_method ||= (group_label_method_from_options || group_label_method_from_grouped_collection)
16
+ end
17
+
18
+ def group_label_method_from_options
19
+ options[:group_label_method]
20
+ end
21
+
22
+ def group_label_method_from_grouped_collection
23
+ label_and_value_method(raw_grouped_collection, true).first
24
+ end
25
+
26
+ def group_association
27
+ @group_association ||= (group_association_from_options || group_association_from_reflection)
28
+ end
29
+
30
+ def group_association_from_options
31
+ options[:group_association]
32
+ end
33
+
34
+ def group_by
35
+ options[:group_by]
36
+ end
37
+
38
+ def group_association_from_reflection
39
+ method_to_group_association_by = reflection.klass.reflect_on_association(group_by)
40
+ group_class = method_to_group_association_by.klass
41
+
42
+ # This will return in the normal case
43
+ return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
44
+
45
+ # This is for belongs_to associations named differently than their class
46
+ # form.input :parent, :group_by => :customer
47
+ # eg.
48
+ # class Project
49
+ # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
50
+ # belongs_to :customer
51
+ # end
52
+ # class Customer
53
+ # has_many :projects
54
+ # end
55
+ group_method = group_class.to_s.underscore.pluralize.to_sym
56
+ return group_method if group_class.reflect_on_association(group_method) # :projects
57
+
58
+ # This is for has_many associations named differently than their class
59
+ # eg.
60
+ # class Project
61
+ # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
62
+ # belongs_to :customer
63
+ # end
64
+ # class Customer
65
+ # has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
66
+ # end
67
+ possible_associations = group_class.reflect_on_all_associations(:has_many).find_all {|assoc| assoc.klass == reflection.klass }
68
+ return possible_associations.first.name.to_sym if possible_associations.count == 1
69
+
70
+ raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end
77
+