bootstrap_form 5.1.0 → 5.2.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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +21 -8
  3. data/.gitignore +4 -2
  4. data/.rubocop.yml +2 -1
  5. data/.yarnrc +5 -0
  6. data/CHANGELOG.md +11 -3
  7. data/CONTRIBUTING.md +101 -14
  8. data/Dangerfile +4 -4
  9. data/Dockerfile +5 -5
  10. data/Gemfile +5 -21
  11. data/README.md +68 -21
  12. data/RELEASING.md +12 -1
  13. data/Rakefile +7 -6
  14. data/bootstrap_form.gemspec +6 -8
  15. data/docker-compose.yml +6 -24
  16. data/gemfiles/6.0.gemfile +1 -1
  17. data/gemfiles/6.1.gemfile +1 -1
  18. data/gemfiles/7.0.gemfile +2 -3
  19. data/gemfiles/common.gemfile +28 -0
  20. data/gemfiles/edge.gemfile +3 -2
  21. data/lib/bootstrap_form/components/labels.rb +3 -9
  22. data/lib/bootstrap_form/components/validation.rb +36 -19
  23. data/lib/bootstrap_form/form_builder.rb +3 -0
  24. data/lib/bootstrap_form/form_group.rb +9 -6
  25. data/lib/bootstrap_form/form_group_builder.rb +10 -7
  26. data/lib/bootstrap_form/helpers/bootstrap.rb +7 -8
  27. data/lib/bootstrap_form/helpers/field.rb +26 -0
  28. data/lib/bootstrap_form/helpers.rb +1 -0
  29. data/lib/bootstrap_form/inputs/check_box.rb +22 -6
  30. data/lib/bootstrap_form/inputs/radio_button.rb +3 -3
  31. data/lib/bootstrap_form/version.rb +2 -1
  32. data/lib/bootstrap_form.rb +1 -1
  33. metadata +14 -146
  34. data/demo/.postcssrc.yml +0 -3
  35. data/demo/.ruby-version +0 -1
  36. data/demo/Gemfile +0 -80
  37. data/demo/Gemfile.lock +0 -261
  38. data/demo/Procfile.dev +0 -2
  39. data/demo/README.md +0 -17
  40. data/demo/Rakefile +0 -6
  41. data/demo/app/assets/builds/.keep +0 -0
  42. data/demo/app/assets/builds/application.js.LICENSE.txt +0 -9
  43. data/demo/app/assets/config/manifest.js +0 -2
  44. data/demo/app/assets/stylesheets/actiontext.css +0 -31
  45. data/demo/app/assets/stylesheets/actiontext.scss +0 -38
  46. data/demo/app/assets/stylesheets/application.scss +0 -1
  47. data/demo/app/controllers/application_controller.rb +0 -2
  48. data/demo/app/controllers/bootstrap_controller.rb +0 -29
  49. data/demo/app/controllers/users_controller.rb +0 -9
  50. data/demo/app/helpers/bootstrap_helper.rb +0 -27
  51. data/demo/app/javascript/application.js +0 -3
  52. data/demo/app/javascript/channels/consumer.js +0 -6
  53. data/demo/app/javascript/channels/index.js +0 -5
  54. data/demo/app/javascript/packs/application.js +0 -11
  55. data/demo/app/models/address.rb +0 -3
  56. data/demo/app/models/application_record.rb +0 -3
  57. data/demo/app/models/faux_user.rb +0 -9
  58. data/demo/app/models/skill.rb +0 -15
  59. data/demo/app/models/super_user.rb +0 -2
  60. data/demo/app/models/user.rb +0 -25
  61. data/demo/app/views/active_storage/blobs/_blob.html.erb +0 -14
  62. data/demo/app/views/bootstrap/form.html.erb +0 -67
  63. data/demo/app/views/layouts/action_text/contents/_content.html.erb +0 -3
  64. data/demo/app/views/layouts/application.html.erb +0 -76
  65. data/demo/bin/bundle +0 -3
  66. data/demo/bin/dev +0 -9
  67. data/demo/bin/rails +0 -4
  68. data/demo/bin/rake +0 -4
  69. data/demo/bin/setup +0 -36
  70. data/demo/bin/update +0 -31
  71. data/demo/bin/webpack +0 -15
  72. data/demo/bin/webpack-dev-server +0 -15
  73. data/demo/bin/yarn +0 -11
  74. data/demo/config/application.rb +0 -21
  75. data/demo/config/boot.rb +0 -5
  76. data/demo/config/database.yml +0 -21
  77. data/demo/config/environment.rb +0 -5
  78. data/demo/config/environments/development.rb +0 -60
  79. data/demo/config/environments/production.rb +0 -48
  80. data/demo/config/environments/test.rb +0 -46
  81. data/demo/config/initializers/application_controller_renderer.rb +0 -8
  82. data/demo/config/initializers/backtrace_silencers.rb +0 -7
  83. data/demo/config/initializers/cookies_serializer.rb +0 -5
  84. data/demo/config/initializers/filter_parameter_logging.rb +0 -4
  85. data/demo/config/initializers/inflections.rb +0 -16
  86. data/demo/config/initializers/mime_types.rb +0 -4
  87. data/demo/config/initializers/wrap_parameters.rb +0 -14
  88. data/demo/config/locales/en.yml +0 -33
  89. data/demo/config/puma.rb +0 -56
  90. data/demo/config/routes.rb +0 -6
  91. data/demo/config/spring.rb +0 -6
  92. data/demo/config/storage.yml +0 -35
  93. data/demo/config/webpack/development.js +0 -5
  94. data/demo/config/webpack/environment.js +0 -3
  95. data/demo/config/webpack/production.js +0 -5
  96. data/demo/config/webpack/test.js +0 -5
  97. data/demo/config/webpacker.yml +0 -92
  98. data/demo/config.ru +0 -5
  99. data/demo/db/schema.rb +0 -84
  100. data/demo/doc/screenshots/bootstrap/index/00_horizontal_form.png +0 -0
  101. data/demo/doc/screenshots/bootstrap/index/01_with_validation_error.png +0 -0
  102. data/demo/doc/screenshots/bootstrap/index/02_inline_form.png +0 -0
  103. data/demo/doc/screenshots/bootstrap/index/03_simple_action_text_example.png +0 -0
  104. data/demo/doc/screenshots/bootstrap/index/04_floating_labels.png +0 -0
  105. data/demo/doc/screenshots/bootstrap/readme/00_example.png +0 -0
  106. data/demo/doc/screenshots/bootstrap/readme/01_example.png +0 -0
  107. data/demo/doc/screenshots/bootstrap/readme/02_example.png +0 -0
  108. data/demo/doc/screenshots/bootstrap/readme/03_example.png +0 -0
  109. data/demo/doc/screenshots/bootstrap/readme/04_example.png +0 -0
  110. data/demo/doc/screenshots/bootstrap/readme/05_example.png +0 -0
  111. data/demo/doc/screenshots/bootstrap/readme/06_example.png +0 -0
  112. data/demo/doc/screenshots/bootstrap/readme/07_example.png +0 -0
  113. data/demo/doc/screenshots/bootstrap/readme/08_example.png +0 -0
  114. data/demo/doc/screenshots/bootstrap/readme/09_example.png +0 -0
  115. data/demo/doc/screenshots/bootstrap/readme/10_example.png +0 -0
  116. data/demo/doc/screenshots/bootstrap/readme/11_example.png +0 -0
  117. data/demo/doc/screenshots/bootstrap/readme/12_example.png +0 -0
  118. data/demo/doc/screenshots/bootstrap/readme/13_example.png +0 -0
  119. data/demo/doc/screenshots/bootstrap/readme/14_example.png +0 -0
  120. data/demo/doc/screenshots/bootstrap/readme/15_example.png +0 -0
  121. data/demo/doc/screenshots/bootstrap/readme/16_example.png +0 -0
  122. data/demo/doc/screenshots/bootstrap/readme/17_example.png +0 -0
  123. data/demo/doc/screenshots/bootstrap/readme/18_example.png +0 -0
  124. data/demo/doc/screenshots/bootstrap/readme/19_example.png +0 -0
  125. data/demo/doc/screenshots/bootstrap/readme/20_example.png +0 -0
  126. data/demo/doc/screenshots/bootstrap/readme/21_example.png +0 -0
  127. data/demo/doc/screenshots/bootstrap/readme/22_example.png +0 -0
  128. data/demo/doc/screenshots/bootstrap/readme/23_example.png +0 -0
  129. data/demo/doc/screenshots/bootstrap/readme/24_example.png +0 -0
  130. data/demo/doc/screenshots/bootstrap/readme/25_example.png +0 -0
  131. data/demo/doc/screenshots/bootstrap/readme/26_example.png +0 -0
  132. data/demo/doc/screenshots/bootstrap/readme/27_example.png +0 -0
  133. data/demo/doc/screenshots/bootstrap/readme/28_example.png +0 -0
  134. data/demo/doc/screenshots/bootstrap/readme/29_example.png +0 -0
  135. data/demo/doc/screenshots/bootstrap/readme/30_example.png +0 -0
  136. data/demo/doc/screenshots/bootstrap/readme/31_example.png +0 -0
  137. data/demo/doc/screenshots/bootstrap/readme/32_example.png +0 -0
  138. data/demo/doc/screenshots/bootstrap/readme/33_example.png +0 -0
  139. data/demo/doc/screenshots/bootstrap/readme/34_example.png +0 -0
  140. data/demo/doc/screenshots/bootstrap/readme/35_example.png +0 -0
  141. data/demo/doc/screenshots/bootstrap/readme/36_example.png +0 -0
  142. data/demo/doc/screenshots/bootstrap/readme/37_example.png +0 -0
  143. data/demo/doc/screenshots/bootstrap/readme/38_example.png +0 -0
  144. data/demo/doc/screenshots/bootstrap/readme/39_example.png +0 -0
  145. data/demo/doc/screenshots/bootstrap/readme/40_example.png +0 -0
  146. data/demo/doc/screenshots/bootstrap/readme/41_example.png +0 -0
  147. data/demo/doc/screenshots/bootstrap/readme/42_example.png +0 -0
  148. data/demo/doc/screenshots/bootstrap/readme/43_example.png +0 -0
  149. data/demo/doc/screenshots/bootstrap/readme/44_example.png +0 -0
  150. data/demo/doc/screenshots/bootstrap/readme/45_example.png +0 -0
  151. data/demo/doc/screenshots/bootstrap/readme/46_example.png +0 -0
  152. data/demo/doc/screenshots/bootstrap/readme/47_example.png +0 -0
  153. data/demo/doc/screenshots/bootstrap/readme/48_example.png +0 -0
  154. data/demo/doc/screenshots/bootstrap/readme/49_example.png +0 -0
  155. data/demo/doc/screenshots/bootstrap/readme/50_example.png +0 -0
  156. data/demo/log/.keep +0 -0
  157. data/demo/package.json +0 -21
  158. data/demo/public/favicon.ico +0 -0
  159. data/demo/test/application_system_test_case.rb +0 -8
  160. data/demo/test/controllers/bootstrap_controller_test.rb +0 -8
  161. data/demo/test/controllers/users_controller_test.rb +0 -13
  162. data/demo/test/fixtures/action_text/rich_texts.yml +0 -4
  163. data/demo/test/fixtures/users.yml +0 -2
  164. data/demo/test/system/bootstrap_test.rb +0 -84
  165. data/demo/test/test_helper.rb +0 -10
  166. data/demo/webpack.config.js +0 -20
  167. data/demo/yarn.lock +0 -7176
  168. data/gemfiles/5.2.gemfile +0 -4
