bootstrap_form 4.4.0 → 5.1.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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +48 -0
  3. data/.gitignore +28 -3
  4. data/.rubocop.yml +19 -15
  5. data/CHANGELOG.md +82 -1
  6. data/CONTRIBUTING.md +73 -12
  7. data/Dangerfile +5 -7
  8. data/Dockerfile +21 -0
  9. data/Gemfile +8 -11
  10. data/README.md +829 -82
  11. data/RELEASING.md +5 -10
  12. data/UPGRADE-4.0.md +1 -1
  13. data/UPGRADE-5.0.md +25 -0
  14. data/bootstrap_form.gemspec +8 -5
  15. data/demo/.ruby-version +1 -0
  16. data/demo/Gemfile +80 -0
  17. data/demo/Gemfile.lock +261 -0
  18. data/demo/Procfile.dev +2 -0
  19. data/demo/app/assets/builds/.keep +0 -0
  20. data/demo/app/assets/builds/application.js.LICENSE.txt +9 -0
  21. data/demo/app/assets/config/manifest.js +2 -2
  22. data/demo/app/assets/stylesheets/actiontext.css +31 -0
  23. data/demo/app/assets/stylesheets/application.scss +1 -1
  24. data/demo/app/controllers/bootstrap_controller.rb +17 -2
  25. data/demo/app/controllers/users_controller.rb +9 -0
  26. data/demo/app/helpers/bootstrap_helper.rb +5 -5
  27. data/demo/app/javascript/application.js +3 -0
  28. data/demo/app/models/skill.rb +15 -0
  29. data/demo/app/models/user.rb +14 -0
  30. data/demo/app/views/active_storage/blobs/_blob.html.erb +1 -1
  31. data/demo/app/views/bootstrap/form.html.erb +13 -0
  32. data/demo/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  33. data/demo/app/views/layouts/application.html.erb +28 -20
  34. data/demo/bin/dev +9 -0
  35. data/demo/config/environments/development.rb +3 -3
  36. data/demo/config/puma.rb +2 -2
  37. data/demo/config/routes.rb +1 -0
  38. data/demo/db/schema.rb +31 -16
  39. data/demo/doc/screenshots/bootstrap/index/00_horizontal_form.png +0 -0
  40. data/demo/doc/screenshots/bootstrap/index/01_with_validation_error.png +0 -0
  41. data/demo/doc/screenshots/bootstrap/index/02_inline_form.png +0 -0
  42. data/demo/doc/screenshots/bootstrap/index/03_simple_action_text_example.png +0 -0
  43. data/demo/doc/screenshots/bootstrap/index/04_floating_labels.png +0 -0
  44. data/demo/doc/screenshots/bootstrap/readme/00_example.png +0 -0
  45. data/demo/doc/screenshots/bootstrap/readme/01_example.png +0 -0
  46. data/demo/doc/screenshots/bootstrap/readme/02_example.png +0 -0
  47. data/demo/doc/screenshots/bootstrap/readme/03_example.png +0 -0
  48. data/demo/doc/screenshots/bootstrap/readme/04_example.png +0 -0
  49. data/demo/doc/screenshots/bootstrap/readme/05_example.png +0 -0
  50. data/demo/doc/screenshots/bootstrap/readme/06_example.png +0 -0
  51. data/demo/doc/screenshots/bootstrap/readme/07_example.png +0 -0
  52. data/demo/doc/screenshots/bootstrap/readme/08_example.png +0 -0
  53. data/demo/doc/screenshots/bootstrap/readme/09_example.png +0 -0
  54. data/demo/doc/screenshots/bootstrap/readme/10_example.png +0 -0
  55. data/demo/doc/screenshots/bootstrap/readme/11_example.png +0 -0
  56. data/demo/doc/screenshots/bootstrap/readme/12_example.png +0 -0
  57. data/demo/doc/screenshots/bootstrap/readme/13_example.png +0 -0
  58. data/demo/doc/screenshots/bootstrap/readme/14_example.png +0 -0
  59. data/demo/doc/screenshots/bootstrap/readme/15_example.png +0 -0
  60. data/demo/doc/screenshots/bootstrap/readme/16_example.png +0 -0
  61. data/demo/doc/screenshots/bootstrap/readme/17_example.png +0 -0
  62. data/demo/doc/screenshots/bootstrap/readme/18_example.png +0 -0
  63. data/demo/doc/screenshots/bootstrap/readme/19_example.png +0 -0
  64. data/demo/doc/screenshots/bootstrap/readme/20_example.png +0 -0
  65. data/demo/doc/screenshots/bootstrap/readme/21_example.png +0 -0
  66. data/demo/doc/screenshots/bootstrap/readme/22_example.png +0 -0
  67. data/demo/doc/screenshots/bootstrap/readme/23_example.png +0 -0
  68. data/demo/doc/screenshots/bootstrap/readme/24_example.png +0 -0
  69. data/demo/doc/screenshots/bootstrap/readme/25_example.png +0 -0
  70. data/demo/doc/screenshots/bootstrap/readme/26_example.png +0 -0
  71. data/demo/doc/screenshots/bootstrap/readme/27_example.png +0 -0
  72. data/demo/doc/screenshots/bootstrap/readme/28_example.png +0 -0
  73. data/demo/doc/screenshots/bootstrap/readme/29_example.png +0 -0
  74. data/demo/doc/screenshots/bootstrap/readme/30_example.png +0 -0
  75. data/demo/doc/screenshots/bootstrap/readme/31_example.png +0 -0
  76. data/demo/doc/screenshots/bootstrap/readme/32_example.png +0 -0
  77. data/demo/doc/screenshots/bootstrap/readme/33_example.png +0 -0
  78. data/demo/doc/screenshots/bootstrap/readme/34_example.png +0 -0
  79. data/demo/doc/screenshots/bootstrap/readme/35_example.png +0 -0
  80. data/demo/doc/screenshots/bootstrap/readme/36_example.png +0 -0
  81. data/demo/doc/screenshots/bootstrap/readme/37_example.png +0 -0
  82. data/demo/doc/screenshots/bootstrap/readme/38_example.png +0 -0
  83. data/demo/doc/screenshots/bootstrap/readme/39_example.png +0 -0
  84. data/demo/doc/screenshots/bootstrap/readme/40_example.png +0 -0
  85. data/demo/doc/screenshots/bootstrap/readme/41_example.png +0 -0
  86. data/demo/doc/screenshots/bootstrap/readme/42_example.png +0 -0
  87. data/demo/doc/screenshots/bootstrap/readme/43_example.png +0 -0
  88. data/demo/doc/screenshots/bootstrap/readme/44_example.png +0 -0
  89. data/demo/doc/screenshots/bootstrap/readme/45_example.png +0 -0
  90. data/demo/doc/screenshots/bootstrap/readme/46_example.png +0 -0
  91. data/demo/doc/screenshots/bootstrap/readme/47_example.png +0 -0
  92. data/demo/doc/screenshots/bootstrap/readme/48_example.png +0 -0
  93. data/demo/doc/screenshots/bootstrap/readme/49_example.png +0 -0
  94. data/demo/doc/screenshots/bootstrap/readme/50_example.png +0 -0
  95. data/demo/package.json +10 -6
  96. data/demo/test/application_system_test_case.rb +8 -0
  97. data/demo/test/controllers/bootstrap_controller_test.rb +8 -0
  98. data/demo/test/controllers/users_controller_test.rb +13 -0
  99. data/demo/test/fixtures/users.yml +2 -0
  100. data/demo/test/system/bootstrap_test.rb +84 -0
  101. data/demo/test/test_helper.rb +10 -0
  102. data/demo/webpack.config.js +20 -0
  103. data/demo/yarn.lock +4063 -3144
  104. data/docker-compose.yml +49 -0
  105. data/gemfiles/5.2.gemfile +2 -15
  106. data/gemfiles/6.0.gemfile +2 -17
  107. data/gemfiles/6.1.gemfile +4 -0
  108. data/gemfiles/7.0.gemfile +6 -0
  109. data/gemfiles/edge.gemfile +2 -17
  110. data/lib/bootstrap_form/action_view_extensions/form_helper.rb +1 -1
  111. data/lib/bootstrap_form/components/hints.rb +13 -4
  112. data/lib/bootstrap_form/components/labels.rb +2 -2
  113. data/lib/bootstrap_form/components/validation.rb +1 -1
  114. data/lib/bootstrap_form/configuration.rb +22 -0
  115. data/lib/bootstrap_form/form_builder.rb +10 -12
  116. data/lib/bootstrap_form/form_group.rb +26 -11
  117. data/lib/bootstrap_form/form_group_builder.rb +6 -8
  118. data/lib/bootstrap_form/helpers/bootstrap.rb +17 -12
  119. data/lib/bootstrap_form/inputs/base.rb +5 -5
  120. data/lib/bootstrap_form/inputs/check_box.rb +11 -23
  121. data/lib/bootstrap_form/inputs/collection_check_boxes.rb +5 -1
  122. data/lib/bootstrap_form/inputs/collection_select.rb +2 -1
  123. data/lib/bootstrap_form/inputs/file_field.rb +3 -15
  124. data/lib/bootstrap_form/inputs/grouped_collection_select.rb +2 -1
  125. data/lib/bootstrap_form/inputs/radio_button.rb +17 -30
  126. data/lib/bootstrap_form/inputs/select.rb +1 -0
  127. data/lib/bootstrap_form/inputs/time_zone_select.rb +1 -0
  128. data/lib/bootstrap_form/version.rb +1 -1
  129. data/lib/bootstrap_form.rb +17 -7
  130. metadata +94 -16
  131. data/.travis.yml +0 -42
  132. data/demo/config/initializers/assets.rb +0 -14
  133. data/gemfiles/5.0.gemfile +0 -18
  134. data/gemfiles/5.1.gemfile +0 -17
