ehoch_simple_form 2.0.2.dev
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +257 -0
- data/MIT-LICENSE +20 -0
- data/README.md +797 -0
- data/lib/generators/simple_form/USAGE +3 -0
- data/lib/generators/simple_form/install_generator.rb +32 -0
- data/lib/generators/simple_form/templates/README +12 -0
- data/lib/generators/simple_form/templates/_form.html.erb +13 -0
- data/lib/generators/simple_form/templates/_form.html.haml +10 -0
- data/lib/generators/simple_form/templates/_form.html.slim +10 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +181 -0
- data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +26 -0
- data/lib/simple_form.rb +215 -0
- data/lib/simple_form/action_view_extensions/builder.rb +338 -0
- data/lib/simple_form/action_view_extensions/form_helper.rb +74 -0
- data/lib/simple_form/components.rb +20 -0
- data/lib/simple_form/components/errors.rb +35 -0
- data/lib/simple_form/components/hints.rb +18 -0
- data/lib/simple_form/components/html5.rb +26 -0
- data/lib/simple_form/components/label_input.rb +15 -0
- data/lib/simple_form/components/labels.rb +79 -0
- data/lib/simple_form/components/maxlength.rb +41 -0
- data/lib/simple_form/components/min_max.rb +50 -0
- data/lib/simple_form/components/pattern.rb +34 -0
- data/lib/simple_form/components/placeholders.rb +16 -0
- data/lib/simple_form/components/readonly.rb +22 -0
- data/lib/simple_form/core_ext/hash.rb +16 -0
- data/lib/simple_form/error_notification.rb +48 -0
- data/lib/simple_form/form_builder.rb +472 -0
- data/lib/simple_form/helpers.rb +12 -0
- data/lib/simple_form/helpers/autofocus.rb +11 -0
- data/lib/simple_form/helpers/disabled.rb +15 -0
- data/lib/simple_form/helpers/readonly.rb +15 -0
- data/lib/simple_form/helpers/required.rb +35 -0
- data/lib/simple_form/helpers/validators.rb +44 -0
- data/lib/simple_form/i18n_cache.rb +22 -0
- data/lib/simple_form/inputs.rb +21 -0
- data/lib/simple_form/inputs/base.rb +162 -0
- data/lib/simple_form/inputs/block_input.rb +14 -0
- data/lib/simple_form/inputs/boolean_input.rb +64 -0
- data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
- data/lib/simple_form/inputs/collection_input.rb +101 -0
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +63 -0
- data/lib/simple_form/inputs/collection_select_input.rb +14 -0
- data/lib/simple_form/inputs/date_time_input.rb +28 -0
- data/lib/simple_form/inputs/file_input.rb +9 -0
- data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
- data/lib/simple_form/inputs/hidden_input.rb +17 -0
- data/lib/simple_form/inputs/numeric_input.rb +24 -0
- data/lib/simple_form/inputs/password_input.rb +12 -0
- data/lib/simple_form/inputs/priority_input.rb +24 -0
- data/lib/simple_form/inputs/range_input.rb +14 -0
- data/lib/simple_form/inputs/string_input.rb +23 -0
- data/lib/simple_form/inputs/text_input.rb +11 -0
- data/lib/simple_form/map_type.rb +16 -0
- data/lib/simple_form/version.rb +3 -0
- data/lib/simple_form/wrappers.rb +8 -0
- data/lib/simple_form/wrappers/builder.rb +103 -0
- data/lib/simple_form/wrappers/many.rb +69 -0
- data/lib/simple_form/wrappers/root.rb +34 -0
- data/lib/simple_form/wrappers/single.rb +18 -0
- data/test/action_view_extensions/builder_test.rb +577 -0
- data/test/action_view_extensions/form_helper_test.rb +104 -0
- data/test/components/label_test.rb +310 -0
- data/test/form_builder/association_test.rb +177 -0
- data/test/form_builder/button_test.rb +47 -0
- data/test/form_builder/error_notification_test.rb +79 -0
- data/test/form_builder/error_test.rb +121 -0
- data/test/form_builder/general_test.rb +356 -0
- data/test/form_builder/hint_test.rb +139 -0
- data/test/form_builder/input_field_test.rb +63 -0
- data/test/form_builder/label_test.rb +71 -0
- data/test/form_builder/wrapper_test.rb +149 -0
- data/test/generators/simple_form_generator_test.rb +32 -0
- data/test/inputs/boolean_input_test.rb +108 -0
- data/test/inputs/collection_check_boxes_input_test.rb +224 -0
- data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
- data/test/inputs/collection_select_input_test.rb +241 -0
- data/test/inputs/datetime_input_test.rb +99 -0
- data/test/inputs/disabled_test.rb +38 -0
- data/test/inputs/discovery_test.rb +61 -0
- data/test/inputs/file_input_test.rb +16 -0
- data/test/inputs/general_test.rb +69 -0
- data/test/inputs/grouped_collection_select_input_test.rb +118 -0
- data/test/inputs/hidden_input_test.rb +30 -0
- data/test/inputs/numeric_input_test.rb +173 -0
- data/test/inputs/priority_input_test.rb +43 -0
- data/test/inputs/readonly_test.rb +61 -0
- data/test/inputs/required_test.rb +113 -0
- data/test/inputs/string_input_test.rb +140 -0
- data/test/inputs/text_input_test.rb +24 -0
- data/test/simple_form_test.rb +9 -0
- data/test/support/discovery_inputs.rb +21 -0
- data/test/support/misc_helpers.rb +102 -0
- data/test/support/mock_controller.rb +24 -0
- data/test/support/models.rb +210 -0
- data/test/test_helper.rb +90 -0
- metadata +210 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
# Helpers are made of several helpers that cannot be turned on automatically.
|
3
|
+
# For instance, disabled cannot be turned on automatically, it requires the
|
4
|
+
# user to explicitly pass the option :disabled => true so it may work.
|
5
|
+
module Helpers
|
6
|
+
autoload :Autofocus, 'simple_form/helpers/autofocus'
|
7
|
+
autoload :Disabled, 'simple_form/helpers/disabled'
|
8
|
+
autoload :Readonly, 'simple_form/helpers/readonly'
|
9
|
+
autoload :Required, 'simple_form/helpers/required'
|
10
|
+
autoload :Validators, 'simple_form/helpers/validators'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Helpers
|
3
|
+
module Required
|
4
|
+
private
|
5
|
+
|
6
|
+
def required_field?
|
7
|
+
@required
|
8
|
+
end
|
9
|
+
|
10
|
+
def calculate_required
|
11
|
+
if !options[:required].nil?
|
12
|
+
options[:required]
|
13
|
+
elsif has_validators?
|
14
|
+
required_by_validators?
|
15
|
+
else
|
16
|
+
required_by_default?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def required_by_validators?
|
21
|
+
(attribute_validators + reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def required_by_default?
|
25
|
+
SimpleForm.required_by_default
|
26
|
+
end
|
27
|
+
|
28
|
+
# Do not use has_required? because we want to add the class
|
29
|
+
# regardless of the required option.
|
30
|
+
def required_class
|
31
|
+
required_field? ? :required : :optional
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Helpers
|
3
|
+
module Validators
|
4
|
+
def has_validators?
|
5
|
+
@has_validators ||= attribute_name && object.class.respond_to?(:validators_on)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def attribute_validators
|
11
|
+
object.class.validators_on(attribute_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def reflection_validators
|
15
|
+
reflection ? object.class.validators_on(reflection.name) : []
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_validator?(validator)
|
19
|
+
!conditional_validators?(validator) && action_validator_match?(validator)
|
20
|
+
end
|
21
|
+
|
22
|
+
def conditional_validators?(validator)
|
23
|
+
validator.options.include?(:if) || validator.options.include?(:unless)
|
24
|
+
end
|
25
|
+
|
26
|
+
def action_validator_match?(validator)
|
27
|
+
return true if !validator.options.include?(:on)
|
28
|
+
|
29
|
+
case validator.options[:on]
|
30
|
+
when :save
|
31
|
+
true
|
32
|
+
when :create
|
33
|
+
!object.persisted?
|
34
|
+
when :update
|
35
|
+
object.persisted?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_validator(validator)
|
40
|
+
attribute_validators.find { |v| validator === v } if has_validators?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
# A lot of configuration values are retrived from I18n,
|
3
|
+
# like boolean collection, required string. This module provides
|
4
|
+
# caching facility to speed up form construction.
|
5
|
+
module I18nCache
|
6
|
+
def i18n_cache(key)
|
7
|
+
get_i18n_cache(key)[I18n.locale] ||= yield.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_i18n_cache(key)
|
11
|
+
if class_variable_defined?(:"@@#{key}")
|
12
|
+
class_variable_get(:"@@#{key}")
|
13
|
+
else
|
14
|
+
reset_i18n_cache(key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset_i18n_cache(key)
|
19
|
+
class_variable_set(:"@@#{key}", {})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Inputs
|
3
|
+
autoload :Base, 'simple_form/inputs/base'
|
4
|
+
autoload :BlockInput, 'simple_form/inputs/block_input'
|
5
|
+
autoload :BooleanInput, 'simple_form/inputs/boolean_input'
|
6
|
+
autoload :CollectionCheckBoxesInput, 'simple_form/inputs/collection_check_boxes_input'
|
7
|
+
autoload :CollectionInput, 'simple_form/inputs/collection_input'
|
8
|
+
autoload :CollectionRadioButtonsInput, 'simple_form/inputs/collection_radio_buttons_input'
|
9
|
+
autoload :CollectionSelectInput, 'simple_form/inputs/collection_select_input'
|
10
|
+
autoload :DateTimeInput, 'simple_form/inputs/date_time_input'
|
11
|
+
autoload :FileInput, 'simple_form/inputs/file_input'
|
12
|
+
autoload :GroupedCollectionSelectInput, 'simple_form/inputs/grouped_collection_select_input'
|
13
|
+
autoload :HiddenInput, 'simple_form/inputs/hidden_input'
|
14
|
+
autoload :NumericInput, 'simple_form/inputs/numeric_input'
|
15
|
+
autoload :PasswordInput, 'simple_form/inputs/password_input'
|
16
|
+
autoload :PriorityInput, 'simple_form/inputs/priority_input'
|
17
|
+
autoload :RangeInput, 'simple_form/inputs/range_input'
|
18
|
+
autoload :StringInput, 'simple_form/inputs/string_input'
|
19
|
+
autoload :TextInput, 'simple_form/inputs/text_input'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Inputs
|
3
|
+
class Base
|
4
|
+
extend I18nCache
|
5
|
+
|
6
|
+
include SimpleForm::Helpers::Autofocus
|
7
|
+
include SimpleForm::Helpers::Disabled
|
8
|
+
include SimpleForm::Helpers::Readonly
|
9
|
+
include SimpleForm::Helpers::Required
|
10
|
+
include SimpleForm::Helpers::Validators
|
11
|
+
|
12
|
+
include SimpleForm::Components::Errors
|
13
|
+
include SimpleForm::Components::Hints
|
14
|
+
include SimpleForm::Components::HTML5
|
15
|
+
include SimpleForm::Components::LabelInput
|
16
|
+
include SimpleForm::Components::Maxlength
|
17
|
+
include SimpleForm::Components::MinMax
|
18
|
+
include SimpleForm::Components::Pattern
|
19
|
+
include SimpleForm::Components::Placeholders
|
20
|
+
include SimpleForm::Components::Readonly
|
21
|
+
|
22
|
+
attr_reader :attribute_name, :column, :input_type, :reflection,
|
23
|
+
:options, :input_html_options, :input_html_classes, :html_classes
|
24
|
+
|
25
|
+
delegate :template, :object, :object_name, :lookup_model_names, :lookup_action, :to => :@builder
|
26
|
+
|
27
|
+
class_attribute :default_options
|
28
|
+
self.default_options = {}
|
29
|
+
|
30
|
+
def self.enable(*keys)
|
31
|
+
options = self.default_options.dup
|
32
|
+
keys.each { |key| options.delete(key) }
|
33
|
+
self.default_options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.disable(*keys)
|
37
|
+
options = self.default_options.dup
|
38
|
+
keys.each { |key| options[key] = false }
|
39
|
+
self.default_options = options
|
40
|
+
end
|
41
|
+
|
42
|
+
# Always enabled.
|
43
|
+
enable :hint
|
44
|
+
|
45
|
+
# Usually disabled, needs to be enabled explicitly passing true as option.
|
46
|
+
disable :maxlength, :placeholder, :pattern, :min_max
|
47
|
+
|
48
|
+
def initialize(builder, attribute_name, column, input_type, options = {})
|
49
|
+
super
|
50
|
+
|
51
|
+
options = options.dup
|
52
|
+
@builder = builder
|
53
|
+
@attribute_name = attribute_name
|
54
|
+
@column = column
|
55
|
+
@input_type = input_type
|
56
|
+
@reflection = options.delete(:reflection)
|
57
|
+
@options = options.reverse_merge!(self.class.default_options)
|
58
|
+
@required = calculate_required
|
59
|
+
|
60
|
+
# Notice that html_options_for receives a reference to input_html_classes.
|
61
|
+
# This means that classes added dynamically to input_html_classes will
|
62
|
+
# still propagate to input_html_options.
|
63
|
+
@html_classes = SimpleForm.additional_classes_for(:input) {
|
64
|
+
[input_type, required_class, readonly_class, disabled_class].compact
|
65
|
+
}
|
66
|
+
|
67
|
+
@input_html_classes = @html_classes.dup
|
68
|
+
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
|
69
|
+
o[:readonly] = true if has_readonly?
|
70
|
+
o[:disabled] = true if has_disabled?
|
71
|
+
o[:autofocus] = true if has_autofocus?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def input
|
76
|
+
raise NotImplementedError
|
77
|
+
end
|
78
|
+
|
79
|
+
def input_options
|
80
|
+
options
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def add_size!
|
86
|
+
input_html_options[:size] ||= [limit, SimpleForm.default_input_size].compact.min
|
87
|
+
end
|
88
|
+
|
89
|
+
def limit
|
90
|
+
column && column.limit
|
91
|
+
end
|
92
|
+
|
93
|
+
def nested_boolean_style?
|
94
|
+
options.fetch(:boolean_style, SimpleForm.boolean_style) == :nested
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find reflection name when available, otherwise use attribute
|
98
|
+
def reflection_or_attribute_name
|
99
|
+
reflection ? reflection.name : attribute_name
|
100
|
+
end
|
101
|
+
|
102
|
+
# Retrieve options for the given namespace from the options hash
|
103
|
+
def html_options_for(namespace, css_classes)
|
104
|
+
html_options = options[:"#{namespace}_html"] || {}
|
105
|
+
css_classes << html_options[:class] if html_options.key?(:class)
|
106
|
+
html_options[:class] = css_classes
|
107
|
+
html_options
|
108
|
+
end
|
109
|
+
|
110
|
+
# Lookup translations for the given namespace using I18n, based on object name,
|
111
|
+
# actual action and attribute name. Lookup priority as follows:
|
112
|
+
#
|
113
|
+
# simple_form.{namespace}.{model}.{action}.{attribute}
|
114
|
+
# simple_form.{namespace}.{model}.{attribute}
|
115
|
+
# simple_form.{namespace}.defaults.{attribute}
|
116
|
+
#
|
117
|
+
# Namespace is used for :labels and :hints.
|
118
|
+
#
|
119
|
+
# Model is the actual object name, for a @user object you'll have :user.
|
120
|
+
# Action is the action being rendered, usually :new or :edit.
|
121
|
+
# And attribute is the attribute itself, :name for example.
|
122
|
+
#
|
123
|
+
# The lookup for nested attributes is also done in a nested format using
|
124
|
+
# both model and nested object names, such as follow:
|
125
|
+
#
|
126
|
+
# simple_form.{namespace}.{model}.{nested}.{action}.{attribute}
|
127
|
+
# simple_form.{namespace}.{model}.{nested}.{attribute}
|
128
|
+
# simple_form.{namespace}.{nested}.{action}.{attribute}
|
129
|
+
# simple_form.{namespace}.{nested}.{attribute}
|
130
|
+
# simple_form.{namespace}.defaults.{attribute}
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
#
|
134
|
+
# simple_form:
|
135
|
+
# labels:
|
136
|
+
# user:
|
137
|
+
# new:
|
138
|
+
# email: 'E-mail para efetuar o sign in.'
|
139
|
+
# edit:
|
140
|
+
# email: 'E-mail.'
|
141
|
+
#
|
142
|
+
# Take a look at our locale example file.
|
143
|
+
def translate(namespace, default='')
|
144
|
+
model_names = lookup_model_names.dup
|
145
|
+
lookups = []
|
146
|
+
|
147
|
+
while !model_names.empty?
|
148
|
+
joined_model_names = model_names.join(".")
|
149
|
+
model_names.shift
|
150
|
+
|
151
|
+
lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
|
152
|
+
lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
|
153
|
+
end
|
154
|
+
lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}"
|
155
|
+
lookups << :"defaults.#{attribute_name}"
|
156
|
+
lookups << default
|
157
|
+
|
158
|
+
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Inputs
|
3
|
+
class BooleanInput < Base
|
4
|
+
def input
|
5
|
+
if nested_boolean_style?
|
6
|
+
build_hidden_field_for_checkbox +
|
7
|
+
template.label_tag(nil, :class => "checkbox") {
|
8
|
+
build_check_box_without_hidden_field + options[:inline_label]
|
9
|
+
}
|
10
|
+
else
|
11
|
+
build_check_box
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def label_input
|
16
|
+
if options[:label] == false
|
17
|
+
input
|
18
|
+
elsif nested_boolean_style?
|
19
|
+
html_options = label_html_options.dup
|
20
|
+
html_options[:class].push(:checkbox)
|
21
|
+
|
22
|
+
build_hidden_field_for_checkbox +
|
23
|
+
@builder.label(label_target, html_options) {
|
24
|
+
build_check_box_without_hidden_field + label_text
|
25
|
+
}
|
26
|
+
else
|
27
|
+
input + label
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Build a checkbox tag using default unchecked value. This allows us to
|
34
|
+
# reuse the method for nested boolean style, but with no unchecked value,
|
35
|
+
# which won't generate the hidden checkbox. This is the default functionality
|
36
|
+
# in Rails > 3.2.1, and is backported in SimpleForm AV helpers.
|
37
|
+
def build_check_box(unchecked_value='0')
|
38
|
+
@builder.check_box(attribute_name, input_html_options, '1', unchecked_value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Build a checkbox without generating the hidden field. See
|
42
|
+
# #build_hidden_field_for_checkbox for more info.
|
43
|
+
def build_check_box_without_hidden_field
|
44
|
+
build_check_box(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create a hidden field for the current checkbox, so we can simulate Rails
|
48
|
+
# functionality with hidden + checkbox, but under a nested context, where
|
49
|
+
# we need the hidden field to be *outside* the label (otherwise it
|
50
|
+
# generates invalid html - html5 only).
|
51
|
+
def build_hidden_field_for_checkbox
|
52
|
+
@builder.hidden_field(attribute_name, :value => '0', :id => nil,
|
53
|
+
:disabled => input_html_options[:disabled])
|
54
|
+
end
|
55
|
+
|
56
|
+
# Booleans are not required by default because in most of the cases
|
57
|
+
# it makes no sense marking them as required. The only exception is
|
58
|
+
# Terms of Use usually presented at most sites sign up screen.
|
59
|
+
def required_by_default?
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Inputs
|
3
|
+
class CollectionCheckBoxesInput < CollectionRadioButtonsInput
|
4
|
+
protected
|
5
|
+
|
6
|
+
# Checkbox components do not use the required html tag.
|
7
|
+
# More info: https://github.com/plataformatec/simple_form/issues/340#issuecomment-2871956
|
8
|
+
def has_required?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_nested_boolean_style_item_tag(collection_builder)
|
13
|
+
collection_builder.check_box + collection_builder.text
|
14
|
+
end
|
15
|
+
|
16
|
+
def item_wrapper_class
|
17
|
+
"checkbox"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|