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