@@ -0,0 +1,49 @@
1
+ version: '3.3'
2
+
3
+ services:
4
+ app: &app
5
+ build:
6
+ context: .
7
+ args:
8
+ NODE_MAJOR: '12'
9
+ YARN_VERSION: '1.22.4'
10
+ image: bootstrap-form:0.0.1
11
+ tmpfs:
12
+ - /tmp
13
+
14
+ shell: &shell
15
+ <<: *app
16
+ stdin_open: true
17
+ tty: true
18
+ volumes:
19
+ - .:/app:cached
20
+ # - rails_cache:/app/tmp/cache
21
+ # - bundle:/app/vendor/bundle
22
+ # - node_modules:/app/node_modules
23
+ # - packs:/app/public/packs
24
+ - /etc/passwd:/etc/passwd:ro
25
+ # One or the other of the following lines might be redundant, or one might be
26
+ # better than the other.
27
+ - ~/.ssh:${HOME}/.ssh
28
+ - ${SSH_AUTH_SOCK}:/ssh-agent
29
+ environment:
30
+ - SSH_AUTH_SOCK=/ssh-agent
31
+ - NODE_ENV=development
32
+ - RAILS_ENV=${RAILS_ENV:-development}
33
+ - BOOTSNAP_CACHE_DIR=/usr/local/bundle/_bootsnap
34
+ - WEBPACKER_DEV_SERVER_HOST=webpacker
35
+ - WEB_CONCURRENCY=1
36
+ - HISTFILE=/app/.bash_history
37
+ command: /bin/bash
38
+
39
+ # server:
40
+ # <<: *shell
41
+ # command: sh -c "cd demo/app && bundle exec rails server -b 0.0.0.0"
42
+ # ports:
43
+ # - '3000:3000'
44
+
45
+ # volumes:
46
+ # bundle:
47
+ # node_modules:
48
+ # rails_cache:
49
+ # packs:
data/gemfiles/5.2.gemfile CHANGED
@@ -1,17 +1,4 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec path: "../"
1
+ gems = "#{File.dirname __dir__}/Gemfile"
2
+ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
4
3
 
