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,31 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Hints
5
+
6
+ def hint_html
7
+ if hint?
8
+ template.content_tag(
9
+ :p,
10
+ Formtastic::Util.html_safe(hint_text),
11
+ :class => (options[:hint_class] || builder.default_hint_class)
12
+ )
13
+ end
14
+ end
15
+
16
+ def hint?
17
+ !hint_text.blank? && !hint_text.kind_of?(Hash)
18
+ end
19
+
20
+ def hint_text
21
+ localized_string(method, options[:hint], :hint)
22
+ end
23
+
24
+ def hint_text_from_options
25
+ options[:hint]
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Html
5
+
6
+ # Defines how the instance of an input should be rendered to a HTML string.
7
+ #
8
+ # @abstract Implement this method in your input class to describe how the input should render itself.
9
+ #
10
+ # @example A basic label and text field input inside a standard wrapping might look like this:
11
+ # def to_html
12
+ # input_wrapping do
13
+ # label_html <<
14
+ # builder.text_field(method, input_html_options)
15
+ # end
16
+ # end
17
+ def to_html
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def input_html_options
22
+ {
23
+ :id => dom_id,
24
+ :required => required?
25
+ }.merge(options[:input_html] || {})
26
+ end
27
+
28
+ def dom_id
29
+ [
30
+ builder.custom_namespace,
31
+ sanitized_object_name,
32
+ dom_index,
33
+ association_primary_key || sanitized_method_name
34
+ ].reject { |x| x.blank? }.join('_')
35
+ end
36
+
37
+ def dom_index
38
+ if builder.options.has_key?(:index)
39
+ builder.options[:index]
40
+ elsif !builder.auto_index.blank?
41
+ # TODO there's no coverage for this case, not sure how to create a scenario for it
42
+ builder.auto_index
43
+ else
44
+ ""
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Labelling
5
+
6
+ include Formtastic::LocalizedString
7
+
8
+ def label_html
9
+ render_label? ? builder.label(input_name, label_text, label_html_options) : "".html_safe
10
+ end
11
+
12
+ def label_html_options
13
+ # opts = options_for_label(options) # TODO
14
+ opts = {}
15
+ opts[:for] ||= input_html_options[:id]
16
+
17
+ opts
18
+ end
19
+
20
+ def label_text
21
+ ((localized_label || humanized_method_name) << requirement_text).html_safe
22
+ end
23
+
24
+ # TODO: why does this need to be memoized in order to make the inputs_spec tests pass?
25
+ def requirement_text_or_proc
26
+ @requirement_text_or_proc ||= required? ? builder.required_string : builder.optional_string
27
+ end
28
+
29
+ def requirement_text
30
+ if requirement_text_or_proc.respond_to?(:call)
31
+ requirement_text_or_proc.call
32
+ else
33
+ requirement_text_or_proc
34
+ end
35
+ end
36
+
37
+ def label_from_options
38
+ options[:label]
39
+ end
40
+
41
+ def localized_label
42
+ localized_string(method, label_from_options || method, :label)
43
+ end
44
+
45
+ def render_label?
46
+ return false if options[:label] == false
47
+ true
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,54 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Naming
5
+
6
+ def as
7
+ self.class.name.split("::").last.underscore.gsub(/_input$/, '')
8
+ end
9
+
10
+ def sanitized_object_name
11
+ object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
12
+ end
13
+
14
+ def sanitized_method_name
15
+ @sanitized_method_name ||= method.to_s.gsub(/[\?\/\-]$/, '')
16
+ end
17
+
18
+ def attributized_method_name
19
+ method.to_s.gsub(/_id$/, '').to_sym
20
+ end
21
+
22
+ def humanized_method_name
23
+ if builder.label_str_method != :humanize
24
+ # Special case where label_str_method should trump the human_attribute_name
25
+ # TODO: is this actually a desired bheavior, or should we ditch label_str_method and
26
+ # rely purely on :human_attribute_name.
27
+ method.to_s.send(builder.label_str_method)
28
+ elsif object && object.class.respond_to?(:human_attribute_name)
29
+ object.class.human_attribute_name(method.to_s).send(builder.label_str_method)
30
+ else
31
+ method.to_s.send(builder.label_str_method)
32
+ end
33
+ end
34
+
35
+ # TODO this seems to overlap or be confused with association_primary_key
36
+ def input_name
37
+ if reflection
38
+ if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
39
+ "#{method.to_s.singularize}_ids"
40
+ elsif reflection.respond_to? :foreign_key
41
+ reflection.foreign_key
42
+ else
43
+ reflection.options[:foreign_key] || "#{method}_id"
44
+ end
45
+ else
46
+ method
47
+ end.to_sym
48
+ end
49
+
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Options
5
+
6
+ def input_options
7
+ options.except(*formtastic_options)
8
+ end
9
+
10
+ def formtastic_options
11
+ [:priority_countries, :priority_zones, :value_method, :label_method, :collection, :required, :label, :as, :hint, :input_html, :label_html, :value_as_class, :find_options, :class]
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,30 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Stringish
5
+
6
+ # @abstract Override this method in your input class to describe how the input should render itself.
7
+ def to_html
8
+ input_wrapping do
9
+ label_html <<
10
+ builder.text_field(method, input_html_options)
11
+ end
12
+ end
13
+
14
+ # Overrides standard `input_html_options` to provide a `maxlength` and `size` attribute.
15
+ def input_html_options
16
+ {
17
+ :maxlength => options[:input_html].try(:[], :maxlength) || limit,
18
+ :size => builder.default_text_field_size,
19
+ :placeholder => placeholder_text
20
+ }.merge(super)
21
+ end
22
+
23
+ def placeholder_text
24
+ localized_string(method, options[:placeholder], :placeholder)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,125 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Timeish
5
+
6
+ def to_html
7
+ input_wrapping do
8
+ fragments_wrapping do
9
+ fragments_label <<
10
+ template.content_tag(:fieldset,
11
+ template.content_tag(:ol,
12
+ fragments.map do |fragment|
13
+ fragment_wrapping do
14
+ fragment_label_html(fragment) <<
15
+ fragment_input_html(fragment)
16
+ end
17
+ end.join.html_safe # TODO is this safe?
18
+ )
19
+ )
20
+ end
21
+ end
22
+ end
23
+
24
+ def fragments
25
+ date_fragments + time_fragments
26
+ end
27
+
28
+ def time_fragments
29
+ options[:include_seconds] ? [:hour, :minute, :second] : [:hour, :minute]
30
+ end
31
+
32
+ def date_fragments
33
+ options[:order] || i18n_date_fragments || default_date_fragments
34
+ end
35
+
36
+ def default_date_fragments
37
+ [:year, :month, :day]
38
+ end
39
+
40
+ def fragment_wrapping(&block)
41
+ template.content_tag(:li, template.capture(&block))
42
+ end
43
+
44
+ def fragment_label(fragment)
45
+ labels_from_options = options[:labels] || {}
46
+ if labels_from_options.key?(fragment)
47
+ labels_from_options[fragment]
48
+ else
49
+ ::I18n.t(fragment.to_s, :default => fragment.to_s.humanize, :scope => [:datetime, :prompts])
50
+ end
51
+ end
52
+
53
+ def fragment_id(fragment)
54
+ "#{input_html_options[:id]}_#{position(fragment)}i"
55
+ end
56
+
57
+ def fragment_name(fragment)
58
+ "#{method}(#{position(fragment)}i)"
59
+ end
60
+
61
+ def fragment_label_html(fragment)
62
+ text = fragment_label(fragment)
63
+ text.blank? ? "" : template.content_tag(:label, text, :for => fragment_id(fragment))
64
+ end
65
+
66
+ def value
67
+ object.send(method) if object && object.respond_to?(method)
68
+ end
69
+
70
+ def fragment_input_html(fragment)
71
+ opts = input_options.merge(:prefix => object_name, :field_name => fragment_name(fragment), :default => value, :include_blank => include_blank?)
72
+ template.send(:"select_#{fragment}", value, opts, input_html_options.merge(:id => fragment_id(fragment)))
73
+ end
74
+
75
+ # TODO extract to BlankOptions or similar -- Select uses similar code
76
+ def include_blank?
77
+ options.key?(:include_blank) ? options[:include_blank] : builder.include_blank_for_select_by_default
78
+ end
79
+
80
+ def positions
81
+ { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
82
+ end
83
+
84
+ def position(fragment)
85
+ positions[fragment]
86
+ end
87
+
88
+ def i18n_date_fragments
89
+ order = ::I18n.t(:order, :scope => [:date])
90
+ order = nil unless order.is_a?(Array)
91
+ order
92
+ end
93
+
94
+ def fragments_wrapping(&block)
95
+ template.content_tag(:fieldset,
96
+ template.capture(&block).html_safe,
97
+ fragments_wrapping_html_options
98
+ )
99
+ end
100
+
101
+ def fragments_wrapping_html_options
102
+ {}
103
+ end
104
+
105
+ def fragments_label
106
+ if render_label?
107
+ template.content_tag(:legend,
108
+ builder.label(method, :for => "#{input_html_options[:id]}_1i"),
109
+ :class => "label"
110
+ )
111
+ else
112
+ ""
113
+ end
114
+ end
115
+
116
+ def fragments_inner_wrapping(&block)
117
+ template.content_tag(:ol,
118
+ template.capture(&block)
119
+ )
120
+ end
121
+
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,125 @@
1
+ module Formtastic
2
+ module Inputs
3
+ module Base
4
+ module Validations
5
+
6
+ def validations
7
+ @validations ||= if object && object.class.respond_to?(:validators_on)
8
+ object.class.validators_on(attributized_method_name).select do |validator|
9
+ validator_relevant?(validator)
10
+ end
11
+ else
12
+ []
13
+ end
14
+ end
15
+
16
+ def validator_relevant?(validator)
17
+ return true unless validator.options.key?(:if) || validator.options.key?(:unless)
18
+ conditional = validator.options.key?(:if) ? validator.options[:if] : validator.options[:unless]
19
+
20
+ result = if conditional.respond_to?(:call)
21
+ conditional.call(object)
22
+ elsif conditional.is_a?(::Symbol) && object.respond_to?(conditional)
23
+ object.send(conditional)
24
+ else
25
+ conditional
26
+ end
27
+
28
+ result = validator.options.key?(:unless) ? !result : !!result
29
+ not_required_through_negated_validation! if !result && [:presence, :inclusion, :length].include?(validator.kind)
30
+
31
+ result
32
+ end
33
+
34
+ def validation_limit
35
+ validation = validations? && validations.find do |validation|
36
+ validation.kind == :length
37
+ end
38
+ if validation
39
+ validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
40
+ else
41
+ nil
42
+ end
43
+ end
44
+
45
+ # Prefer :greater_than_or_equal_to over :greater_than, for no particular reason.
46
+ def validation_min
47
+ validation = validations? && validations.find do |validation|
48
+ validation.kind == :numericality
49
+ end
50
+ if validation
51
+ return validation.options[:greater_than_or_equal_to] if validation.options[:greater_than_or_equal_to]
52
+ return (validation.options[:greater_than] + 1) if validation.options[:greater_than]
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ # Prefer :less_than_or_equal_to over :less_than, for no particular reason.
59
+ def validation_max
60
+ validation = validations? && validations.find do |validation|
61
+ validation.kind == :numericality
62
+ end
63
+ if validation
64
+ return validation.options[:less_than_or_equal_to] if validation.options[:less_than_or_equal_to]
65
+ return (validation.options[:less_than] - 1) if validation.options[:less_than]
66
+ else
67
+ nil
68
+ end
69
+ end
70
+
71
+ def validation_integer_only?
72
+ validation = validations? && validations.find do |validation|
73
+ validation.kind == :numericality
74
+ end
75
+ if validation
76
+ validation.options[:only_integer]
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ def validations?
83
+ !validations.empty?
84
+ end
85
+
86
+ def required?
87
+ return false if not_required_through_negated_validation?
88
+ if validations?
89
+ validations.select { |validator|
90
+ [:presence, :inclusion, :length].include?(validator.kind) &&
91
+ validator.options[:allow_blank] != true
92
+ }.any?
93
+ else
94
+ return false if options[:required] == false
95
+ return true if options[:required] == true
96
+ return !!builder.all_fields_required_by_default
97
+ end
98
+ end
99
+
100
+ def not_required_through_negated_validation?
101
+ @not_required_through_negated_validation
102
+ end
103
+
104
+ def not_required_through_negated_validation!
105
+ @not_required_through_negated_validation = true
106
+ end
107
+
108
+ def optional?
109
+ !required?
110
+ end
111
+
112
+ def column_limit
113
+ column.limit if column? && column.respond_to?(:limit)
114
+ end
115
+
116
+ def limit
117
+ validation_limit || column_limit
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+