data/Rakefile CHANGED
@@ -17,12 +17,13 @@ end
17
17
 
18
18
  require 'bundler/gem_tasks'
19
19
 
20
- require 'rake/testtask'
20
+ require "minitest/test_task"
21
21
 
22
- Rake::TestTask.new(:test) do |t|
23
- t.libs << 'test'
24
- t.pattern = 'test/**/*_test.rb'
25
- t.verbose = false
22
+ Minitest::TestTask.create(:test) do |t|
23
+ t.libs << "test"
24
+ t.libs << "lib"
25
+ t.warning = false
26
+ t.test_globs = ["test/**/*_test.rb"]
26
27
  end
27
28
 
28
29
  # This automatically updates GitHub Releases whenever we `rake release` the gem
@@ -34,4 +35,4 @@ end
34
35
  desc 'Run RuboCop checks'
35
36
  RuboCop::RakeTask.new(:rubocop)
36
37
 
37
- task default: %i[test rubocop]
38
+ task default: %i[test rubocop:autocorrect]
@@ -3,30 +3,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require "bootstrap_form/version"
5
5
 
6
- REQUIRED_RAILS_VERSION = ">= 5.2".freeze
7
-
8
6
  Gem::Specification.new do |s|
9
7
  s.name = "bootstrap_form"