5
4
  gem "rails", "~> 5.2.0"
6
-
7
- group :development do
8
- gem "rubocop-rails", require: false
9
- end
10
-
11
- group :test do
12
- gem "diffy"
13
- gem "equivalent-xml"
14
- gem "mocha"
15
- gem "sqlite3", "~> 1.4"
16
- gem "timecop", "~> 0.7.1"
17
- end
data/gemfiles/6.0.gemfile CHANGED
@@ -1,19 +1,4 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec path: "../"
1
+ gems = "#{File.dirname __dir__}/Gemfile"
2
+ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
4
3
 
5
4
  gem "rails", "~> 6.0.0"
6
-
7
- group :development do
8
- gem "rubocop-rails", require: false
9
- gem "sassc"
10
- gem "webpacker", ">= 4.0.0.rc.3"
11
- end
12
-
13
- group :test do
14
- gem "diffy"
15
- gem "equivalent-xml"
16
- gem "mocha"
17
- gem "sqlite3"
18
- gem "timecop", "~> 0.7.1"
19
- end
@@ -0,0 +1,4 @@
1
+ gems = "#{File.dirname __dir__}/Gemfile"
2
+ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
+
4
+ gem "rails", "~> 6.1.0"
@@ -0,0 +1,6 @@
1
+ gems = "#{File.dirname __dir__}/Gemfile"
2
+ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
+
4
+ # 7.0.0 has an issue with Ruby 3.1.
5
+ # And a test case has an issue with 7.0.1
6
+ gem "rails", "~> 7.0.2"
@@ -1,19 +1,4 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec path: "../"
1
+ gems = "#{File.dirname __dir__}/Gemfile"
2
+ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
4
3
 
