simple_form 3.5.1 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of simple_form might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +165 -3
- data/lib/generators/simple_form/templates/README +3 -3
- data/lib/generators/simple_form/templates/_form.html.erb +1 -0
- data/lib/generators/simple_form/templates/_form.html.haml +1 -0
- data/lib/generators/simple_form/templates/_form.html.slim +1 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +14 -2
- data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +357 -73
- data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +16 -5
- data/lib/simple_form.rb +51 -2
- data/lib/simple_form/components/errors.rb +8 -0
- data/lib/simple_form/components/labels.rb +9 -3
- data/lib/simple_form/components/maxlength.rb +3 -13
- data/lib/simple_form/components/minlength.rb +3 -13
- data/lib/simple_form/components/placeholders.rb +1 -1
- data/lib/simple_form/form_builder.rb +65 -16
- data/lib/simple_form/inputs/base.rb +18 -0
- data/lib/simple_form/version.rb +1 -1
- data/lib/simple_form/wrappers/root.rb +1 -0
- data/test/components/custom_components_test.rb +62 -0
- data/test/components/label_test.rb +28 -0
- data/test/form_builder/general_test.rb +34 -0
- data/test/form_builder/input_field_test.rb +25 -0
- data/test/form_builder/wrapper_test.rb +21 -2
- data/test/support/misc_helpers.rb +14 -0
- data/test/support/models.rb +9 -3
- metadata +4 -14
@@ -1,4 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Uncomment this and change the path if necessary to include your own
|
4
|
+
# components.
|
5
|
+
# See https://github.com/plataformatec/simple_form#custom-components to know
|
6
|
+
# more about custom components.
|
7
|
+
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
|
8
|
+
#
|
2
9
|
# Use this setup block to configure all options available in SimpleForm.
|
3
10
|
SimpleForm.setup do |config|
|
4
11
|
# Don't forget to edit this file to adapt it to your needs (specially
|
@@ -8,7 +15,7 @@ SimpleForm.setup do |config|
|
|
8
15
|
# doesn't provide styles for hints. You will need to provide your own CSS styles for hints.
|
9
16
|
# Uncomment them to enable hints.
|
10
17
|
|
11
|
-
config.wrappers :vertical_form, class: :input, hint_class: :field_with_hint, error_class: :error do |b|
|
18
|
+
config.wrappers :vertical_form, class: :input, hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
|
12
19
|
b.use :html5
|
13
20
|
b.use :placeholder
|
14
21
|
b.optional :maxlength
|
@@ -22,7 +29,7 @@ SimpleForm.setup do |config|
|
|
22
29
|
# b.use :hint, wrap_with: { tag: :span, class: :hint }
|
23
30
|
end
|
24
31
|
|
25
|
-
config.wrappers :horizontal_form, tag: 'div', class: 'row', hint_class: :field_with_hint, error_class: :error do |b|
|
32
|
+
config.wrappers :horizontal_form, tag: 'div', class: 'row', hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
|
26
33
|
b.use :html5
|
27
34
|
b.use :placeholder
|
28
35
|
b.optional :maxlength
|
@@ -32,7 +39,7 @@ SimpleForm.setup do |config|
|
|
32
39
|
b.optional :readonly
|
33
40
|
|
34
41
|
b.wrapper :label_wrapper, tag: :div, class: 'small-3 columns' do |ba|
|
35
|
-
ba.use :label, class: 'right inline'
|
42
|
+
ba.use :label, class: 'text-right inline'
|
36
43
|
end
|
37
44
|
|
38
45
|
b.wrapper :right_input_wrapper, tag: :div, class: 'small-9 columns' do |ba|
|
@@ -64,7 +71,7 @@ SimpleForm.setup do |config|
|
|
64
71
|
# Note that you need to adapt this wrapper to your needs. If you need a 4
|
65
72
|
# columns form then change the wrapper class to 'small-3', if you need
|
66
73
|
# only two use 'small-6' and so on.
|
67
|
-
config.wrappers :inline_form, tag: 'div', class: 'column small-4', hint_class: :field_with_hint, error_class: :error do |b|
|
74
|
+
config.wrappers :inline_form, tag: 'div', class: 'column small-4', hint_class: :field_with_hint, error_class: :error, valid_class: :valid do |b|
|
68
75
|
b.use :html5
|
69
76
|
b.use :placeholder
|
70
77
|
b.optional :maxlength
|
@@ -83,7 +90,7 @@ SimpleForm.setup do |config|
|
|
83
90
|
# Examples of use:
|
84
91
|
# - wrapper_html: {class: 'row'}, custom_wrapper_html: {class: 'column small-12'}
|
85
92
|
# - custom_wrapper_html: {class: 'column small-3 end'}
|
86
|
-
config.wrappers :customizable_wrapper, tag: 'div', error_class: :error do |b|
|
93
|
+
config.wrappers :customizable_wrapper, tag: 'div', error_class: :error, valid_class: :valid do |b|
|
87
94
|
b.use :html5
|
88
95
|
b.optional :readonly
|
89
96
|
|
@@ -108,4 +115,8 @@ SimpleForm.setup do |config|
|
|
108
115
|
|
109
116
|
# The default wrapper to be used by the FormBuilder.
|
110
117
|
config.default_wrapper = :vertical_form
|
118
|
+
|
119
|
+
# Defines validation classes to the input_field. By default it's nil.
|
120
|
+
# config.input_field_valid_class = 'is-valid'
|
121
|
+
# config.input_field_error_class = 'is-invalid'
|
111
122
|
end
|
data/lib/simple_form.rb
CHANGED
@@ -122,7 +122,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
|
|
122
122
|
|
123
123
|
# Collection of methods to detect if a file type was given.
|
124
124
|
mattr_accessor :file_methods
|
125
|
-
@@file_methods = %i[mounted_as file? public_filename]
|
125
|
+
@@file_methods = %i[mounted_as file? public_filename attached?]
|
126
126
|
|
127
127
|
# Custom mappings for input types. This should be a hash containing a regexp
|
128
128
|
# to match as key, and the input type that will be used when the field name
|
@@ -200,6 +200,12 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
|
|
200
200
|
mattr_accessor :i18n_scope
|
201
201
|
@@i18n_scope = 'simple_form'
|
202
202
|
|
203
|
+
mattr_accessor :input_field_error_class
|
204
|
+
@@input_field_error_class = nil
|
205
|
+
|
206
|
+
mattr_accessor :input_field_valid_class
|
207
|
+
@@input_field_valid_class = nil
|
208
|
+
|
203
209
|
# Retrieves a given wrapper
|
204
210
|
def self.wrapper(name)
|
205
211
|
@@wrappers[name.to_s] or raise WrapperNotFound, "Couldn't find wrapper with name #{name}"
|
@@ -229,7 +235,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
|
|
229
235
|
SimpleForm::Wrappers::Root.new(builder.to_a, options)
|
230
236
|
end
|
231
237
|
|
232
|
-
wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors do |b|
|
238
|
+
wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
|
233
239
|
b.use :html5
|
234
240
|
|
235
241
|
b.use :min_max
|
@@ -265,6 +271,49 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
|
|
265
271
|
@@configured = true
|
266
272
|
yield self
|
267
273
|
end
|
274
|
+
|
275
|
+
# Includes a component to be used by Simple Form. Methods defined in a
|
276
|
+
# component will be exposed to be used in the wrapper as Simple::Components
|
277
|
+
#
|
278
|
+
# Examples
|
279
|
+
#
|
280
|
+
# # The application needs to tell where the components will be.
|
281
|
+
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
|
282
|
+
#
|
283
|
+
# # Create a custom component in the path specified above.
|
284
|
+
# # lib/components/input_group_component.rb
|
285
|
+
# module InputGroupComponent
|
286
|
+
# def prepend
|
287
|
+
# ...
|
288
|
+
# end
|
289
|
+
#
|
290
|
+
# def append
|
291
|
+
# ...
|
292
|
+
# end
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# SimpleForm.setup do |config|
|
296
|
+
# # Create a wrapper using the custom component.
|
297
|
+
# config.wrappers :input_group, tag: :div, error_class: :error do |b|
|
298
|
+
# b.use :label
|
299
|
+
# b.optional :prepend
|
300
|
+
# b.use :input
|
301
|
+
# b.use :append
|
302
|
+
# end
|
303
|
+
# end
|
304
|
+
#
|
305
|
+
# # Using the custom component in the form.
|
306
|
+
# <%= simple_form_for @blog, wrapper: input_group do |f| %>
|
307
|
+
# <%= f.input :title, prepend: true %>
|
308
|
+
# <% end %>
|
309
|
+
#
|
310
|
+
def self.include_component(component)
|
311
|
+
if Module === component
|
312
|
+
SimpleForm::Inputs::Base.include(component)
|
313
|
+
else
|
314
|
+
raise TypeError, "SimpleForm.include_component expects a module but got: #{component.class}"
|
315
|
+
end
|
316
|
+
end
|
268
317
|
end
|
269
318
|
|
270
319
|
require 'simple_form/railtie' if defined?(Rails)
|
@@ -14,6 +14,14 @@ module SimpleForm
|
|
14
14
|
object && object.respond_to?(:errors) && errors.present?
|
15
15
|
end
|
16
16
|
|
17
|
+
def has_value?
|
18
|
+
object && object.respond_to?(attribute_name) && object.send(attribute_name).present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
!has_errors? && has_value?
|
23
|
+
end
|
24
|
+
|
17
25
|
protected
|
18
26
|
|
19
27
|
def error_text
|
@@ -7,18 +7,24 @@ module SimpleForm
|
|
7
7
|
module ClassMethods #:nodoc:
|
8
8
|
def translate_required_html
|
9
9
|
i18n_cache :translate_required_html do
|
10
|
-
I18n.t(:"
|
10
|
+
I18n.t(:"required.html", scope: i18n_scope, default:
|
11
11
|
%(<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>)
|
12
12
|
)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def translate_required_text
|
17
|
-
I18n.t(:"
|
17
|
+
I18n.t(:"required.text", scope: i18n_scope, default: 'required')
|
18
18
|
end
|
19
19
|
|
20
20
|
def translate_required_mark
|
21
|
-
I18n.t(:"
|
21
|
+
I18n.t(:"required.mark", scope: i18n_scope, default: '*')
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def i18n_scope
|
27
|
+
SimpleForm.i18n_scope
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -28,19 +28,9 @@ module SimpleForm
|
|
28
28
|
length_validator.options[:tokenizer]
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def maximum_length_value_from(length_validator)
|
35
|
-
if length_validator && !has_tokenizer?(length_validator)
|
36
|
-
length_validator.options[:is] || length_validator.options[:maximum]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
elsif ActionPack::VERSION::STRING >= '5'
|
40
|
-
def maximum_length_value_from(length_validator)
|
41
|
-
if length_validator
|
42
|
-
length_validator.options[:is] || length_validator.options[:maximum]
|
43
|
-
end
|
31
|
+
def maximum_length_value_from(length_validator)
|
32
|
+
if length_validator
|
33
|
+
length_validator.options[:is] || length_validator.options[:maximum]
|
44
34
|
end
|
45
35
|
end
|
46
36
|
end
|
@@ -28,19 +28,9 @@ module SimpleForm
|
|
28
28
|
length_validator.options[:tokenizer]
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def minimum_length_value_from(length_validator)
|
35
|
-
if length_validator && !has_tokenizer?(length_validator)
|
36
|
-
length_validator.options[:is] || length_validator.options[:minimum]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
elsif ActionPack::VERSION::STRING >= '5'
|
40
|
-
def minimum_length_value_from(length_validator)
|
41
|
-
if length_validator
|
42
|
-
length_validator.options[:is] || length_validator.options[:minimum]
|
43
|
-
end
|
31
|
+
def minimum_length_value_from(length_validator)
|
32
|
+
if length_validator
|
33
|
+
length_validator.options[:is] || length_validator.options[:minimum]
|
44
34
|
end
|
45
35
|
end
|
46
36
|
end
|
@@ -18,20 +18,20 @@ module SimpleForm
|
|
18
18
|
extend MapType
|
19
19
|
include SimpleForm::Inputs
|
20
20
|
|
21
|
-
map_type :text,
|
22
|
-
map_type :file,
|
23
|
-
map_type :string, :email, :search, :tel, :url, :uuid, to: SimpleForm::Inputs::StringInput
|
24
|
-
map_type :password,
|
25
|
-
map_type :integer, :decimal, :float,
|
26
|
-
map_type :range,
|
27
|
-
map_type :check_boxes,
|
28
|
-
map_type :radio_buttons,
|
29
|
-
map_type :select,
|
30
|
-
map_type :grouped_select,
|
31
|
-
map_type :date, :time, :datetime,
|
32
|
-
map_type :country, :time_zone,
|
33
|
-
map_type :boolean,
|
34
|
-
map_type :hidden,
|
21
|
+
map_type :text, :hstore, :json, :jsonb, to: SimpleForm::Inputs::TextInput
|
22
|
+
map_type :file, to: SimpleForm::Inputs::FileInput
|
23
|
+
map_type :string, :email, :search, :tel, :url, :uuid, :citext, to: SimpleForm::Inputs::StringInput
|
24
|
+
map_type :password, to: SimpleForm::Inputs::PasswordInput
|
25
|
+
map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
|
26
|
+
map_type :range, to: SimpleForm::Inputs::RangeInput
|
27
|
+
map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
|
28
|
+
map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
|
29
|
+
map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
|
30
|
+
map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
|
31
|
+
map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
|
32
|
+
map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
|
33
|
+
map_type :boolean, to: SimpleForm::Inputs::BooleanInput
|
34
|
+
map_type :hidden, to: SimpleForm::Inputs::HiddenInput
|
35
35
|
|
36
36
|
def self.discovery_cache
|
37
37
|
@discovery_cache ||= {}
|
@@ -138,6 +138,29 @@ module SimpleForm
|
|
138
138
|
# <input class="string required" id="user_name" maxlength="100"
|
139
139
|
# name="user[name]" type="text" value="Carlos" />
|
140
140
|
#
|
141
|
+
# It also support validation classes once it is configured.
|
142
|
+
#
|
143
|
+
# # config/initializers/simple_form.rb
|
144
|
+
# SimpleForm.setup do |config|
|
145
|
+
# config.input_field_valid_class = 'is-valid'
|
146
|
+
# config.input_field_error_class = 'is-invalid'
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# simple_form_for @user do |f|
|
150
|
+
# f.input_field :name
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# When the validation happens, the input will be rendered with
|
154
|
+
# the class configured according to the validation:
|
155
|
+
#
|
156
|
+
# - when the input is valid:
|
157
|
+
#
|
158
|
+
# <input class="is-valid string required" id="user_name" value="Carlos" />
|
159
|
+
#
|
160
|
+
# - when the input is invalid:
|
161
|
+
#
|
162
|
+
# <input class="is-invalid string required" id="user_name" value="" />
|
163
|
+
#
|
141
164
|
def input_field(attribute_name, options = {})
|
142
165
|
components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
|
143
166
|
|
@@ -147,7 +170,7 @@ module SimpleForm
|
|
147
170
|
|
148
171
|
input = find_input(attribute_name, options)
|
149
172
|
wrapper = find_wrapper(input.input_type, options)
|
150
|
-
components = components.
|
173
|
+
components = build_input_field_components(components.push(:input))
|
151
174
|
|
152
175
|
SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
|
153
176
|
end
|
@@ -527,7 +550,7 @@ module SimpleForm
|
|
527
550
|
case input_type
|
528
551
|
when :timestamp
|
529
552
|
:datetime
|
530
|
-
when :string, nil
|
553
|
+
when :string, :citext, nil
|
531
554
|
case attribute_name.to_s
|
532
555
|
when /password/ then :password
|
533
556
|
when /time_zone/ then :time_zone
|
@@ -646,5 +669,31 @@ module SimpleForm
|
|
646
669
|
|
647
670
|
nil
|
648
671
|
end
|
672
|
+
|
673
|
+
def build_input_field_components(components)
|
674
|
+
components.map do |component|
|
675
|
+
if component == :input
|
676
|
+
SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
|
677
|
+
else
|
678
|
+
SimpleForm::Wrappers::Leaf.new(component)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
def build_input_field_options
|
684
|
+
input_field_options = {}
|
685
|
+
valid_class = SimpleForm.input_field_valid_class
|
686
|
+
error_class = SimpleForm.input_field_error_class
|
687
|
+
|
688
|
+
if error_class.present?
|
689
|
+
input_field_options[:error_class] = error_class
|
690
|
+
end
|
691
|
+
|
692
|
+
if valid_class.present?
|
693
|
+
input_field_options[:valid_class] = valid_class
|
694
|
+
end
|
695
|
+
|
696
|
+
input_field_options
|
697
|
+
end
|
649
698
|
end
|
650
699
|
end
|
@@ -191,6 +191,8 @@ module SimpleForm
|
|
191
191
|
|
192
192
|
def merge_wrapper_options(options, wrapper_options)
|
193
193
|
if wrapper_options
|
194
|
+
wrapper_options = set_input_classes(wrapper_options)
|
195
|
+
|
194
196
|
wrapper_options.merge(options) do |key, oldval, newval|
|
195
197
|
case key.to_s
|
196
198
|
when "class"
|
@@ -206,6 +208,22 @@ module SimpleForm
|
|
206
208
|
end
|
207
209
|
end
|
208
210
|
|
211
|
+
def set_input_classes(wrapper_options)
|
212
|
+
wrapper_options = wrapper_options.dup
|
213
|
+
error_class = wrapper_options.delete(:error_class)
|
214
|
+
valid_class = wrapper_options.delete(:valid_class)
|
215
|
+
|
216
|
+
if error_class.present? && has_errors?
|
217
|
+
wrapper_options[:class] = "#{wrapper_options[:class]} #{error_class}"
|
218
|
+
end
|
219
|
+
|
220
|
+
if valid_class.present? && valid?
|
221
|
+
wrapper_options[:class] = "#{wrapper_options[:class]} #{valid_class}"
|
222
|
+
end
|
223
|
+
|
224
|
+
wrapper_options
|
225
|
+
end
|
226
|
+
|
209
227
|
def i18n_scope
|
210
228
|
SimpleForm.i18n_scope
|
211
229
|
end
|
data/lib/simple_form/version.rb
CHANGED
@@ -30,6 +30,7 @@ module SimpleForm
|
|
30
30
|
end
|
31
31
|
css << (options[:wrapper_error_class] || @defaults[:error_class]) if input.has_errors?
|
32
32
|
css << (options[:wrapper_hint_class] || @defaults[:hint_class]) if input.has_hint?
|
33
|
+
css << (options[:wrapper_valid_class] || @defaults[:valid_class]) if input.valid?
|
33
34
|
css.compact
|
34
35
|
end
|
35
36
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
# Module that represents a custom component.
|
6
|
+
module Numbers
|
7
|
+
def number(wrapper_options = nil)
|
8
|
+
@number ||= options[:number].to_s.html_safe
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Module that represents a custom component.
|
13
|
+
module InputGroup
|
14
|
+
def prepend(wrapper_options = nil)
|
15
|
+
span_tag = content_tag(:span, options[:prepend], class: 'input-group-text')
|
16
|
+
template.content_tag(:div, span_tag, class: 'input-group-prepend')
|
17
|
+
end
|
18
|
+
|
19
|
+
def append(wrapper_options = nil)
|
20
|
+
span_tag = content_tag(:span, options[:append], class: 'input-group-text')
|
21
|
+
template.content_tag(:div, span_tag, class: 'input-group-append')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class CustomComponentsTest < ActionView::TestCase
|
26
|
+
test 'includes the custom components' do
|
27
|
+
SimpleForm.include_component Numbers
|
28
|
+
|
29
|
+
custom_wrapper = SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
|
30
|
+
b.use :number, wrap_with: { tag: 'div', class: 'number' }
|
31
|
+
end
|
32
|
+
|
33
|
+
with_form_for @user, :name, number: 1, wrapper: custom_wrapper
|
34
|
+
|
35
|
+
assert_select 'div.number', text: '1'
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'includes custom components and use it as optional in the wrapper' do
|
39
|
+
SimpleForm.include_component InputGroup
|
40
|
+
|
41
|
+
custom_wrapper = SimpleForm.build tag: :div, class: 'custom_wrapper' do |b|
|
42
|
+
b.use :label
|
43
|
+
b.optional :prepend
|
44
|
+
b.use :input
|
45
|
+
b.use :append
|
46
|
+
end
|
47
|
+
|
48
|
+
with_form_for @user, :name, prepend: true, wrapper: custom_wrapper
|
49
|
+
|
50
|
+
assert_select 'div.input-group-prepend > span.input-group-text'
|
51
|
+
assert_select 'div.input-group-append > span.input-group-text'
|
52
|
+
end
|
53
|
+
|
54
|
+
test 'raises a TypeError when the component is not a Module' do
|
55
|
+
component = 'MyComponent'
|
56
|
+
|
57
|
+
exception = assert_raises TypeError do
|
58
|
+
SimpleForm.include_component(component)
|
59
|
+
end
|
60
|
+
assert_equal exception.message, "SimpleForm.include_component expects a module but got: String"
|
61
|
+
end
|
62
|
+
end
|