10
8
  s.version = BootstrapForm::VERSION
11
9
  s.authors = ["Stephen Potenza", "Carlos Lopes"]
12
10
  s.email = ["potenza@gmail.com", "carlos.el.lopes@gmail.com"]
13
11
  s.homepage = "https://github.com/bootstrap-ruby/bootstrap_form"
14
- s.summary = "Rails form builder that makes it easy to style forms using "\
12
+ s.summary = "Rails form builder that makes it easy to style forms using " \
15
13
  "Bootstrap 5"
16
- s.description = "bootstrap_form is a rails form builder that makes it super "\
14
+ s.description = "bootstrap_form is a rails form builder that makes it super " \
17
15
  "easy to create beautiful-looking forms using Bootstrap 5"
18
16
  s.license = "MIT"
19
17
  s.metadata = { "rubygems_mfa_required" => "true" }
20
18
 
21
19
  s.files = `git ls-files -z`.split("\x0").reject do |f|
22
- f.match(%r{^(test)/})
20
+ f.match(%r{^(test)/|^(demo)/})
23
21
  end
24
22
 
25
23
  s.bindir = "exe"
26
24
  s.require_paths = ["lib"]
27
25
 
28
- s.required_ruby_version = ">= 2.7"
26
+ s.required_ruby_version = ">= 3.0"
29
27
 
