simple_form 3.5.1 → 4.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.

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