ehoch_simple_form 2.0.2.dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/CHANGELOG.md +257 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +797 -0
  4. data/lib/generators/simple_form/USAGE +3 -0
  5. data/lib/generators/simple_form/install_generator.rb +32 -0
  6. data/lib/generators/simple_form/templates/README +12 -0
  7. data/lib/generators/simple_form/templates/_form.html.erb +13 -0
  8. data/lib/generators/simple_form/templates/_form.html.haml +10 -0
  9. data/lib/generators/simple_form/templates/_form.html.slim +10 -0
  10. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb.tt +181 -0
  11. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +26 -0
  12. data/lib/simple_form.rb +215 -0
  13. data/lib/simple_form/action_view_extensions/builder.rb +338 -0
  14. data/lib/simple_form/action_view_extensions/form_helper.rb +74 -0
  15. data/lib/simple_form/components.rb +20 -0
  16. data/lib/simple_form/components/errors.rb +35 -0
  17. data/lib/simple_form/components/hints.rb +18 -0
  18. data/lib/simple_form/components/html5.rb +26 -0
  19. data/lib/simple_form/components/label_input.rb +15 -0
  20. data/lib/simple_form/components/labels.rb +79 -0
  21. data/lib/simple_form/components/maxlength.rb +41 -0
  22. data/lib/simple_form/components/min_max.rb +50 -0
  23. data/lib/simple_form/components/pattern.rb +34 -0
  24. data/lib/simple_form/components/placeholders.rb +16 -0
  25. data/lib/simple_form/components/readonly.rb +22 -0
  26. data/lib/simple_form/core_ext/hash.rb +16 -0
  27. data/lib/simple_form/error_notification.rb +48 -0
  28. data/lib/simple_form/form_builder.rb +472 -0
  29. data/lib/simple_form/helpers.rb +12 -0
  30. data/lib/simple_form/helpers/autofocus.rb +11 -0
  31. data/lib/simple_form/helpers/disabled.rb +15 -0
  32. data/lib/simple_form/helpers/readonly.rb +15 -0
  33. data/lib/simple_form/helpers/required.rb +35 -0
  34. data/lib/simple_form/helpers/validators.rb +44 -0
  35. data/lib/simple_form/i18n_cache.rb +22 -0
  36. data/lib/simple_form/inputs.rb +21 -0
  37. data/lib/simple_form/inputs/base.rb +162 -0
  38. data/lib/simple_form/inputs/block_input.rb +14 -0
  39. data/lib/simple_form/inputs/boolean_input.rb +64 -0
  40. data/lib/simple_form/inputs/collection_check_boxes_input.rb +21 -0
  41. data/lib/simple_form/inputs/collection_input.rb +101 -0
  42. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +63 -0
  43. data/lib/simple_form/inputs/collection_select_input.rb +14 -0
  44. data/lib/simple_form/inputs/date_time_input.rb +28 -0
  45. data/lib/simple_form/inputs/file_input.rb +9 -0
  46. data/lib/simple_form/inputs/grouped_collection_select_input.rb +41 -0
  47. data/lib/simple_form/inputs/hidden_input.rb +17 -0
  48. data/lib/simple_form/inputs/numeric_input.rb +24 -0
  49. data/lib/simple_form/inputs/password_input.rb +12 -0
  50. data/lib/simple_form/inputs/priority_input.rb +24 -0
  51. data/lib/simple_form/inputs/range_input.rb +14 -0
  52. data/lib/simple_form/inputs/string_input.rb +23 -0
  53. data/lib/simple_form/inputs/text_input.rb +11 -0
  54. data/lib/simple_form/map_type.rb +16 -0
  55. data/lib/simple_form/version.rb +3 -0
  56. data/lib/simple_form/wrappers.rb +8 -0
  57. data/lib/simple_form/wrappers/builder.rb +103 -0
  58. data/lib/simple_form/wrappers/many.rb +69 -0
  59. data/lib/simple_form/wrappers/root.rb +34 -0
  60. data/lib/simple_form/wrappers/single.rb +18 -0
  61. data/test/action_view_extensions/builder_test.rb +577 -0
  62. data/test/action_view_extensions/form_helper_test.rb +104 -0
  63. data/test/components/label_test.rb +310 -0
  64. data/test/form_builder/association_test.rb +177 -0
  65. data/test/form_builder/button_test.rb +47 -0
  66. data/test/form_builder/error_notification_test.rb +79 -0
  67. data/test/form_builder/error_test.rb +121 -0
  68. data/test/form_builder/general_test.rb +356 -0
  69. data/test/form_builder/hint_test.rb +139 -0
  70. data/test/form_builder/input_field_test.rb +63 -0
  71. data/test/form_builder/label_test.rb +71 -0
  72. data/test/form_builder/wrapper_test.rb +149 -0
  73. data/test/generators/simple_form_generator_test.rb +32 -0
  74. data/test/inputs/boolean_input_test.rb +108 -0
  75. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  76. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  77. data/test/inputs/collection_select_input_test.rb +241 -0
  78. data/test/inputs/datetime_input_test.rb +99 -0
  79. data/test/inputs/disabled_test.rb +38 -0
  80. data/test/inputs/discovery_test.rb +61 -0
  81. data/test/inputs/file_input_test.rb +16 -0
  82. data/test/inputs/general_test.rb +69 -0
  83. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  84. data/test/inputs/hidden_input_test.rb +30 -0
  85. data/test/inputs/numeric_input_test.rb +173 -0
  86. data/test/inputs/priority_input_test.rb +43 -0
  87. data/test/inputs/readonly_test.rb +61 -0
  88. data/test/inputs/required_test.rb +113 -0
  89. data/test/inputs/string_input_test.rb +140 -0
  90. data/test/inputs/text_input_test.rb +24 -0
  91. data/test/simple_form_test.rb +9 -0
  92. data/test/support/discovery_inputs.rb +21 -0
  93. data/test/support/misc_helpers.rb +102 -0
  94. data/test/support/mock_controller.rb +24 -0
  95. data/test/support/models.rb +210 -0
  96. data/test/test_helper.rb +90 -0
  97. 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,11 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Autofocus
4
+ private
5
+
6
+ def has_autofocus?
7
+ options[:autofocus] == true
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Disabled
4
+ private
5
+
6
+ def has_disabled?
7
+ options[:disabled] == true
8
+ end
9
+
10
+ def disabled_class
11
+ :disabled if has_disabled?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Readonly
4
+ private
5
+
6
+ def readonly_class
7
+ :readonly if has_readonly?
8
+ end
9
+
10
+ def has_readonly?
11
+ options[:readonly] == true
12
+ end
13
+ end
14
+ end
15
+ 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,14 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class BlockInput < Base
4
+ def initialize(*args, &block)
5
+ super
6
+ @block = block
7
+ end
8
+
9
+ def input
10
+ template.capture(&@block)
11
+ end
12
+ end
13
+ end
14
+ 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