30
- s.add_dependency("actionpack", REQUIRED_RAILS_VERSION)
31
- s.add_dependency("activemodel", REQUIRED_RAILS_VERSION)
28
+ s.add_dependency("actionpack", BootstrapForm::REQUIRED_RAILS_VERSION)
29
+ s.add_dependency("activemodel", BootstrapForm::REQUIRED_RAILS_VERSION)
32
30
  end
data/docker-compose.yml CHANGED
@@ -5,9 +5,10 @@ services:
5
5
  build:
6
6
  context: .
7
7
  args:
8
- NODE_MAJOR: '12'
9
- YARN_VERSION: '1.22.4'
10
- image: bootstrap-form:0.0.1
8
+ NODE_MAJOR: "12"
9
+ YARN_VERSION: "1.22.4"
10
+ RUBY_VERSION: ${RUBY_VERSION}
11
+ image: bootstrap-form:latest-$RUBY_VERSION
11
12
  tmpfs:
12
13
  - /tmp
13
14
 
@@ -17,15 +18,6 @@ services:
17
18
  tty: true
18
19
  volumes:
19
20
  - .:/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
21
  environment:
30
22
  - SSH_AUTH_SOCK=/ssh-agent
31
23
  - NODE_ENV=development
@@ -34,16 +26,6 @@ services:
34
26
  - WEBPACKER_DEV_SERVER_HOST=webpacker
