bootstrap_form 4.4.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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