simple_form 1.5.2 → 2.0.0
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/CHANGELOG.md +234 -0
- data/MIT-LICENSE +1 -1
- data/README.md +816 -0
- data/lib/generators/simple_form/install_generator.rb +15 -1
- data/lib/generators/simple_form/templates/README +12 -0
- data/lib/generators/simple_form/templates/_form.html.erb +2 -2
- data/lib/generators/simple_form/templates/_form.html.haml +2 -2
- data/lib/generators/simple_form/templates/_form.html.slim +4 -4
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +176 -0
- data/lib/simple_form/action_view_extensions/builder.rb +206 -59
- data/lib/simple_form/action_view_extensions/form_helper.rb +30 -23
- data/lib/simple_form/components/errors.rb +6 -24
- data/lib/simple_form/components/hints.rb +7 -21
- data/lib/simple_form/components/html5.rb +26 -0
- data/lib/simple_form/components/labels.rb +22 -14
- data/lib/simple_form/components/maxlength.rb +41 -0
- data/lib/simple_form/components/min_max.rb +49 -0
- data/lib/simple_form/components/pattern.rb +34 -0
- data/lib/simple_form/components/placeholders.rb +5 -17
- data/lib/simple_form/components/readonly.rb +22 -0
- data/lib/simple_form/components.rb +11 -1
- data/lib/simple_form/core_ext/hash.rb +16 -0
- data/lib/simple_form/error_notification.rb +9 -3
- data/lib/simple_form/form_builder.rb +105 -28
- 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 +10 -11
- data/lib/simple_form/helpers/validators.rb +4 -4
- data/lib/simple_form/helpers.rb +7 -4
- data/lib/simple_form/inputs/base.rb +53 -81
- data/lib/simple_form/inputs/boolean_input.rb +46 -4
- data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
- data/lib/simple_form/inputs/collection_input.rb +27 -13
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +67 -0
- data/lib/simple_form/inputs/collection_select_input.rb +14 -0
- data/lib/simple_form/inputs/date_time_input.rb +10 -6
- data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
- data/lib/simple_form/inputs/hidden_input.rb +3 -6
- data/lib/simple_form/inputs/numeric_input.rb +3 -51
- data/lib/simple_form/inputs/password_input.rb +1 -2
- data/lib/simple_form/inputs/priority_input.rb +2 -2
- data/lib/simple_form/inputs/range_input.rb +1 -3
- data/lib/simple_form/inputs/string_input.rb +6 -8
- data/lib/simple_form/inputs/text_input.rb +1 -2
- data/lib/simple_form/inputs.rb +17 -13
- data/lib/simple_form/version.rb +1 -1
- 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/lib/simple_form/wrappers.rb +8 -0
- data/lib/simple_form.rb +118 -48
- data/test/action_view_extensions/builder_test.rb +285 -102
- data/test/action_view_extensions/form_helper_test.rb +32 -10
- data/test/components/label_test.rb +44 -5
- data/test/form_builder/association_test.rb +177 -0
- data/test/form_builder/button_test.rb +47 -0
- data/test/{error_notification_test.rb → form_builder/error_notification_test.rb} +18 -1
- 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 +123 -0
- data/test/form_builder/input_field_test.rb +63 -0
- data/test/form_builder/label_test.rb +65 -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 +101 -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 +167 -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/support/misc_helpers.rb +53 -12
- data/test/support/mock_controller.rb +2 -2
- data/test/support/models.rb +20 -5
- data/test/test_helper.rb +11 -12
- metadata +124 -96
- data/.gitignore +0 -3
- data/.gitmodules +0 -3
- data/.travis.yml +0 -15
- data/CHANGELOG.rdoc +0 -159
- data/Gemfile +0 -9
- data/README.rdoc +0 -466
- data/Rakefile +0 -27
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +0 -93
- data/lib/simple_form/components/wrapper.rb +0 -38
- data/lib/simple_form/helpers/has_errors.rb +0 -15
- data/lib/simple_form/helpers/maxlength.rb +0 -24
- data/lib/simple_form/helpers/pattern.rb +0 -28
- data/simple_form.gemspec +0 -25
- data/test/components/error_test.rb +0 -56
- data/test/components/hint_test.rb +0 -74
- data/test/components/wrapper_test.rb +0 -63
- data/test/custom_components.rb +0 -7
- data/test/form_builder_test.rb +0 -930
- data/test/inputs_test.rb +0 -995
- /data/test/{discovery_inputs.rb → support/discovery_inputs.rb} +0 -0
|
@@ -13,9 +13,8 @@ module SimpleForm
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def input
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
value_method, label_method, input_options, input_html_options)
|
|
16
|
+
raise NotImplementedError,
|
|
17
|
+
"input should be implemented by classes inheriting from CollectionInput"
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def input_options
|
|
@@ -27,18 +26,23 @@ module SimpleForm
|
|
|
27
26
|
private
|
|
28
27
|
|
|
29
28
|
def collection
|
|
30
|
-
@collection ||=
|
|
29
|
+
@collection ||= begin
|
|
30
|
+
collection = options.delete(:collection) || self.class.boolean_collection
|
|
31
|
+
collection.respond_to?(:call) ? collection.call : collection.to_a
|
|
32
|
+
end
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
# Select components does not allow the required html tag.
|
|
34
35
|
def has_required?
|
|
35
|
-
super &&
|
|
36
|
+
super && (input_options[:include_blank] || multiple?)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Check if :include_blank must be included by default.
|
|
39
40
|
def skip_include_blank?
|
|
40
|
-
(options.keys & [:prompt, :include_blank, :default, :selected]).any? ||
|
|
41
|
-
|
|
41
|
+
(options.keys & [:prompt, :include_blank, :default, :selected]).any? || multiple?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def multiple?
|
|
45
|
+
!!options[:input_html].try(:[], :multiple)
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
# Detect the right method to find the label and value for a collection.
|
|
@@ -58,10 +62,10 @@ module SimpleForm
|
|
|
58
62
|
[label, value]
|
|
59
63
|
end
|
|
60
64
|
|
|
61
|
-
def detect_common_display_methods
|
|
62
|
-
|
|
65
|
+
def detect_common_display_methods(collection_classes = detect_collection_classes)
|
|
66
|
+
collection_translated = translate_collection if collection_classes == [Symbol]
|
|
63
67
|
|
|
64
|
-
if collection_classes.include?(Array)
|
|
68
|
+
if collection_translated || collection_classes.include?(Array)
|
|
65
69
|
{ :label => :first, :value => :last }
|
|
66
70
|
elsif collection_includes_basic_objects?(collection_classes)
|
|
67
71
|
{ :label => :to_s, :value => :to_s }
|
|
@@ -73,8 +77,8 @@ module SimpleForm
|
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
|
|
76
|
-
def detect_collection_classes
|
|
77
|
-
|
|
80
|
+
def detect_collection_classes(some_collection = collection)
|
|
81
|
+
some_collection.map { |e| e.class }.uniq
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def collection_includes_basic_objects?(collection_classes)
|
|
@@ -82,6 +86,16 @@ module SimpleForm
|
|
|
82
86
|
String, Integer, Fixnum, Bignum, Float, NilClass, Symbol, TrueClass, FalseClass
|
|
83
87
|
]).any?
|
|
84
88
|
end
|
|
89
|
+
|
|
90
|
+
def translate_collection
|
|
91
|
+
if translated_collection = translate(:options)
|
|
92
|
+
@collection = collection.map do |key|
|
|
93
|
+
[translated_collection[key] || key, key]
|
|
94
|
+
end
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
85
98
|
end
|
|
86
99
|
end
|
|
87
100
|
end
|
|
101
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Inputs
|
|
3
|
+
class CollectionRadioButtonsInput < CollectionInput
|
|
4
|
+
def input
|
|
5
|
+
label_method, value_method = detect_collection_methods
|
|
6
|
+
|
|
7
|
+
@builder.send("collection_#{input_type}",
|
|
8
|
+
attribute_name, collection, value_method, label_method,
|
|
9
|
+
input_options, input_html_options, &collection_block_for_nested_boolean_style
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def input_options
|
|
14
|
+
options = super
|
|
15
|
+
apply_default_collection_options!(options)
|
|
16
|
+
apply_nested_boolean_collection_options!(options) if nested_boolean_style?
|
|
17
|
+
options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
def apply_default_collection_options!(options)
|
|
23
|
+
unless options.key?(:item_wrapper_tag)
|
|
24
|
+
options[:item_wrapper_tag] = SimpleForm.item_wrapper_tag
|
|
25
|
+
end
|
|
26
|
+
options[:item_wrapper_class] = [
|
|
27
|
+
item_wrapper_class, options[:item_wrapper_class], SimpleForm.item_wrapper_class
|
|
28
|
+
].compact.presence
|
|
29
|
+
|
|
30
|
+
unless options.key?(:collection_wrapper_tag)
|
|
31
|
+
options[:collection_wrapper_tag] = SimpleForm.collection_wrapper_tag
|
|
32
|
+
end
|
|
33
|
+
options[:collection_wrapper_class] = [
|
|
34
|
+
options[:collection_wrapper_class], SimpleForm.collection_wrapper_class
|
|
35
|
+
].compact.presence
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Force item wrapper to be a label when using nested boolean, to support
|
|
39
|
+
# configuring classes through :item_wrapper_class, and to maintain
|
|
40
|
+
# compatibility with :inline style and default :item_wrapper_tag.
|
|
41
|
+
def apply_nested_boolean_collection_options!(options)
|
|
42
|
+
options[:item_wrapper_tag] = :label
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def collection_block_for_nested_boolean_style
|
|
46
|
+
return unless nested_boolean_style?
|
|
47
|
+
|
|
48
|
+
proc { |builder| build_nested_boolean_style_item_tag(builder) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_nested_boolean_style_item_tag(collection_builder)
|
|
52
|
+
collection_builder.radio_button + collection_builder.text
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def item_wrapper_class
|
|
56
|
+
"radio"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Do not attempt to generate label[for] attributes by default, unless an
|
|
60
|
+
# explicit html option is given. This avoids generating labels pointing to
|
|
61
|
+
# non existent fields.
|
|
62
|
+
def generate_label_for_attribute?
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Inputs
|
|
3
|
+
class CollectionSelectInput < CollectionInput
|
|
4
|
+
def input
|
|
5
|
+
label_method, value_method = detect_collection_methods
|
|
6
|
+
|
|
7
|
+
@builder.collection_select(
|
|
8
|
+
attribute_name, collection, value_method, label_method,
|
|
9
|
+
input_options, input_html_options
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -5,19 +5,23 @@ module SimpleForm
|
|
|
5
5
|
@builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
private
|
|
9
|
-
|
|
10
8
|
def has_required?
|
|
11
9
|
false
|
|
12
10
|
end
|
|
13
11
|
|
|
12
|
+
private
|
|
13
|
+
|
|
14
14
|
def label_target
|
|
15
|
-
case input_type
|
|
15
|
+
position = case input_type
|
|
16
16
|
when :date, :datetime
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
date_order = input_options[:order] || I18n.t('date.order')
|
|
18
|
+
date_order.first
|
|
19
|
+
else
|
|
20
|
+
:hour
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
position = ActionView::Helpers::DateTimeSelector::POSITION[position]
|
|
24
|
+
"#{attribute_name}_#{position}i"
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Inputs
|
|
3
|
+
class GroupedCollectionSelectInput < CollectionInput
|
|
4
|
+
def input
|
|
5
|
+
label_method, value_method = detect_collection_methods
|
|
6
|
+
@builder.grouped_collection_select(attribute_name, grouped_collection,
|
|
7
|
+
group_method, group_label_method, value_method, label_method,
|
|
8
|
+
input_options, input_html_options)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def grouped_collection
|
|
14
|
+
@grouped_collection ||= begin
|
|
15
|
+
grouped_collection = options.delete(:collection)
|
|
16
|
+
grouped_collection.respond_to?(:call) ? grouped_collection.call : grouped_collection.to_a
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Sample collection
|
|
21
|
+
def collection
|
|
22
|
+
@collection ||= grouped_collection.first.try(:send, group_method) || []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def group_method
|
|
26
|
+
@group_method ||= options.delete(:group_method)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def group_label_method
|
|
30
|
+
label = options.delete(:group_label_method)
|
|
31
|
+
|
|
32
|
+
unless label
|
|
33
|
+
common_method_for = detect_common_display_methods(detect_collection_classes(grouped_collection))
|
|
34
|
+
label = common_method_for[:label]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
label
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
3
|
class HiddenInput < Base
|
|
4
|
-
|
|
4
|
+
disable :label, :errors, :hint, :required
|
|
5
|
+
|
|
6
|
+
def input
|
|
5
7
|
@builder.hidden_field(attribute_name, input_html_options)
|
|
6
8
|
end
|
|
7
|
-
alias :input :render
|
|
8
9
|
|
|
9
10
|
private
|
|
10
11
|
|
|
11
|
-
def attribute_required?
|
|
12
|
-
false
|
|
13
|
-
end
|
|
14
|
-
|
|
15
12
|
def required_class
|
|
16
13
|
nil
|
|
17
14
|
end
|
|
@@ -1,72 +1,24 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
3
|
class NumericInput < Base
|
|
4
|
-
enable :placeholder
|
|
4
|
+
enable :placeholder, :min_max
|
|
5
5
|
|
|
6
6
|
def input
|
|
7
7
|
add_size!
|
|
8
|
-
|
|
8
|
+
input_html_classes.unshift("numeric")
|
|
9
|
+
if html5?
|
|
9
10
|
input_html_options[:type] ||= "number"
|
|
10
11
|
input_html_options[:step] ||= integer? ? 1 : "any"
|
|
11
|
-
infer_attributes_from_validations!
|
|
12
12
|
end
|
|
13
13
|
@builder.text_field(attribute_name, input_html_options)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def input_html_classes
|
|
17
|
-
super.unshift("numeric")
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
private
|
|
21
17
|
|
|
22
18
|
# Rails adds the size attr by default, if the :size key does not exist.
|
|
23
19
|
def add_size!
|
|
24
20
|
input_html_options[:size] ||= nil
|
|
25
21
|
end
|
|
26
|
-
|
|
27
|
-
def infer_attributes_from_validations!
|
|
28
|
-
return unless has_validators?
|
|
29
|
-
|
|
30
|
-
numeric_validator = find_numericality_validator or return
|
|
31
|
-
validator_options = numeric_validator.options
|
|
32
|
-
|
|
33
|
-
input_html_options[:min] ||= minimum_value(validator_options)
|
|
34
|
-
input_html_options[:max] ||= maximum_value(validator_options)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def integer?
|
|
38
|
-
input_type == :integer
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def minimum_value(validator_options)
|
|
42
|
-
if integer? && validator_options.key?(:greater_than)
|
|
43
|
-
evaluate_validator_option(validator_options[:greater_than]) + 1
|
|
44
|
-
else
|
|
45
|
-
evaluate_validator_option(validator_options[:greater_than_or_equal_to])
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def maximum_value(validator_options)
|
|
50
|
-
if integer? && validator_options.key?(:less_than)
|
|
51
|
-
evaluate_validator_option(validator_options[:less_than]) - 1
|
|
52
|
-
else
|
|
53
|
-
evaluate_validator_option(validator_options[:less_than_or_equal_to])
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def find_numericality_validator
|
|
58
|
-
find_validator(ActiveModel::Validations::NumericalityValidator)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def evaluate_validator_option(option)
|
|
62
|
-
if option.is_a?(Numeric)
|
|
63
|
-
option
|
|
64
|
-
elsif option.is_a?(Symbol)
|
|
65
|
-
object.send(option)
|
|
66
|
-
elsif option.respond_to?(:call)
|
|
67
|
-
option.call(object)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
22
|
end
|
|
71
23
|
end
|
|
72
24
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
|
-
class PriorityInput <
|
|
3
|
+
class PriorityInput < CollectionSelectInput
|
|
4
4
|
def input
|
|
5
5
|
@builder.send(:"#{input_type}_select", attribute_name, input_priority,
|
|
6
6
|
input_options, input_html_options)
|
|
@@ -10,7 +10,7 @@ module SimpleForm
|
|
|
10
10
|
options[:priority] || SimpleForm.send(:"#{input_type}_priority")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
protected
|
|
14
14
|
|
|
15
15
|
def has_required?
|
|
16
16
|
false
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
3
|
class StringInput < Base
|
|
4
|
-
enable :placeholder
|
|
4
|
+
enable :placeholder, :maxlength, :pattern
|
|
5
5
|
|
|
6
6
|
def input
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
unless string?
|
|
8
|
+
input_html_classes.unshift("string")
|
|
9
|
+
input_html_options[:type] ||= input_type if html5?
|
|
10
|
+
end
|
|
11
|
+
|
|
10
12
|
add_size!
|
|
11
13
|
@builder.text_field(attribute_name, input_html_options)
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
def input_html_classes
|
|
15
|
-
string? ? super : super.unshift("string")
|
|
16
|
-
end
|
|
17
|
-
|
|
18
16
|
private
|
|
19
17
|
|
|
20
18
|
def string?
|
data/lib/simple_form/inputs.rb
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
module SimpleForm
|
|
2
2
|
module Inputs
|
|
3
|
-
autoload :Base,
|
|
4
|
-
autoload :BlockInput,
|
|
5
|
-
autoload :BooleanInput,
|
|
6
|
-
autoload :
|
|
7
|
-
autoload :
|
|
8
|
-
autoload :
|
|
9
|
-
autoload :
|
|
10
|
-
autoload :
|
|
11
|
-
autoload :
|
|
12
|
-
autoload :
|
|
13
|
-
autoload :
|
|
14
|
-
autoload :
|
|
15
|
-
autoload :
|
|
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'
|
|
16
20
|
end
|
|
17
21
|
end
|
data/lib/simple_form/version.rb
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Wrappers
|
|
3
|
+
# Provides the builder syntax for components. The builder provides
|
|
4
|
+
# three methods `use`, `optional` and `wrapper` and they allow the following invocations:
|
|
5
|
+
#
|
|
6
|
+
# config.wrappers do |b|
|
|
7
|
+
# # Use a single component
|
|
8
|
+
# b.use :html5
|
|
9
|
+
#
|
|
10
|
+
# # Use the component, but do not automatically lookup. It will only be triggered when
|
|
11
|
+
# # :placeholder is explicitly set.
|
|
12
|
+
# b.optional :placeholder
|
|
13
|
+
#
|
|
14
|
+
# # Use a component with specific wrapper options
|
|
15
|
+
# b.use :error, :wrap_with => { :tag => "span", :class => "error" }
|
|
16
|
+
#
|
|
17
|
+
# # Use a set of components by wrapping them in a tag+class.
|
|
18
|
+
# b.wrapper :tag => "div", :class => "another" do |ba|
|
|
19
|
+
# ba.use :label
|
|
20
|
+
# ba.use :input
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # Use a set of components by wrapping them in a tag+class.
|
|
24
|
+
# # This wrapper is identified by :label_input, which means it can
|
|
25
|
+
# # be turned off on demand with `f.input :name, :label_input => false`
|
|
26
|
+
# b.wrapper :label_input, :tag => "div", :class => "another" do |ba|
|
|
27
|
+
# ba.use :label
|
|
28
|
+
# ba.use :input
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# The builder also accepts default options at the root level. This is usually
|
|
33
|
+
# used if you want a component to be disabled by default:
|
|
34
|
+
#
|
|
35
|
+
# config.wrappers :hint => false do |b|
|
|
36
|
+
# b.use :hint
|
|
37
|
+
# b.use :label_input
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# In the example above, hint defaults to false, which means it won't automatically
|
|
41
|
+
# do the lookup anymore. It will only be triggered when :hint is explicitly set.
|
|
42
|
+
class Builder
|
|
43
|
+
def initialize(options)
|
|
44
|
+
@options = options
|
|
45
|
+
@components = []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def use(name, options=nil, &block)
|
|
49
|
+
if block_given?
|
|
50
|
+
ActiveSupport::Deprecation.warn "Passing a block to use is deprecated. " \
|
|
51
|
+
"Please use wrapper instead of use."
|
|
52
|
+
return wrapper(name, options, &block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if options && options.keys != [:wrap_with]
|
|
56
|
+
ActiveSupport::Deprecation.warn "Passing :tag, :class and others to use is deprecated. " \
|
|
57
|
+
"Please invoke b.use #{name.inspect}, :wrap_with => #{options.inspect} instead."
|
|
58
|
+
options = { :wrap_with => options }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if options && wrapper = options[:wrap_with]
|
|
62
|
+
@components << Single.new(name, wrapper)
|
|
63
|
+
else
|
|
64
|
+
@components << name
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def optional(name, options=nil, &block)
|
|
69
|
+
if block_given?
|
|
70
|
+
ActiveSupport::Deprecation.warn "Passing a block to optional is deprecated. " \
|
|
71
|
+
"Please use wrapper instead of optional."
|
|
72
|
+
return wrapper(name, options, &block)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if options && options.keys != [:wrap_with]
|
|
76
|
+
ActiveSupport::Deprecation.warn "Passing :tag, :class and others to optional is deprecated. " \
|
|
77
|
+
"Please invoke b.optional #{name.inspect}, :wrap_with => #{options.inspect} instead."
|
|
78
|
+
options = { :wrap_with => options }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@options[name] = false
|
|
82
|
+
use(name, options, &block)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def wrapper(name, options=nil)
|
|
86
|
+
if block_given?
|
|
87
|
+
name, options = nil, name if name.is_a?(Hash)
|
|
88
|
+
builder = self.class.new(@options)
|
|
89
|
+
options ||= {}
|
|
90
|
+
options[:tag] = :div if options[:tag].nil?
|
|
91
|
+
yield builder
|
|
92
|
+
@components << Many.new(name, builder.to_a, options)
|
|
93
|
+
else
|
|
94
|
+
raise ArgumentError, "A block is required as argument to wrapper"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_a
|
|
99
|
+
@components
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Wrappers
|
|
3
|
+
# A wrapper is an object that holds several components and render them.
|
|
4
|
+
# A component may either be a symbol or any object that responds to `render`.
|
|
5
|
+
# This API allows inputs/components to be easily wrapped, removing the
|
|
6
|
+
# need to modify the code only to wrap input in an extra tag.
|
|
7
|
+
#
|
|
8
|
+
# `Many` represents a wrapper around several components at the same time.
|
|
9
|
+
# It may optionally receive a namespace, allowing it to be configured
|
|
10
|
+
# on demand on input generation.
|
|
11
|
+
class Many
|
|
12
|
+
attr_reader :namespace, :defaults, :components
|
|
13
|
+
alias :to_sym :namespace
|
|
14
|
+
|
|
15
|
+
def initialize(namespace, components, defaults={})
|
|
16
|
+
@namespace = namespace
|
|
17
|
+
@components = components
|
|
18
|
+
@defaults = defaults
|
|
19
|
+
@defaults[:tag] = :div unless @defaults.key?(:tag)
|
|
20
|
+
@defaults[:class] = Array.wrap(@defaults[:class])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render(input)
|
|
24
|
+
content = "".html_safe
|
|
25
|
+
options = input.options
|
|
26
|
+
|
|
27
|
+
components.each do |component|
|
|
28
|
+
next if options[component] == false
|
|
29
|
+
rendered = component.respond_to?(:render) ? component.render(input) : input.send(component)
|
|
30
|
+
content.safe_concat rendered.to_s if rendered
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
wrap(input, options, content)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find(name)
|
|
37
|
+
return self if namespace == name
|
|
38
|
+
|
|
39
|
+
@components.each do |c|
|
|
40
|
+
if c.is_a?(Symbol)
|
|
41
|
+
return nil if c == namespace
|
|
42
|
+
elsif value = c.find(name)
|
|
43
|
+
return value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def wrap(input, options, content)
|
|
53
|
+
return content if options[namespace] == false
|
|
54
|
+
|
|
55
|
+
tag = (namespace && options[:"#{namespace}_tag"]) || @defaults[:tag]
|
|
56
|
+
return content unless tag
|
|
57
|
+
|
|
58
|
+
klass = html_classes(input, options)
|
|
59
|
+
opts = options[:"#{namespace}_html"] || {}
|
|
60
|
+
opts[:class] = (klass << opts[:class]).join(' ').strip unless klass.empty?
|
|
61
|
+
input.template.content_tag(tag, content, opts)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def html_classes(input, options)
|
|
65
|
+
@defaults[:class].dup
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SimpleForm
|
|
2
|
+
module Wrappers
|
|
3
|
+
# `Root` is the root wrapper for all components. It is special cased to
|
|
4
|
+
# always have a namespace and to add special html classes.
|
|
5
|
+
class Root < Many
|
|
6
|
+
attr_reader :options
|
|
7
|
+
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
super(:wrapper, *args)
|
|
10
|
+
@options = @defaults.except(:tag, :class, :error_class, :hint_class)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def render(input)
|
|
14
|
+
input.options.reverse_merge!(@options)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Provide a fallback if name cannot be found.
|
|
19
|
+
def find(name)
|
|
20
|
+
super || SimpleForm::Wrappers::Many.new(name, [name])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def html_classes(input, options)
|
|
26
|
+
css = options[:wrapper_class] ? Array.wrap(options[:wrapper_class]) : @defaults[:class]
|
|
27
|
+
css += SimpleForm.additional_classes_for(:wrapper) { input.html_classes }
|
|
28
|
+
css << (options[:wrapper_error_class] || @defaults[:error_class]) if input.has_errors?
|
|
29
|
+
css << (options[:wrapper_hint_class] || @defaults[:hint_class]) if input.has_hint?
|
|
30
|
+
css
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|