35
27
  - WEB_CONCURRENCY=1
36
28
  - HISTFILE=/app/.bash_history
29
+ ports:
30
+ - "3000:3000"
37
31
  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/6.0.gemfile CHANGED
@@ -1,4 +1,4 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
4
  gem "rails", "~> 6.0.0"
data/gemfiles/6.1.gemfile CHANGED
@@ -1,4 +1,4 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
4
  gem "rails", "~> 6.1.0"
data/gemfiles/7.0.gemfile CHANGED
@@ -1,6 +1,5 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
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
4
  gem "rails", "~> 7.0.2"
5
+ gem "sprockets-rails", require: "sprockets/railtie"
@@ -0,0 +1,28 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec path: File.dirname(__dir__)
4
+
5
+ # To test with different Rails versions, use the files in `./gemfiles`
6
+
7
+ group :development do
8
+ gem "htmlbeautifier"
9
+ gem "puma"
10
+ gem "rubocop-performance", require: false
11
+ gem "rubocop-rails", require: false
12
+ end
13
+
14
+ group :test do
15
+ gem "diffy"
16
+ gem "equivalent-xml"
17
+ gem "mocha"
18
+ gem "sqlite3"
19
+ end
20
+
21
+ group :development, :test do
22
+ gem "debug"
23
+ gem "pry-byebug"
24
+ end
25
+
26
+ group :ci do
27
+ gem "danger"
28
+ end
@@ -1,4 +1,5 @@
1
- gems = "#{File.dirname __dir__}/Gemfile"
1
+ gems = "#{__dir__}/common.gemfile"
2
2
  eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
3
 
4
- gem "rails", git: "https://github.com/rails/rails.git"
4
+ gem "rails", git: "https://github.com/rails/rails.git", branch: "main"
5
+ gem "sprockets-rails", require: "sprockets/railtie"
@@ -24,14 +24,8 @@ module BootstrapForm
24
24
 
25
25
  def label_classes(name, options, custom_label_col, group_layout)
26
26
  classes = ["form-label", options[:class], label_layout_classes(custom_label_col, group_layout)]
27
-
28
- case options.delete(:required)
29
- when true
30
- classes << "required"
31
- when nil, :default
32
- classes << "required" if required_attribute?(object, name)
33
- end
34
-
27
+ classes << "required" if required_field_options(options, name)[:required]
28
+ options.delete(:required)
35
29
  classes << "text-danger" if label_errors && error?(name)
36
30
  classes.flatten.compact
37
31
  end
@@ -46,7 +40,7 @@ module BootstrapForm
46
40
 
47
41
  def label_text(name, options)
48
42
  if label_errors && error?(name)
49
- (options[:text] || object.class.human_attribute_name(name)).to_s.concat(" #{get_error_messages(name)}")
43
+ (options[:text] || object.class.human_attribute_name(name)).to_s + " #{get_error_messages(name)}"
50
44
  else
51
45
  options[:text] || object&.class.try(:human_attribute_name, name)
52
46
  end
@@ -8,35 +8,45 @@ module BootstrapForm
8
8
  private
9
9
 
10
10
  def error?(name)
11
- object.respond_to?(:errors) && !(name.nil? || object.errors[name].empty?)
11
+ name && object.respond_to?(:errors) && (object.errors[name].any? || association_error?(name))
12
+ end
13
+
14
+ def association_error?(name)
15
+ object.class.reflections.any? do |association_name, a|
16
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
17
+ next unless a.foreign_key == name.to_s
18
+
19
+ object.errors[association_name].any?
20
+ end
12
21
  end
13
22
 