5
4
  gem "rails", git: "https://github.com/rails/rails.git"
6
-
7
- group :development do
8
- gem "rubocop-rails", require: false
9
- gem "sassc-rails"
10
- gem "webpacker"
11
- end
12
-
13
- group :test do
14
- gem "diffy"
15
- gem "equivalent-xml"
16
- gem "mocha"
17
- gem "sqlite3"
18
- gem "timecop", "~> 0.7.1"
19
- end
@@ -29,7 +29,7 @@ module BootstrapForm
29
29
  options.reverse_merge!(builder: BootstrapForm::FormBuilder)
30
30
 
31
31
  with_bootstrap_form_field_error_proc do
32
- form_with(options, &block)
32
+ form_with(**options, &block)
33
33
  end
34
34
  end
35
35
 
@@ -8,7 +8,7 @@ module BootstrapForm
8
8
  private
9
9
 
10
10
  def generate_help(name, help_text)
11
- return if help_text == false || inline_error?(name)
11
+ return if help_text == false
12
12
 
13
13
  help_klass ||= "form-text text-muted"
14
14
  help_text ||= get_help_text_by_i18n_key(name)
@@ -20,10 +20,10 @@ module BootstrapForm
20
20
  def get_help_text_by_i18n_key(name)
21
21
  return unless object
22
22
 
23
- partial_scope = if object.class.respond_to?(:model_name)
24
- object.class.model_name.name
23
+ partial_scope = if object_class.respond_to?(:model_name)
24
+ object_class.model_name.name
25
25
  else
26
- object.class.name
26
+ object_class.name
27
27
  end
28
28
 
29
29
  # First check for a subkey :html, as it is also accepted by i18n, and the
@@ -38,6 +38,15 @@ module BootstrapForm
38
38
  help_text
39
39
  end
40
40
 
41
+ def object_class
42
+ if !object.class.is_a?(ActiveModel::Naming) &&
43
+ object.respond_to?(:klass) && object.klass.is_a?(ActiveModel::Naming)
44
+ object.klass
45
+ else
46
+ object.class
47
+ end
48
+ end
49
+
41
50
  def scoped_help_text(name, partial_scope)
42
51
  underscored_scope = "activerecord.help.#{partial_scope.underscore}"
43
52
  downcased_scope = "activerecord.help.#{partial_scope.downcase}"
@@ -23,7 +23,7 @@ module BootstrapForm
23
23
  end
24
24
 
25
25
  def label_classes(name, options, custom_label_col, group_layout)
26
- classes = [options[:class], label_layout_classes(custom_label_col, group_layout)]
26
+ classes = ["form-label", options[:class], label_layout_classes(custom_label_col, group_layout)]
27
27
 
28
28
  case options.delete(:required)
29
29
  when true
@@ -48,7 +48,7 @@ module BootstrapForm
48
48
  if label_errors && error?(name)
49
49
  (options[:text] || object.class.human_attribute_name(name)).to_s.concat(" #{get_error_messages(name)}")
50
50
  else
51
- options[:text]
51
+ options[:text] || object&.class.try(:human_attribute_name, name)
52
52
  end
53
53
  end
54
54
  end
@@ -14,7 +14,7 @@ module BootstrapForm
14
14
  def required_attribute?(obj, attribute)
15
15
  return false unless obj && attribute
16
16
 
17
- target = obj.class == Class ? obj : obj.class
17
+ target = obj.instance_of?(Class) ? obj : obj.class
18
18
 
19
19
  target_validators = if target.respond_to? :validators_on
20
20
  target.validators_on(attribute).map(&:class)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ class Configuration
5
+ def default_form_attributes=(attributes)
6
+ case attributes
7
+ when nil
8
+ @default_form_attributes = {}
9
+ when Hash
10
+ @default_form_attributes = attributes
11
+ else
12
+ raise ArgumentError, "Unsupported default_form_attributes #{attributes.inspect}"
13
+ end
14
+ end
15
+
16
+ def default_form_attributes
17
+ return @default_form_attributes if defined? @default_form_attributes
18
+
19
+ {}
20
+ end
21
+ end
22
+ end
@@ -43,9 +43,8 @@ module BootstrapForm
43
43
  include BootstrapForm::Inputs::UrlField
44
44
  include BootstrapForm::Inputs::WeekField
45
45
 
46
- delegate :content_tag, :capture, :concat, to: :@template
46
+ delegate :content_tag, :capture, :concat, :tag, to: :@template
47
47
 
