nuatt-formtastic 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.textile +635 -0
- data/lib/formtastic.rb +24 -0
- data/lib/formtastic/form_builder.rb +75 -0
- data/lib/formtastic/helpers.rb +15 -0
- data/lib/formtastic/helpers/buttons_helper.rb +277 -0
- data/lib/formtastic/helpers/errors_helper.rb +124 -0
- data/lib/formtastic/helpers/fieldset_wrapper.rb +62 -0
- data/lib/formtastic/helpers/file_column_detection.rb +16 -0
- data/lib/formtastic/helpers/form_helper.rb +221 -0
- data/lib/formtastic/helpers/input_helper.rb +357 -0
- data/lib/formtastic/helpers/inputs_helper.rb +381 -0
- data/lib/formtastic/helpers/reflection.rb +12 -0
- data/lib/formtastic/helpers/semantic_form_helper.rb +11 -0
- data/lib/formtastic/html_attributes.rb +21 -0
- data/lib/formtastic/i18n.rb +32 -0
- data/lib/formtastic/inputs.rb +29 -0
- data/lib/formtastic/inputs/base.rb +50 -0
- data/lib/formtastic/inputs/base/associations.rb +33 -0
- data/lib/formtastic/inputs/base/choices.rb +88 -0
- data/lib/formtastic/inputs/base/collections.rb +94 -0
- data/lib/formtastic/inputs/base/database.rb +17 -0
- data/lib/formtastic/inputs/base/errors.rb +58 -0
- data/lib/formtastic/inputs/base/fileish.rb +23 -0
- data/lib/formtastic/inputs/base/grouped_collections.rb +77 -0
- data/lib/formtastic/inputs/base/hints.rb +31 -0
- data/lib/formtastic/inputs/base/html.rb +51 -0
- data/lib/formtastic/inputs/base/labelling.rb +53 -0
- data/lib/formtastic/inputs/base/naming.rb +54 -0
- data/lib/formtastic/inputs/base/options.rb +18 -0
- data/lib/formtastic/inputs/base/stringish.rb +30 -0
- data/lib/formtastic/inputs/base/timeish.rb +125 -0
- data/lib/formtastic/inputs/base/validations.rb +125 -0
- data/lib/formtastic/inputs/base/wrapping.rb +38 -0
- data/lib/formtastic/inputs/boolean_input.rb +87 -0
- data/lib/formtastic/inputs/check_boxes_input.rb +169 -0
- data/lib/formtastic/inputs/country_input.rb +66 -0
- data/lib/formtastic/inputs/date_input.rb +14 -0
- data/lib/formtastic/inputs/datetime_input.rb +9 -0
- data/lib/formtastic/inputs/email_input.rb +40 -0
- data/lib/formtastic/inputs/file_input.rb +42 -0
- data/lib/formtastic/inputs/hidden_input.rb +66 -0
- data/lib/formtastic/inputs/number_input.rb +72 -0
- data/lib/formtastic/inputs/numeric_input.rb +20 -0
- data/lib/formtastic/inputs/password_input.rb +40 -0
- data/lib/formtastic/inputs/phone_input.rb +41 -0
- data/lib/formtastic/inputs/radio_input.rb +146 -0
- data/lib/formtastic/inputs/search_input.rb +40 -0
- data/lib/formtastic/inputs/select_input.rb +208 -0
- data/lib/formtastic/inputs/string_input.rb +34 -0
- data/lib/formtastic/inputs/text_input.rb +47 -0
- data/lib/formtastic/inputs/time_input.rb +14 -0
- data/lib/formtastic/inputs/time_zone_input.rb +48 -0
- data/lib/formtastic/inputs/url_input.rb +40 -0
- data/lib/formtastic/localized_string.rb +96 -0
- data/lib/formtastic/railtie.rb +12 -0
- data/lib/formtastic/semantic_form_builder.rb +11 -0
- data/lib/formtastic/util.rb +25 -0
- data/lib/generators/formtastic/form/form_generator.rb +95 -0
- data/lib/generators/formtastic/install/install_generator.rb +23 -0
- data/lib/generators/templates/_form.html.erb +7 -0
- data/lib/generators/templates/_form.html.haml +5 -0
- data/lib/generators/templates/formtastic.css +145 -0
- data/lib/generators/templates/formtastic.rb +74 -0
- data/lib/generators/templates/formtastic_changes.css +14 -0
- data/lib/locale/en.yml +7 -0
- data/lib/tasks/verify_rcov.rb +44 -0
- 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
|
+
|