14
23
  def required_attribute?(obj, attribute)
15
24
  return false unless obj && attribute
16
25
 
17
26
  target = obj.instance_of?(Class) ? obj : obj.class
27
+ return false unless target.respond_to? :validators_on
18
28
 
19
- target_validators = if target.respond_to? :validators_on
20
- target.validators_on(attribute).map(&:class)
21
- else
22
- []
23
- end
24
-
25
- presence_validator?(target_validators)
29
+ presence_validator?(target_validators(target, attribute)) ||
30
+ required_association?(target, attribute)
26
31
  end
27
32
 
28
- def presence_validator?(target_validators)
29
- has_presence_validator = target_validators.include?(
30
- ActiveModel::Validations::PresenceValidator
31
- )
32
-
33
- if defined? ActiveRecord::Validations::PresenceValidator
34
- has_presence_validator |= target_validators.include?(
35
- ActiveRecord::Validations::PresenceValidator
36
- )
33
+ def required_association?(target, attribute)
34
+ target.reflections.find do |name, a|
35
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
36
+ next unless a.foreign_key == attribute.to_s
37
+
38
+ presence_validator?(target_validators(target, name))
37
39
  end
40
+ end
41
+
42
+ def target_validators(target, attribute)
43
+ target.validators_on(attribute).map(&:class)
44
+ end
38
45
 
39
- has_presence_validator
46
+ def presence_validator?(target_validators)
47
+ target_validators.include?(ActiveModel::Validations::PresenceValidator) ||
48
+ (defined?(ActiveRecord::Validations::PresenceValidator) &&
49
+ target_validators.include?(ActiveRecord::Validations::PresenceValidator))
40
50
  end
41
51
 
42
52
  def inline_error?(name)
@@ -54,7 +64,14 @@ module BootstrapForm
54
64
  end
55
65
 
56
66
  def get_error_messages(name)
57
- object.errors[name].join(", ")
67
+ messages = object.errors[name]
68
+ object.class.reflections.each do |association_name, a|
69
+ next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
70
+ next unless a.foreign_key == name.to_s
71
+
72
+ messages << object.errors[association_name]
73
+ end
74
+ messages.join(", ")
58
75
  end
59
76
  end
60
77
  end
@@ -1,10 +1,12 @@
1
1
  # require 'bootstrap_form/aliasing'
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module BootstrapForm
4
5
  class FormBuilder < ActionView::Helpers::FormBuilder
5
6
  attr_reader :layout, :label_col, :control_col, :has_error, :inline_errors,
6
7
  :label_errors, :acts_like_form_tag
7
8
 
9
+ include BootstrapForm::Helpers::Field
8
10
  include BootstrapForm::Helpers::Bootstrap
9
11
 
10
12
  include BootstrapForm::FormGroupBuilder
@@ -130,3 +132,4 @@ module BootstrapForm
130
132
  end
131
133
  end
132
134
  end
135
+ # rubocop:enable Metrics/ClassLength
@@ -32,15 +32,18 @@ module BootstrapForm
32
32
  end
33
33
  end
34
34
 
35
- def form_group_content(label, help_text, options, &block)
35
+ def form_group_content(label, help_text, options, &block) # rubocop:disable Metrics/AbcSize
36
+ label ||= ActiveSupport::SafeBuffer.new
36
37
  if group_layout_horizontal?(options[:layout])
37
- concat(label).concat(tag.div(capture(&block) + help_text, class: form_group_control_class(options)))
38
+ label + tag.div(capture(&block) + help_text, class: form_group_control_class(options))
38
39
  else
40
+ content = ActiveSupport::SafeBuffer.new
39
41
  # Floating labels need to be rendered after the field
40
- concat(label) unless options[:floating]
41
- concat(capture(&block))
42
- concat(label) if options[:floating]
43
- concat(help_text) if help_text
42
+ content << label unless options[:floating]
43
+ content << capture(&block)
44
+ content << label if options[:floating]
45
+ content << help_text if help_text
46
+ content
44
47
  end