48
- # rubocop:disable Metrics/AbcSize
49
48
  def initialize(object_name, object, template, options)
50
49
  @layout = options[:layout] || default_layout
51
50
  @label_col = options[:label_col] || default_label_col
@@ -58,24 +57,24 @@ module BootstrapForm
58
57
  options[:inline_errors] != false
59
58
  end
60
59
  @acts_like_form_tag = options[:acts_like_form_tag]
61
- add_form_role_and_form_inline options
60
+ add_default_form_attributes_and_form_inline options
62
61
  super
63
62
  end
64
- # rubocop:enable Metrics/AbcSize
65
63
 
66
- def add_form_role_and_form_inline(options)
64
+ def add_default_form_attributes_and_form_inline(options)
67
65
  options[:html] ||= {}
68
- options[:html][:role] ||= "form"
66
+ options[:html].reverse_merge!(BootstrapForm.config.default_form_attributes)
69
67
 
70
68
  return unless options[:layout] == :inline
71
69
 
72
- options[:html][:class] = [options[:html][:class], "form-inline"].compact.join(" ")
70
+ options[:html][:class] =
71
+ ([*options[:html][:class]&.split(/\s+/)] + %w[row row-cols-auto g-3 align-items-center])
72
+ .compact.uniq.join(" ")
73
73
  end
74
74
 
75
75
  def fields_for_with_bootstrap(record_name, record_object=nil, fields_options={}, &block)
76
76
  fields_options = fields_for_options(record_object, fields_options)
77
- record_object.is_a?(Hash) && record_object.extractable_options? &&
78
- record_object = nil
77
+ record_object = nil if record_object.is_a?(Hash) && record_object.extractable_options?
79
78
  fields_for_without_bootstrap(record_name, record_object, fields_options, &block)
80
79
  end
81
80
 
@@ -89,8 +88,7 @@ module BootstrapForm
89
88
 
90
89
  def fields_for_options(record_object, fields_options)
91
90
  field_options = fields_options
92
- record_object.is_a?(Hash) && record_object.extractable_options? &&
93
- field_options = record_object
91
+ field_options = record_object if record_object.is_a?(Hash) && record_object.extractable_options?
94
92
  %i[layout control_col inline_errors label_errors].each do |option|
95
93
  field_options[option] ||= options[option]
96
94
  end
@@ -116,7 +114,7 @@ module BootstrapForm
116
114
  end
117
115
 
118
116
  def hide_class
119
- "sr-only" # still accessible for screen readers
117
+ "visually-hidden" # still accessible for screen readers
120
118
  end
121
119
 
122
120
  def control_class
@@ -10,9 +10,9 @@ module BootstrapForm
10
10
 
11
11
  options[:class] = form_group_classes(options)
12
12
 
