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.

@@ -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
@@ -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(:"simple_form.required.html", default:
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(:"simple_form.required.text", default: 'required')
17
+ I18n.t(:"required.text", scope: i18n_scope, default: 'required')
18
18
  end
19
19
 
20
20
  def translate_required_mark
21
- I18n.t(:"simple_form.required.mark", default: '*')
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
- # Use validation with tokenizer if version of Rails is less than 5,
32
- # if not validate without the tokenizer, if version is greater than Rails 4.
33
- if ActionPack::VERSION::STRING < '5'
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
- # Use validation with tokenizer if version of Rails is less than 5,
32
- # if not validate without the tokenizer, if version is greater than Rails 4.
33
- if ActionPack::VERSION::STRING < '5'
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
@@ -8,7 +8,7 @@ module SimpleForm
8
8
  nil
9
9
  end
10
10
 
11
- def placeholder_text
11
+ def placeholder_text(wrapper_options = nil)
12
12
  placeholder = options[:placeholder]
13
13
  placeholder.is_a?(String) ? placeholder : translate_from_namespace(:placeholders)
14
14
  end
@@ -18,20 +18,20 @@ module SimpleForm
18
18
  extend MapType
19
19
  include SimpleForm::Inputs
20
20
 
21
- map_type :text, to: SimpleForm::Inputs::TextInput
22
- map_type :file, to: SimpleForm::Inputs::FileInput
23
- map_type :string, :email, :search, :tel, :url, :uuid, 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
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.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module SimpleForm
3
- VERSION = "3.5.1".freeze
3
+ VERSION = "4.0.0".freeze
4
4
  end
@@ -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