45
48
  end
46
49
 
@@ -28,8 +28,7 @@ module BootstrapForm
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
- options[:required] = form_group_required(options) if !options[:skip_label] && options.key?(:skip_required)
32
- options
31
+ options.merge!(required_field_options(options, method))
33
32
  end
34
33
 
35
34
  def convert_form_tag_options(method, options={})
@@ -77,11 +76,15 @@ module BootstrapForm
77
76
  classes
78
77
  end
79
78
 
80
- def form_group_required(options)
81
- return unless options.key?(:skip_required)
82
-
83
- warn "`:skip_required` is deprecated, use `:required: false` instead"
84
- options[:skip_required] ? false : :default
79
+ def form_group_required(options, method)
80
+ if options[:skip_required]
81
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
82
+ false
83
+ elsif options.key?(:required)
84
+ options[:required]
85
+ else
86
+ required_attribute?(object, method)
87
+ end
85
88
  end
86
89
 
87
90
  def form_group_css_options(method, html_options, options)
@@ -30,8 +30,7 @@ module BootstrapForm
30
30
  if options[:error_summary] == false
31
31
  title
32
32
  else
33
- concat tag.p title
34
- concat error_summary
33
+ tag.p(title) + error_summary
35
34
  end
36
35
  end
37
36
  end
@@ -39,9 +38,9 @@ module BootstrapForm
39
38
  def error_summary
40
39
  return unless object.errors.any?
41
40
 
42
- tag.ul class: "rails-bootstrap-forms-error-summary" do
43
- object.errors.full_messages.each do |error|
44
- concat tag.li(error)
41
+ tag.ul(class: "rails-bootstrap-forms-error-summary") do
42
+ object.errors.full_messages.reduce(ActiveSupport::SafeBuffer.new) do |acc, error|
43
+ acc << tag.li(error)
45
44
  end
46
45
  end
47
46
  end
@@ -83,12 +82,12 @@ module BootstrapForm
83
82
  end
84
83
 
85
84
  def prepend_and_append_input(name, options, &block)
86
- options = options.extract!(:prepend, :append, :input_group_class)
85
+ options = options.extract!(:prepend, :append, :input_group_class).compact
87
86
 
88
87
  input = capture(&block) || ActiveSupport::SafeBuffer.new
89
88
 
90
89
  input = attach_input(options, :prepend) + input + attach_input(options, :append)
91
- input += generate_error(name)
90
+ input << generate_error(name)
92
91
  options.present? &&
93
92
  input = tag.div(input, class: ["input-group", options[:input_group_class]].compact)
94
93
  input
@@ -100,7 +99,7 @@ module BootstrapForm
100
99
  end
101
100
 
102
101
  def input_group_content(content)
103
- return content if /btn/.match?(content)
102
+ return content if content.include?("btn")
104
103
 
105
104
  tag.span(content, class: "input-group-text")
106
105
  end
@@ -0,0 +1,26 @@
1
+ module BootstrapForm
2
+ module Helpers
3
+ module Field
4
+ def required_field_options(options, method)
5
+ required = required_field?(options, method)
6
+ {}.tap do |option|
7
+ option[:required] = required
8
+ option[:aria] = { required: true } if required
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def required_field?(options, method)
15
+ if options[:skip_required]
16
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
17
+ false
18
+ elsif options.key?(:required)
19
+ options[:required]
20
+ else
21
+ required_attribute?(object, method)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -5,5 +5,6 @@ module BootstrapForm
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  autoload :Bootstrap
8
+ autoload :Field
8
9
  end
9
10
  end
@@ -9,14 +9,11 @@ module BootstrapForm
9
9
  included do
10
10
  def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_value="0", &block)
11
11
  options = options.symbolize_keys!
12
- check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
13
- :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
14
- check_box_options[:class] = check_box_classes(name, options)
15
12
 