13
- content_tag(:div, options.except(:append, :id, :label, :help, :icon,
14
- :input_group_class, :label_col, :control_col,
15
- :add_control_col_class, :layout, :prepend)) do
13
+ tag.div(**options.except(:append, :id, :label, :help, :icon,
14
+ :input_group_class, :label_col, :control_col,
15
+ :add_control_col_class, :layout, :prepend, :floating)) do
16
16
  form_group_content(
17
17
  generate_label(options[:id], name, options[:label], options[:label_col], options[:layout]),
18
18
  generate_help(name, options[:help]), options, &block
@@ -24,8 +24,8 @@ module BootstrapForm
24
24
 
25
25
  def form_group_content_tag(name, field_name, without_field_name, options, html_options)
26
26
  html_class = control_specific_class(field_name)
27
- html_class = "#{html_class} form-inline" if @layout == :horizontal && options[:skip_inline].blank?
28
- content_tag(:div, class: html_class) do
27
+ html_class = "#{html_class} col-auto g-3" if @layout == :horizontal && options[:skip_inline].blank?
28
+ tag.div(class: html_class) do
29
29
  input_with_error(name) do
30
30
  send(without_field_name, name, options, html_options)
31
31
  end
@@ -34,10 +34,12 @@ module BootstrapForm
34
34
 
35
35
  def form_group_content(label, help_text, options, &block)
36
36
  if group_layout_horizontal?(options[:layout])
37
- concat(label).concat(content_tag(:div, capture(&block) + help_text, class: form_group_control_class(options)))
37
+ concat(label).concat(tag.div(capture(&block) + help_text, class: form_group_control_class(options)))
38
38
  else
39
- concat(label)
39
+ # Floating labels need to be rendered after the field
40
+ concat(label) unless options[:floating]
40
41
  concat(capture(&block))
42
+ concat(label) if options[:floating]
41
43
  concat(help_text) if help_text
42
44
  end
43
45
  end
@@ -50,15 +52,28 @@ module BootstrapForm
50
52
  end
51
53
 
52
54
  def form_group_classes(options)
53
- classes = ["form-group", options[:class].try(:split)].flatten.compact
54
- classes << "row" if group_layout_horizontal?(options[:layout]) && classes.exclude?("form-row")
55
- classes << "form-inline" if field_inline_override?(options[:layout])
55
+ classes = options[:class] == false ? [] : (options[:class] || form_group_default_class).split
56
+ classes << "row" if horizontal_group_with_gutters?(options[:layout], classes)
57
+ classes << "col-auto g-3" if field_inline_override?(options[:layout])
56
58
  classes << feedback_class if options[:icon]
57
- classes
59
+ classes << "form-floating" if options[:floating]
60
+ classes.presence
61
+ end
62
+
63
+ def form_group_default_class
64
+ (layout == :inline ? "col" : "mb-3")
65
+ end
66
+
67
+ def horizontal_group_with_gutters?(layout, classes)
68
+ group_layout_horizontal?(layout) && !classes_include_gutters?(classes)
58
69
  end
59
70
 
60
71
  def group_layout_horizontal?(layout)
61
72
  get_group_layout(layout) == :horizontal
62
73
  end
74
+
75
+ def classes_include_gutters?(classes)
76
+ classes.any? { |c| c =~ /^g-\d+$/ }
77
+ end
63
78
  end
64
79
  end
@@ -6,7 +6,7 @@ module BootstrapForm
6
6
 
7
7
  private
8
8
 
9
- def form_group_builder(method, options, html_options=nil)
9
+ def form_group_builder(method, options, html_options=nil, &block)
10
10
  no_wrapper = options[:wrapper] == false
11
11
 
12
12
  options = form_group_builder_options(options, method)
@@ -21,16 +21,14 @@ module BootstrapForm
21
21
  if no_wrapper
22
22
  yield
23
23
  else
24
- form_group(method, form_group_options) { yield }
24
+ form_group(method, form_group_options, &block)
25
25
  end
26
26
  end
27
27
 
28
28
  def form_group_builder_options(options, method)
29
29
  options.symbolize_keys!
30
30
  options = convert_form_tag_options(method, options) if acts_like_form_tag
31
- unless options[:skip_label]
32
- options[:required] = form_group_required(options) if options.key?(:skip_required)
33
- end
31
+ options[:required] = form_group_required(options) if !options[:skip_label] && options.key?(:skip_required)
34
32
  options
35
33
  end
36
34
 
@@ -48,7 +46,8 @@ module BootstrapForm
48
46
  id: options[:id], help: options[:help], icon: options[:icon],
49
47
  label_col: options[:label_col], control_col: options[:control_col],
50
48
  add_control_col_class: options[:add_control_col_class],
51
- layout: get_group_layout(options[:layout]), class: options[:wrapper_class]
49
+ layout: get_group_layout(options[:layout]), class: options[:wrapper_class],
50
+ floating: options[:floating]
52
51
  }
53
52
 
54
53
  form_group_options.merge!(wrapper_options) if wrapper_options.is_a?(Hash)
@@ -57,12 +56,11 @@ module BootstrapForm
57
56
  end
58
57
 
59
58
  def form_group_label(options, css_options)
