bootstrap_form 5.4.0 → 5.6.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.
- checksums.yaml +4 -4
- data/.devcontainer.json +38 -0
- data/.github/workflows/ruby.yml +19 -7
- data/.gitignore +21 -7
- data/.rubocop.yml +25 -15
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +53 -111
- data/DOCKER.md +95 -0
- data/Gemfile +7 -1
- data/README.md +166 -100
- data/RELEASING.md +4 -10
- data/Rakefile +34 -16
- data/bootstrap_form.gemspec +1 -1
- data/compose.yml +36 -0
- data/gemfiles/7.2.gemfile +10 -0
- data/gemfiles/8.0.gemfile +10 -0
- data/gemfiles/8.1.gemfile +9 -0
- data/gemfiles/common.gemfile +10 -9
- data/gemfiles/edge.gemfile +5 -1
- data/lib/bootstrap_form/action_view_extensions/form_helper.rb +6 -6
- data/lib/bootstrap_form/components/labels.rb +17 -13
- data/lib/bootstrap_form/components/validation.rb +6 -3
- data/lib/bootstrap_form/configuration.rb +17 -2
- data/lib/bootstrap_form/engine.rb +12 -0
- data/lib/bootstrap_form/form_builder.rb +14 -6
- data/lib/bootstrap_form/form_group.rb +4 -4
- data/lib/bootstrap_form/form_group_builder.rb +17 -8
- data/lib/bootstrap_form/helpers/bootstrap.rb +15 -9
- data/lib/bootstrap_form/helpers/field.rb +2 -0
- data/lib/bootstrap_form/inputs/base.rb +11 -6
- data/lib/bootstrap_form/inputs/check_box.rb +13 -6
- data/lib/bootstrap_form/inputs/collection_check_boxes.rb +3 -8
- data/lib/bootstrap_form/inputs/collection_radio_buttons.rb +1 -0
- data/lib/bootstrap_form/inputs/file_field.rb +1 -1
- data/lib/bootstrap_form/inputs/inputs_collection.rb +67 -11
- data/lib/bootstrap_form/inputs/radio_button.rb +2 -1
- data/lib/bootstrap_form/inputs/range_field.rb +1 -1
- data/lib/bootstrap_form/inputs/rich_text_area.rb +2 -0
- data/lib/bootstrap_form/inputs/submit.rb +3 -1
- data/lib/bootstrap_form/inputs/text_area.rb +2 -0
- data/lib/bootstrap_form/inputs/time_zone_select.rb +1 -1
- data/lib/bootstrap_form/version.rb +4 -2
- data/lib/bootstrap_form.rb +4 -15
- metadata +17 -18
- data/.yarnrc +0 -5
- data/Dockerfile +0 -22
- data/docker-compose-system-test.yml +0 -45
- data/docker-compose.yml +0 -29
- data/gemfiles/6.1.gemfile +0 -4
- data/gemfiles/7.0.gemfile +0 -5
- data/gemfiles/7.1.gemfile +0 -5
data/RELEASING.md
CHANGED
|
@@ -10,28 +10,22 @@ Follow these steps to release a new version of bootstrap_form to rubygems.org.
|
|
|
10
10
|
|
|
11
11
|
## How to release
|
|
12
12
|
|
|
13
|
+
In the `bootstrap_form` repository (not a fork):
|
|
14
|
+
|
|
13
15
|
1. Determine which would be the correct next version number according to [semver](http://semver.org/).
|
|
14
16
|
2. Update the version in `./lib/bootstrap_form/version.rb`.
|
|
15
17
|
3. Make sure that you have all the gems necessary for testing and releasing.
|
|
16
18
|
|
|
17
|
-
BUNDLE_GEMFILE=gemfiles/7.
|
|
19
|
+
BUNDLE_GEMFILE=gemfiles/7.2.gemfile bundle update
|
|
18
20
|
|
|
19
21
|
4. **Ensure the tests are passing by running the tests**
|
|
20
22
|
|
|
21
23
|
(There should be no errors or warnings.)
|
|
22
24
|
|
|
23
|
-
BUNDLE_GEMFILE=gemfiles/7.1.gemfile bundle exec rake test
|
|
24
|
-
|
|
25
|
-
5. **Ensure the demo tests are passing by running**
|
|
26
|
-
|
|
27
|
-
cd demo
|
|
28
|
-
bundle update
|
|
29
25
|
bundle exec rake test:all
|
|
30
|
-
cd -
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
You will have failures in the system tests unless you're running on Linux. Chrome on each operating system renders slightly differently.
|
|
33
28
|
|
|
34
|
-
6. Update the GitHub diff links at the beginning of `CHANGELOG.md` (The pattern should be obvious when you look at them).
|
|
35
29
|
7. Update the installation instructions in `README.md` to use the new version.
|
|
36
30
|
8. Commit the CHANGELOG and version changes in a single commit; the message should be "Preparing vX.Y.Z" where `X.Y.Z` is the version being released.
|
|
37
31
|
9. Tag, push to GitHub, and publish to rubygems.org:
|
data/Rakefile
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
begin
|
|
2
|
-
require
|
|
3
|
-
|
|
4
|
-
require 'bundler/gem_tasks'
|
|
2
|
+
require "bundler/setup"
|
|
3
|
+
require "bundler/gem_tasks"
|
|
5
4
|
require "minitest/test_task"
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
rescue LoadError
|
|
9
|
-
puts
|
|
5
|
+
require "rdoc/task"
|
|
6
|
+
require "rubocop/rake_task"
|
|
7
|
+
rescue LoadError => e
|
|
8
|
+
puts "You must run `bundle install` to run rake tasks (#{e.message})"
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
RDoc::Task.new(:rdoc) do |rdoc|
|
|
13
|
-
rdoc.rdoc_dir =
|
|
14
|
-
rdoc.title =
|
|
15
|
-
rdoc.options <<
|
|
16
|
-
rdoc.rdoc_files.include(
|
|
17
|
-
rdoc.rdoc_files.include(
|
|
12
|
+
rdoc.rdoc_dir = "rdoc"
|
|
13
|
+
rdoc.title = "BootstrapForm"
|
|
14
|
+
rdoc.options << "--line-numbers"
|
|
15
|
+
rdoc.rdoc_files.include("README.md")
|
|
16
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
Minitest::TestTask.create(:test) do |t|
|
|
21
|
-
t.
|
|
22
|
-
t.libs << "lib"
|
|
23
|
-
t.warning = false
|
|
20
|
+
t.warning = true
|
|
24
21
|
t.test_globs = ["test/**/*_test.rb"]
|
|
25
22
|
end
|
|
26
23
|
|
|
27
|
-
desc
|
|
24
|
+
desc "Run RuboCop checks"
|
|
28
25
|
RuboCop::RakeTask.new(:rubocop)
|
|
29
26
|
|
|
30
27
|
task default: %i[test rubocop:autocorrect]
|
|
28
|
+
|
|
29
|
+
namespace :test do
|
|
30
|
+
desc "Run tests for all supported Rails versions, with current Ruby version"
|
|
31
|
+
task :all do
|
|
32
|
+
gemfiles.each do |gemfile|
|
|
33
|
+
system("BUNDLE_GEMFILE=#{gemfile} rake test")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Dir.chdir("demo")
|
|
37
|
+
system("BUNDLE_GEMFILE= rake test:all")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "Update gem .lock files e.g. for changed Ruby version"
|
|
42
|
+
task :update_gemfile_locks do
|
|
43
|
+
gemfiles.append("Gemfile").each do |gemfile|
|
|
44
|
+
system("BUNDLE_GEMFILE=#{gemfile} bundle update --bundler")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def gemfiles = Dir.glob("gemfiles/*.gemfile").reject { |f| File.basename(f) == "common.gemfile" }
|
data/bootstrap_form.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
s.bindir = "exe"
|
|
24
24
|
s.require_paths = ["lib"]
|
|
25
25
|
|
|
26
|
-
s.required_ruby_version = ">= 3.
|
|
26
|
+
s.required_ruby_version = ">= 3.2"
|
|
27
27
|
|
|
28
28
|
s.add_dependency("actionpack", BootstrapForm::REQUIRED_RAILS_VERSION)
|
|
29
29
|
s.add_dependency("activemodel", BootstrapForm::REQUIRED_RAILS_VERSION)
|
data/compose.yml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Set up the Selenium container based on the Selenium official:
|
|
2
|
+
# https://github.com/SeleniumHQ/docker-selenium/blob/trunk/docker-compose-v3.yml
|
|
3
|
+
# And:
|
|
4
|
+
# https://medium.com/@retrorubies/chrome-as-a-service-for-rails-testing-b1a45e70fec1
|
|
5
|
+
services:
|
|
6
|
+
web:
|
|
7
|
+
image: lenchoreyes/jade:rails-app-${RUBY_VERSION:-3.3}-sqlite-${DISTRO:-bookworm}
|
|
8
|
+
stdin_open: true
|
|
9
|
+
tty: true
|
|
10
|
+
volumes:
|
|
11
|
+
- .:/app:cached
|
|
12
|
+
environment:
|
|
13
|
+
- HISTFILE=/app/.bash_history
|
|
14
|
+
- SELENIUM_HOST=selenium
|
|
15
|
+
- SELENIUM_PORT=4444
|
|
16
|
+
- TEST_APP_HOST=web
|
|
17
|
+
- TEST_APP_PORT=3001
|
|
18
|
+
ports:
|
|
19
|
+
- "3000"
|
|
20
|
+
- "3001"
|
|
21
|
+
command: /bin/bash
|
|
22
|
+
|
|
23
|
+
selenium:
|
|
24
|
+
image: selenium/standalone-chrome:133.0
|
|
25
|
+
shm_size: 2gb
|
|
26
|
+
logging:
|
|
27
|
+
driver: none
|
|
28
|
+
stdin_open: true
|
|
29
|
+
tty: true
|
|
30
|
+
environment:
|
|
31
|
+
- LANG=en_CA.UTF-8
|
|
32
|
+
- LANGUAGE=en_CA.UTF-8
|
|
33
|
+
- LANG_WHERE=CA
|
|
34
|
+
- LANG_WHICH=en
|
|
35
|
+
ports:
|
|
36
|
+
- "7900"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
gems = "#{__dir__}/common.gemfile"
|
|
2
|
+
eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
3
|
+
|
|
4
|
+
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
|
+
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "minitest", "~> 5.0"
|
|
7
|
+
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
8
|
+
gem "rails", "~> 7.2.0"
|
|
9
|
+
gem "sprockets-rails", require: "sprockets/railtie"
|
|
10
|
+
gem "sqlite3"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
gems = "#{__dir__}/common.gemfile"
|
|
2
|
+
eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
3
|
+
|
|
4
|
+
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
|
+
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "minitest", "~> 5.0"
|
|
7
|
+
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
8
|
+
gem "propshaft"
|
|
9
|
+
gem "rails", "~> 8.0.1"
|
|
10
|
+
gem "sqlite3"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
gems = "#{__dir__}/common.gemfile"
|
|
2
|
+
eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
3
|
+
|
|
4
|
+
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
|
+
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
7
|
+
gem "propshaft"
|
|
8
|
+
gem "rails", "~> 8.1.0"
|
|
9
|
+
gem "sqlite3"
|
data/gemfiles/common.gemfile
CHANGED
|
@@ -4,23 +4,24 @@ gemspec path: File.dirname(__dir__)
|
|
|
4
4
|
|
|
5
5
|
# To test with different Rails versions, use the files in `./gemfiles`
|
|
6
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
7
|
group :test do
|
|
8
|
+
gem "capybara-screenshot-diff", require: false
|
|
9
|
+
gem "chunky_png", "~> 1.4"
|
|
15
10
|
gem "diffy"
|
|
16
11
|
gem "equivalent-xml"
|
|
12
|
+
gem "minitest-mock"
|
|
17
13
|
gem "mocha"
|
|
18
|
-
gem "
|
|
14
|
+
gem "selenium-webdriver"
|
|
19
15
|
end
|
|
20
16
|
|
|
21
17
|
group :development, :test do
|
|
22
18
|
gem "debug"
|
|
23
|
-
gem "
|
|
19
|
+
gem "htmlbeautifier"
|
|
20
|
+
gem "ostruct" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.5.0")
|
|
21
|
+
gem "puma"
|
|
22
|
+
gem "rubocop-performance", require: false
|
|
23
|
+
gem "rubocop-rails", require: false
|
|
24
|
+
gem "warning"
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
group :ci do
|
data/gemfiles/edge.gemfile
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
gems = "#{__dir__}/common.gemfile"
|
|
2
2
|
eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
3
3
|
|
|
4
|
+
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
|
+
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
7
|
+
gem "propshaft"
|
|
4
8
|
gem "rails", git: "https://github.com/rails/rails.git", branch: "main"
|
|
5
|
-
gem "
|
|
9
|
+
gem "sqlite3"
|
|
@@ -33,20 +33,20 @@ module BootstrapForm
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def bootstrap_form_tag(options={}, &
|
|
36
|
+
def bootstrap_form_tag(options={}, &)
|
|
37
37
|
options[:acts_like_form_tag] = true
|
|
38
38
|
|
|
39
|
-
bootstrap_form_for("", options, &
|
|
39
|
+
bootstrap_form_for("", options, &)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def bootstrap_fields_for(record_name, record_object=nil, options={}, &
|
|
42
|
+
def bootstrap_fields_for(record_name, record_object=nil, options={}, &)
|
|
43
43
|
options[:builder] = BootstrapForm::FormBuilder
|
|
44
|
-
fields_for(record_name, record_object, options, &
|
|
44
|
+
fields_for(record_name, record_object, options, &)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def bootstrap_fields(scope=nil, model: nil, **options, &
|
|
47
|
+
def bootstrap_fields(scope=nil, model: nil, **options, &)
|
|
48
48
|
options[:builder] = BootstrapForm::FormBuilder
|
|
49
|
-
fields(scope, model: model, **options, &
|
|
49
|
+
fields(scope, model: model, **options, &)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
private
|
|
@@ -10,16 +10,8 @@ module BootstrapForm
|
|
|
10
10
|
def generate_label(id, name, options, custom_label_col, group_layout)
|
|
11
11
|
return if options.blank?
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# been passed to generate_label's caller, and definitely doesn't include
|
|
16
|
-
# :id.
|
|
17
|
-
options[:for] = id if acts_like_form_tag
|
|
18
|
-
|
|
19
|
-
options[:class] = label_classes(name, options, custom_label_col, group_layout)
|
|
20
|
-
options.delete(:class) if options[:class].none?
|
|
21
|
-
|
|
22
|
-
label(name, label_text(name, options), options.except(:text))
|
|
13
|
+
prepare_label_options(id, name, options, custom_label_col, group_layout)
|
|
14
|
+
label(name, label_text(name, options[:text]), options.except(:text))
|
|
23
15
|
end
|
|
24
16
|
|
|
25
17
|
def label_classes(name, options, custom_label_col, group_layout)
|
|
@@ -34,7 +26,7 @@ module BootstrapForm
|
|
|
34
26
|
|
|
35
27
|
def label_layout_classes(custom_label_col, group_layout)
|
|
36
28
|
if layout_horizontal?(group_layout)
|
|
37
|
-
["col-form-label",
|
|
29
|
+
["col-form-label", custom_label_col || label_col]
|
|
38
30
|
elsif layout_inline?(group_layout)
|
|
39
31
|
%w[form-label me-sm-2]
|
|
40
32
|
else
|
|
@@ -42,14 +34,26 @@ module BootstrapForm
|
|
|
42
34
|
end
|
|
43
35
|
end
|
|
44
36
|
|
|
45
|
-
def label_text(name,
|
|
46
|
-
label =
|
|
37
|
+
def label_text(name, text)
|
|
38
|
+
label = text || object&.class&.try(:human_attribute_name, name)&.html_safe # rubocop:disable Rails/OutputSafety, Style/SafeNavigationChainLength
|
|
47
39
|
if label_errors && error?(name)
|
|
48
40
|
(" ".html_safe + get_error_messages(name)).prepend(label)
|
|
49
41
|
else
|
|
50
42
|
label
|
|
51
43
|
end
|
|
52
44
|
end
|
|
45
|
+
|
|
46
|
+
def prepare_label_options(id, name, options, custom_label_col, group_layout)
|
|
47
|
+
# id is the caller's options[:id] at the only place this method is called.
|
|
48
|
+
# The options argument is a small subset of the options that might have
|
|
49
|
+
# been passed to generate_label's caller, and definitely doesn't include
|
|
50
|
+
# :id.
|
|
51
|
+
options[:for] = id if acts_like_form_tag
|
|
52
|
+
|
|
53
|
+
options[:class] = label_classes(name, options, custom_label_col, group_layout)
|
|
54
|
+
options.delete(:class) if options[:class].none?
|
|
55
|
+
options[:id] = aria_feedback_id(name:, id:) if error?(name) && label_errors
|
|
56
|
+
end
|
|
53
57
|
end
|
|
54
58
|
end
|
|
55
59
|
end
|
|
@@ -61,17 +61,16 @@ module BootstrapForm
|
|
|
61
61
|
error?(name) && inline_errors
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def generate_error(name)
|
|
64
|
+
def generate_error(name, id)
|
|
65
65
|
return unless inline_error?(name)
|
|
66
66
|
|
|
67
67
|
help_text = get_error_messages(name)
|
|
68
68
|
help_klass = "invalid-feedback"
|
|
69
69
|
help_tag = :div
|
|
70
70
|
|
|
71
|
-
content_tag(help_tag, help_text, class: help_klass)
|
|
71
|
+
content_tag(help_tag, help_text, class: help_klass, id: aria_feedback_id(id:, name:))
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# rubocop:disable Metrics/AbcSize
|
|
75
74
|
def get_error_messages(name)
|
|
76
75
|
object.class.try(:reflections)&.each do |association_name, a|
|
|
77
76
|
next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
|
@@ -85,6 +84,10 @@ module BootstrapForm
|
|
|
85
84
|
safe_join(object.errors[name], ", ")
|
|
86
85
|
end
|
|
87
86
|
# rubocop:enable Metrics/AbcSize
|
|
87
|
+
|
|
88
|
+
def aria_feedback_id(name:, id: nil)
|
|
89
|
+
id.present? ? "#{id}_feedback" : field_id(name, :feedback)
|
|
90
|
+
end
|
|
88
91
|
end
|
|
89
92
|
end
|
|
90
93
|
end
|
|
@@ -7,16 +7,31 @@ module BootstrapForm
|
|
|
7
7
|
when nil
|
|
8
8
|
@default_form_attributes = {}
|
|
9
9
|
when Hash
|
|
10
|
+
BootstrapForm.deprecator.warn(<<~MESSAGE.squish)
|
|
11
|
+
BootstrapForm::Configuration#default_form_attributes= will be removed in a future release.
|
|
12
|
+
Please use BootstrapForm.config.default_form_attributes= instead.
|
|
13
|
+
MESSAGE
|
|
10
14
|
@default_form_attributes = attributes
|
|
15
|
+
BootstrapForm.config.default_form_attributes = attributes
|
|
11
16
|
else
|
|
12
17
|
raise ArgumentError, "Unsupported default_form_attributes #{attributes.inspect}"
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def default_form_attributes
|
|
17
|
-
|
|
22
|
+
BootstrapForm.deprecator.warn(<<~MESSAGE.squish)
|
|
23
|
+
BootstrapForm::Configuration#default_form_attributes will be removed in a future release.
|
|
24
|
+
Please use BootstrapForm.config.default_form_attributes instead.
|
|
25
|
+
MESSAGE
|
|
26
|
+
BootstrapForm.config.default_form_attributes
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
mattr_accessor :config, default: ActiveSupport::OrderedOptions.new
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
class << self
|
|
33
|
+
def configure
|
|
34
|
+
yield(config) if block_given?
|
|
20
35
|
end
|
|
21
36
|
end
|
|
22
37
|
end
|
|
@@ -6,5 +6,17 @@ module BootstrapForm
|
|
|
6
6
|
class Engine < Rails::Engine
|
|
7
7
|
config.eager_load_namespaces << BootstrapForm
|
|
8
8
|
config.autoload_paths << File.expand_path("lib", __dir__)
|
|
9
|
+
|
|
10
|
+
config.bootstrap_form = BootstrapForm.config
|
|
11
|
+
config.bootstrap_form.default_form_attributes ||= {}
|
|
12
|
+
config.bootstrap_form.group_around_collections = Rails.env.development? if config.bootstrap_form.group_around_collections.nil?
|
|
13
|
+
|
|
14
|
+
initializer "bootstrap_form.configure" do |app|
|
|
15
|
+
BootstrapForm.config = app.config.bootstrap_form
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "bootstrap_form.deprecator" do |app|
|
|
19
|
+
app.deprecators[:bootstrap_form] = BootstrapForm.deprecator
|
|
20
|
+
end
|
|
9
21
|
end
|
|
10
22
|
end
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# require 'bootstrap_form/aliasing'
|
|
2
4
|
|
|
3
5
|
module BootstrapForm
|
|
4
|
-
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
6
|
+
class FormBuilder < ActionView::Helpers::FormBuilder # rubocop:disable Metrics/ClassLength
|
|
5
7
|
attr_reader :layout, :label_col, :control_col, :has_error, :inline_errors,
|
|
6
8
|
:label_errors, :acts_like_form_tag
|
|
7
9
|
|
|
10
|
+
class << self
|
|
11
|
+
def redefine_rich_text_area?
|
|
12
|
+
ActionView::Helpers::FormBuilder.instance_methods.any? { _1 == :rich_text_area }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
include BootstrapForm::Helpers::Field
|
|
9
17
|
include BootstrapForm::Helpers::Bootstrap
|
|
10
18
|
|
|
@@ -32,7 +40,7 @@ module BootstrapForm
|
|
|
32
40
|
include BootstrapForm::Inputs::PhoneField
|
|
33
41
|
include BootstrapForm::Inputs::RadioButton
|
|
34
42
|
include BootstrapForm::Inputs::RangeField
|
|
35
|
-
include BootstrapForm::Inputs::RichTextArea
|
|
43
|
+
include BootstrapForm::Inputs::RichTextArea if redefine_rich_text_area?
|
|
36
44
|
include BootstrapForm::Inputs::SearchField
|
|
37
45
|
include BootstrapForm::Inputs::Select
|
|
38
46
|
include BootstrapForm::Inputs::Submit
|
|
@@ -72,10 +80,10 @@ module BootstrapForm
|
|
|
72
80
|
.compact.uniq, " ")
|
|
73
81
|
end
|
|
74
82
|
|
|
75
|
-
def fields_for_with_bootstrap(record_name, record_object=nil, fields_options={}, &
|
|
83
|
+
def fields_for_with_bootstrap(record_name, record_object=nil, fields_options={}, &)
|
|
76
84
|
fields_options = fields_for_options(record_object, fields_options)
|
|
77
85
|
record_object = nil if record_object.is_a?(Hash) && record_object.extractable_options?
|
|
78
|
-
fields_for_without_bootstrap(record_name, record_object, fields_options, &
|
|
86
|
+
fields_for_without_bootstrap(record_name, record_object, fields_options, &)
|
|
79
87
|
end
|
|
80
88
|
|
|
81
89
|
bootstrap_alias :fields_for
|
|
@@ -90,9 +98,9 @@ module BootstrapForm
|
|
|
90
98
|
field_options = fields_options
|
|
91
99
|
field_options = record_object if record_object.is_a?(Hash) && record_object.extractable_options?
|
|
92
100
|
%i[layout control_col inline_errors label_errors].each do |option|
|
|
93
|
-
field_options[option]
|
|
101
|
+
field_options[option] = field_options.key?(option) ? field_options[option] : options[option]
|
|
94
102
|
end
|
|
95
|
-
field_options[:label_col] = field_options[:label_col].present? ?
|
|
103
|
+
field_options[:label_col] = field_options[:label_col].present? ? field_options[:label_col].to_s : options[:label_col]
|
|
96
104
|
field_options
|
|
97
105
|
end
|
|
98
106
|
|
|
@@ -23,21 +23,21 @@ module BootstrapForm
|
|
|
23
23
|
html_class = control_specific_class(field_name)
|
|
24
24
|
html_class = "#{html_class} col-auto g-3" if @layout == :horizontal && options[:skip_inline].blank?
|
|
25
25
|
tag.div(class: html_class) do
|
|
26
|
-
input_with_error(name) do
|
|
26
|
+
input_with_error(name, options[:id]) do
|
|
27
27
|
send(without_field_name, name, options, html_options)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def form_group_content(label, help_text, options, &
|
|
32
|
+
def form_group_content(label, help_text, options, &)
|
|
33
33
|
label ||= ActiveSupport::SafeBuffer.new
|
|
34
34
|
if group_layout_horizontal?(options[:layout])
|
|
35
|
-
label + tag.div(capture(&
|
|
35
|
+
label + tag.div(capture(&) + help_text, class: form_group_control_class(options))
|
|
36
36
|
else
|
|
37
37
|
content = ActiveSupport::SafeBuffer.new
|
|
38
38
|
# Floating labels need to be rendered after the field
|
|
39
39
|
content << label unless options[:floating]
|
|
40
|
-
content << capture(&
|
|
40
|
+
content << capture(&)
|
|
41
41
|
content << label if options[:floating]
|
|
42
42
|
content << help_text if help_text
|
|
43
43
|
content
|
|
@@ -6,7 +6,17 @@ 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, &)
|
|
10
|
+
form_group_builder_wrapper(method, options, html_options) do |form_group_options, no_wrapper|
|
|
11
|
+
if no_wrapper
|
|
12
|
+
yield
|
|
13
|
+
else
|
|
14
|
+
form_group(method, form_group_options, &)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def form_group_builder_wrapper(method, options, html_options=nil)
|
|
10
20
|
no_wrapper = options[:wrapper] == false
|
|
11
21
|
|
|
12
22
|
options = form_group_builder_options(options, method)
|
|
@@ -18,11 +28,7 @@ module BootstrapForm
|
|
|
18
28
|
:hide_label, :skip_required, :label_as_placeholder, :wrapper_class, :wrapper
|
|
19
29
|
)
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
yield
|
|
23
|
-
else
|
|
24
|
-
form_group(method, form_group_options, &block)
|
|
25
|
-
end
|
|
31
|
+
yield(form_group_options, no_wrapper)
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def form_group_builder_options(options, method)
|
|
@@ -92,13 +98,16 @@ module BootstrapForm
|
|
|
92
98
|
# Add control_class; allow it to be overridden by :control_class option
|
|
93
99
|
control_classes = css_options.delete(:control_class) { control_class }
|
|
94
100
|
css_options[:class] = safe_join([control_classes, css_options[:class]].compact, " ")
|
|
95
|
-
|
|
101
|
+
if error?(method)
|
|
102
|
+
css_options[:class] << " is-invalid"
|
|
103
|
+
css_options[:aria] = { describedby: aria_feedback_id(id: options[:id], name: method) }
|
|
104
|
+
end
|
|
96
105
|
css_options[:placeholder] = form_group_placeholder(options, method) if options[:label_as_placeholder]
|
|
97
106
|
css_options
|
|
98
107
|
end
|
|
99
108
|
|
|
100
109
|
def form_group_placeholder(options, method)
|
|
101
|
-
form_group_label_text(options[:label]) || object.class.human_attribute_name(method)
|
|
110
|
+
form_group_label_text(options[:label]) || (object && object.class.human_attribute_name(method)) || method.to_s.humanize # rubocop:disable Style/SafeNavigation
|
|
102
111
|
end
|
|
103
112
|
end
|
|
104
113
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module BootstrapForm
|
|
2
4
|
module Helpers
|
|
3
5
|
module Bootstrap
|
|
@@ -32,7 +34,10 @@ module BootstrapForm
|
|
|
32
34
|
hide_attribute_name = options[:hide_attribute_name] || false
|
|
33
35
|
custom_class = options[:custom_class] || false
|
|
34
36
|
|
|
35
|
-
tag.div
|
|
37
|
+
tag.div(
|
|
38
|
+
class: custom_class || "invalid-feedback",
|
|
39
|
+
id: aria_feedback_id(id: options[:id], name:)
|
|
40
|
+
) do
|
|
36
41
|
errors = if hide_attribute_name
|
|
37
42
|
object.errors[name]
|
|
38
43
|
else
|
|
@@ -56,28 +61,29 @@ module BootstrapForm
|
|
|
56
61
|
text_field_with_bootstrap(name, static_options)
|
|
57
62
|
end
|
|
58
63
|
|
|
59
|
-
def custom_control(*args, &
|
|
64
|
+
def custom_control(*args, &)
|
|
60
65
|
options = args.extract_options!
|
|
61
66
|
name = args.first
|
|
62
67
|
|
|
63
|
-
form_group_builder(name, options, &
|
|
68
|
+
form_group_builder(name, options, &)
|
|
64
69
|
end
|
|
65
70
|
|
|
66
|
-
def prepend_and_append_input(name, options, &
|
|
71
|
+
def prepend_and_append_input(name, options, &)
|
|
72
|
+
id = options[:id]
|
|
67
73
|
options = options.extract!(:prepend, :append, :input_group_class).compact
|
|
68
74
|
|
|
69
|
-
input = capture(&
|
|
75
|
+
input = capture(&) || ActiveSupport::SafeBuffer.new
|
|
70
76
|
|
|
71
77
|
input = attach_input(options, :prepend) + input + attach_input(options, :append)
|
|
72
|
-
input << generate_error(name)
|
|
78
|
+
input << generate_error(name, id)
|
|
73
79
|
options.present? &&
|
|
74
80
|
input = tag.div(input, class: ["input-group", options[:input_group_class]].compact)
|
|
75
81
|
input
|
|
76
82
|
end
|
|
77
83
|
|
|
78
|
-
def input_with_error(name, &
|
|
79
|
-
input = capture(&
|
|
80
|
-
input << generate_error(name)
|
|
84
|
+
def input_with_error(name, id, &)
|
|
85
|
+
input = capture(&)
|
|
86
|
+
input << generate_error(name, id)
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
def input_group_content(content)
|
|
@@ -6,13 +6,15 @@ module BootstrapForm
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
class_methods do
|
|
9
|
-
def bootstrap_field(field_name
|
|
10
|
-
define_method "#{field_name}_with_bootstrap" do |name, options={
|
|
9
|
+
def bootstrap_field(field_name)
|
|
10
|
+
define_method :"#{field_name}_with_bootstrap" do |name, options={}|
|
|
11
11
|
warn_deprecated_layout_value(options)
|
|
12
|
+
options = options.reverse_merge(control_class: "form-range") if field_name == :range_field
|
|
13
|
+
options = options.reverse_merge(control_class: "form-control form-control-color") if field_name == :color_field
|
|
12
14
|
form_group_builder(name, options) do
|
|
13
15
|
prepend_and_append_input(name, options) do
|
|
14
16
|
options[:placeholder] ||= name if options[:floating]
|
|
15
|
-
send("#{field_name}_without_bootstrap"
|
|
17
|
+
send(:"#{field_name}_without_bootstrap", name, options.except(:floating))
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -21,7 +23,10 @@ module BootstrapForm
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def bootstrap_select_group(field_name)
|
|
24
|
-
define_method("#{field_name}_with_bootstrap") do |name, options={}, html_options={}|
|
|
26
|
+
define_method(:"#{field_name}_with_bootstrap") do |name, options={}, html_options={}|
|
|
27
|
+
# Specifying the id for a select doesn't work. The Rails helpers need to generate
|
|
28
|
+
# what they generate, and that includes the ids for each select option.
|
|
29
|
+
options.delete(:id)
|
|
25
30
|
html_options = html_options.reverse_merge(control_class: "form-select")
|
|
26
31
|
form_group_builder(name, options, html_options) do
|
|
27
32
|
form_group_content_tag(name, field_name, "#{field_name}_without_bootstrap", options, html_options)
|
|
@@ -38,8 +43,8 @@ module BootstrapForm
|
|
|
38
43
|
# if your application does not include the actiontext dependency due to
|
|
39
44
|
# `rich_text_area` not being defined.
|
|
40
45
|
def bootstrap_alias(field_name)
|
|
41
|
-
alias_method "#{field_name}_without_bootstrap"
|
|
42
|
-
alias_method field_name, "#{field_name}_with_bootstrap"
|
|
46
|
+
alias_method :"#{field_name}_without_bootstrap", field_name
|
|
47
|
+
alias_method field_name, :"#{field_name}_with_bootstrap"
|
|
43
48
|
rescue NameError # rubocop:disable Lint/SuppressedException
|
|
44
49
|
end
|
|
45
50
|
end
|