16
13
  tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do
17
- html = check_box_without_bootstrap(name, check_box_options, checked_value, unchecked_value)
18
- html.concat(check_box_label(name, options, checked_value, &block)) unless options[:skip_label]
19
- html.concat(generate_error(name)) if options[:error_message]
14
+ html = check_box_without_bootstrap(name, check_box_options(name, options), checked_value, unchecked_value)
15
+ html << check_box_label(name, options, checked_value, &block) unless options[:skip_label]
16
+ html << generate_error(name) if options[:error_message]
20
17
  html
21
18
  end
22
19
  end
@@ -26,6 +23,13 @@ module BootstrapForm
26
23
 
27
24
  private
28
25
 
26
+ def check_box_options(name, options)
27
+ check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
28
+ :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
29
+ check_box_options[:class] = check_box_classes(name, options)
30
+ check_box_options.merge!(required_field_options(options, name))
31
+ end
32
+
29
33
  def check_box_label(name, options, checked_value, &block)
30
34
  label_name = if options[:multiple]
31
35
  check_box_value(name, checked_value)
@@ -59,6 +63,7 @@ module BootstrapForm
59
63
  def check_box_label_class(options)
60
64
  classes = ["form-check-label"]
61
65
  classes << options[:label_class]
66
+ classes << "required" if options[:required]
62
67
  classes << hide_class if options[:hide_label]
63
68
  classes.flatten.compact
64
69
  end
@@ -72,6 +77,17 @@ module BootstrapForm
72
77
  classes << options[:wrapper_class].presence
73
78
  classes.flatten.compact
74
79
  end
80
+
81
+ def checkbox_required(options, method)
82
+ if options[:skip_required]
83
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
84
+ false
85
+ elsif options.key?(:required)
86
+ options[:required]
87
+ else
88
+ required_attribute?(object, method)
89
+ end
90
+ end
75
91
  end
76
92
  end
77
93
  end
@@ -13,8 +13,8 @@ module BootstrapForm
13
13
  wrapper_attributes[:class] = radio_button_wrapper_class(options)
14
14
  tag.div(**wrapper_attributes) do
15
15
  html = radio_button_without_bootstrap(name, value, radio_button_options(name, options))
16
- html.concat(radio_button_label(name, value, options)) unless options[:skip_label]
17
- html.concat(generate_error(name)) if options[:error_message]
16
+ html << radio_button_label(name, value, options) unless options[:skip_label]
17
+ html << generate_error(name) if options[:error_message]
18
18
  html
19
19
  end
20
20
  end
@@ -28,7 +28,7 @@ module BootstrapForm
28
28
  radio_button_options = options.except(:class, :label, :label_class, :error_message, :help,
29
29
  :inline, :hide_label, :skip_label, :wrapper, :wrapper_class)
30
30
  radio_button_options[:class] = radio_button_classes(name, options)
31
- radio_button_options
31
+ radio_button_options.merge!(required_field_options(options, name))
32
32
  end
33
33
 
34
34
  def radio_button_label(name, value, options)
@@ -1,3 +1,4 @@
1
1
  module BootstrapForm
2
- VERSION = "5.1.0".freeze
2
+ VERSION = "5.2.0".freeze
3
+ REQUIRED_RAILS_VERSION = ">= 6.0".freeze
3
4
  end
@@ -1,7 +1,7 @@
1
1
  # NOTE: The rich_text_area and rich_text_area_tag helpers are defined in a file with a different
2
2
  # name and not in the usual autoload-reachable way.
3
3
  # The following line is definitely need to make `bootstrap_form` work.
4
- if ::Rails::VERSION::STRING > "6"
4
+ if Rails::VERSION::STRING > "6"
5
5
  require "#{Gem::Specification.find_by_name('actiontext').gem_dir}/app/helpers/action_text/tag_helper"
6
6
  end
7
7
  require "action_view"