60
- hash = {
59
+ {
61
60
  text: form_group_label_text(options[:label]),
62
61
  class: form_group_label_class(options),
63
62
  required: options[:required]
64
63
  }.merge(css_options[:id].present? ? { for: css_options[:id] } : {})
65
- hash
66
64
  end
67
65
 
68
66
  def form_group_label_text(label)
@@ -8,13 +8,13 @@ module BootstrapForm
8
8
 
9
9
  def submit(name=nil, options={})
10
10
  setup_css_class "btn btn-secondary", options
11
- super
11
+ layout == :inline ? form_group { super } : super
12
12
  end
13
13
 
14
14
  def primary(name=nil, options={}, &block)
15
15
  setup_css_class "btn btn-primary", options
16
16
 
17
- if options[:render_as_button] || block_given?
17
+ if options[:render_as_button] || block
18
18
  options.except! :render_as_button
19
19
  button(name, options, &block)
20
20
  else
@@ -26,18 +26,22 @@ module BootstrapForm
26
26
  css = options[:class] || "alert alert-danger"
27
27
  return unless object.respond_to?(:errors) && object.errors.full_messages.any?
28
28
 
29
- content_tag :div, class: css do
30
- concat content_tag :p, title
31
- concat error_summary unless options[:error_summary] == false
29
+ tag.div class: css do
30
+ if options[:error_summary] == false
31
+ title
32
+ else
33
+ concat tag.p title
34
+ concat error_summary
35
+ end
32
36
  end
33
37
  end
34
38
 
35
39
  def error_summary
36
40
  return unless object.errors.any?
37
41
 
38
- content_tag :ul, class: "rails-bootstrap-forms-error-summary" do
42
+ tag.ul class: "rails-bootstrap-forms-error-summary" do
39
43
  object.errors.full_messages.each do |error|
40
- concat content_tag(:li, error)
44
+ concat tag.li(error)
41
45
  end
42
46
  end
43
47
  end
@@ -46,8 +50,9 @@ module BootstrapForm
46
50
  return unless error?(name)
47
51
 
48
52
  hide_attribute_name = options[:hide_attribute_name] || false
53
+ custom_class = options[:custom_class] || false
49
54
 
50
- content_tag :div, class: "alert alert-danger" do
55
+ tag.div class: custom_class || "invalid-feedback" do
51
56
  if hide_attribute_name
52
57
  object.errors[name].join(", ")
53
58
  else
@@ -85,7 +90,7 @@ module BootstrapForm
85
90
  input = attach_input(options, :prepend) + input + attach_input(options, :append)
86
91
  input += generate_error(name)
87
92
  options.present? &&
88
- input = content_tag(:div, input, class: ["input-group", options[:input_group_class]].compact)
93
+ input = tag.div(input, class: ["input-group", options[:input_group_class]].compact)
89
94
  input
90
95
  end
91
96
 
@@ -95,9 +100,9 @@ module BootstrapForm
95
100
  end
96
101
 
97
102
  def input_group_content(content)
98
- return content if content =~ /btn/
103
+ return content if /btn/.match?(content)
99
104
 
100
- content_tag(:span, content, class: "input-group-text")
105
+ tag.span(content, class: "input-group-text")
101
106
  end
102
107
 
103
108
  def static_class
@@ -108,7 +113,7 @@ module BootstrapForm
108
113
 
109
114
  def attach_input(options, key)
110
115
  tags = [*options[key]].map do |item|
111
- content_tag(:div, input_group_content(item), class: "input-group-#{key}")
116
+ input_group_content(item)
112
117
  end
113
118
  ActiveSupport::SafeBuffer.new(tags.join)
114
119
  end
@@ -10,7 +10,8 @@ module BootstrapForm
10
10
  define_method "#{field_name}_with_bootstrap" do |name, options={}|
11
11
  form_group_builder(name, options) do
12
12
  prepend_and_append_input(name, options) do
13
- send("#{field_name}_without_bootstrap".to_sym, name, options)
13
+ options[:placeholder] ||= name if options[:floating]
14
+ send("#{field_name}_without_bootstrap".to_sym, name, options.except(:floating))
14
15
  end
15
16
  end
16
17
  end
@@ -19,11 +20,10 @@ module BootstrapForm
19
20
  end
20
21
 
21
22
  def bootstrap_select_group(field_name)
22
- with_field_name = "#{field_name}_with_bootstrap"
23
- without_field_name = "#{field_name}_without_bootstrap"
24
- define_method(with_field_name) do |name, options={}, html_options={}|
23
+ define_method("#{field_name}_with_bootstrap") do |name, options={}, html_options={}|
24
+ html_options = html_options.reverse_merge(control_class: "form-select")
25
25
  form_group_builder(name, options, html_options) do
26
- form_group_content_tag(name, field_name, without_field_name, options, html_options)
26
+ form_group_content_tag(name, field_name, "#{field_name}_without_bootstrap", options, html_options)
27
27
  end
28
28
  end
29
29
 
@@ -10,10 +10,10 @@ module BootstrapForm
10
10
  def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_value="0", &block)
11
11
  options = options.symbolize_keys!
12
12
  check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
13
- :inline, :custom, :hide_label, :skip_label, :wrapper_class)
13
+ :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
14
14
  check_box_options[:class] = check_box_classes(name, options)
15
15
 
16
- content_tag(:div, class: check_box_wrapper_class(options)) do
16
+ tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do
17
17
  html = check_box_without_bootstrap(name, check_box_options, checked_value, unchecked_value)
18
18
  html.concat(check_box_label(name, options, checked_value, &block)) unless options[:skip_label]
19
19
  html.concat(generate_error(name)) if options[:error_message]
@@ -38,7 +38,7 @@ module BootstrapForm
38
38
  end
39
39
 
40
40
  def check_box_description(name, options, &block)
