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