41
- content = block_given? ? capture(&block) : options[:label]
41
+ content = block ? capture(&block) : options[:label]
42
42
  content || object&.class&.human_attribute_name(name) || name.to_s.humanize
43
43
  end
44
44
 
@@ -50,40 +50,28 @@ module BootstrapForm
50
50
  end
51
51
 
52
52
  def check_box_classes(name, options)
53
- classes = [options[:class]]
54
- classes << (options[:custom] ? "custom-control-input" : "form-check-input")
53
+ classes = Array(options[:class]) << "form-check-input"
55
54
  classes << "is-invalid" if error?(name)
56
55
  classes << "position-static" if options[:skip_label] || options[:hide_label]
57
56
  classes.flatten.compact
58
57
  end
59
58
 
60
59
  def check_box_label_class(options)
61
- classes = []
62
- classes << (options[:custom] ? "custom-control-label" : "form-check-label")
60
+ classes = ["form-check-label"]
63
61
  classes << options[:label_class]
64
62
  classes << hide_class if options[:hide_label]
65
63
  classes.flatten.compact
66
64
  end
67
65
 
68
66
  def check_box_wrapper_class(options)
69
- classes = []
70
- if options[:custom]
71
- classes << custom_check_box_wrapper_class(options)
72
- else
73
- classes << "form-check"
74
- classes << "form-check-inline" if layout_inline?(options[:inline])
75
- end
76
- classes << options[:wrapper_class] if options[:wrapper_class].present?
67
+ classes = ["form-check"]
68
+ classes << "form-check-inline" if layout_inline?(options[:inline])
69
+ classes << "mb-3" unless options[:multiple] || layout == :horizontal
70
+ classes << "form-switch" if options[:switch]
71
+ classes << options.dig(:wrapper, :class).presence
72
+ classes << options[:wrapper_class].presence
77
73
  classes.flatten.compact
78
74
  end
79
-
80
- def custom_check_box_wrapper_class(options)
81
- classes = []
82
- classes << "custom-control"
83
- classes << (options[:custom] == :switch ? "custom-switch" : "custom-checkbox")
84
- classes << "custom-control-inline" if layout_inline?(options[:inline])
85
- classes
86
- end
87
75
  end
88
76
  end
89
77
  end
@@ -13,7 +13,11 @@ module BootstrapForm
13
13
  options[:multiple] = true
14
14
  check_box(name, options, value, nil)
15
15
  end
16
- hidden_field(args.first, value: "", multiple: true).concat(html)
16
+
17
+ if args.extract_options!.symbolize_keys!.delete(:include_hidden) { true }
18
+ html.prepend hidden_field(args.first, value: "", multiple: true)
19
+ end
20
+ html
17
21
  end
18
22
 
19
23
  bootstrap_alias :collection_check_boxes
@@ -10,8 +10,9 @@ module BootstrapForm
10
10
  # Disabling Metrics/ParameterLists because the upstream Rails method has the same parameters
11
11
  # rubocop:disable Metrics/ParameterLists
12
12
  def collection_select_with_bootstrap(method, collection, value_method, text_method, options={}, html_options={})
13
+ html_options = html_options.reverse_merge(control_class: "form-select")
13
14
  form_group_builder(method, options, html_options) do
14
- input_with_error(method) do
15
+ prepend_and_append_input(method, options) do
15
16
  collection_select_without_bootstrap(method, collection, value_method, text_method, options, html_options)
16
17
  end
17
18
  end
@@ -8,28 +8,16 @@ module BootstrapForm
8
8
 
9
9
  included do
10
10
  def file_field_with_bootstrap(name, options={})
11
- options = options.reverse_merge(control_class: "custom-file-input")
11
+ options = options.reverse_merge(control_class: "form-control")
12
12
  form_group_builder(name, options) do
13
- content_tag(:div, class: "custom-file") do
14
- input_with_error(name) do
15
- file_field_input(name, options)
16
- end
13
+ input_with_error(name) do
14
+ file_field_without_bootstrap(name, options)
17
15
  end
18
16
  end
19
17
  end
20
18
 
21
19
  bootstrap_alias :file_field
22
20
  end
23
-
24
- private
25
-
26
- def file_field_input(name, options)
27
- placeholder = options.delete(:placeholder) || "Choose file"
28
- placeholder_opts = { class: "custom-file-label" }
29
- placeholder_opts[:for] = options[:id] if options[:id].present?
30
-
31
- file_field_without_bootstrap(name, options) + label(name, placeholder, placeholder_opts)
32
- end
33
21
  end
34
22
  end
35
23
  end