bootstrap_form 5.5.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/.github/workflows/ruby.yml +10 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +22 -10
- data/CONTRIBUTING.md +20 -3
- data/Gemfile +2 -0
- data/README.md +32 -29
- data/RELEASING.md +2 -1
- data/Rakefile +24 -29
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/common.gemfile +9 -7
- data/lib/bootstrap_form/components/labels.rb +16 -12
- data/lib/bootstrap_form/components/validation.rb +6 -2
- data/lib/bootstrap_form/configuration.rb +17 -2
- data/lib/bootstrap_form/engine.rb +12 -0
- data/lib/bootstrap_form/form_builder.rb +2 -0
- data/lib/bootstrap_form/form_group.rb +1 -1
- data/lib/bootstrap_form/form_group_builder.rb +16 -7
- data/lib/bootstrap_form/helpers/bootstrap.rb +10 -4
- data/lib/bootstrap_form/helpers/field.rb +2 -0
- data/lib/bootstrap_form/inputs/base.rb +3 -0
- data/lib/bootstrap_form/inputs/check_box.rb +2 -1
- data/lib/bootstrap_form/inputs/collection_check_boxes.rb +1 -0
- data/lib/bootstrap_form/inputs/collection_radio_buttons.rb +1 -0
- 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/submit.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 +5 -6
- metadata +2 -3
- data/.yarnrc +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '080b03659b41c88ebbce90498ef35b440c8f41f557b290369a7193c3d8ae3e3e'
|
|
4
|
+
data.tar.gz: 6659f30906b3c7ce3b01f276b7e12dc777ef8fdd0213977aeb42dccdd7b5ef0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a39ff618a9f881079640c35c345a1e64e376259bad7eeaf20442a22804083ec1661b2d2babb9ef4dbcf420eb7b5681f2907dc68df20688c3234bf1ebc776a98
|
|
7
|
+
data.tar.gz: df4ce96547bd4e460d5e6960262c74619a377a7ae47d5dcac3e930c0a39f175a8853017e4536419a8e179c91c53930a8902a53be176c61dea2013a5cf9b492c6
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -11,12 +11,12 @@ jobs:
|
|
|
11
11
|
env:
|
|
12
12
|
BUNDLE_GEMFILE: gemfiles/7.2.gemfile
|
|
13
13
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v5
|
|
15
15
|
with:
|
|
16
16
|
fetch-depth: 0
|
|
17
17
|
- uses: ruby/setup-ruby@v1
|
|
18
18
|
with:
|
|
19
|
-
ruby-version: 3.2
|
|
19
|
+
ruby-version: 3.2
|
|
20
20
|
bundler-cache: true
|
|
21
21
|
# Disabled since it requires access not granted by GitHub Actions for PRs
|
|
22
22
|
# - name: Danger
|
|
@@ -34,10 +34,13 @@ jobs:
|
|
|
34
34
|
matrix:
|
|
35
35
|
ruby-version: [ '3.4', '3.3', '3.2', 'ruby-head' ]
|
|
36
36
|
gemfile: [ '8.1', '8.0', '7.2', 'edge' ]
|
|
37
|
+
exclude:
|
|
38
|
+
- ruby-version: 3.2
|
|
39
|
+
- gemfile: 'edge'
|
|
37
40
|
env:
|
|
38
41
|
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
|
39
42
|
steps:
|
|
40
|
-
- uses: actions/checkout@
|
|
43
|
+
- uses: actions/checkout@v5
|
|
41
44
|
- name: Set up Ruby
|
|
42
45
|
uses: ruby/setup-ruby@v1
|
|
43
46
|
with:
|
|
@@ -48,13 +51,15 @@ jobs:
|
|
|
48
51
|
Demo:
|
|
49
52
|
runs-on: ubuntu-latest
|
|
50
53
|
steps:
|
|
51
|
-
- uses: actions/checkout@
|
|
54
|
+
- uses: actions/checkout@v5
|
|
52
55
|
- name: Set up Ruby
|
|
53
56
|
uses: ruby/setup-ruby@v1
|
|
54
57
|
with:
|
|
55
58
|
working-directory: demo
|
|
56
59
|
bundler-cache: true
|
|
57
|
-
ruby-version: 3.4
|
|
60
|
+
ruby-version: 3.4
|
|
61
|
+
env:
|
|
62
|
+
BUNDLE_GEMFILE: '../Gemfile'
|
|
58
63
|
- name: Run tests
|
|
59
64
|
working-directory: demo
|
|
60
65
|
run: bundle exec rake test:all
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -6,7 +6,7 @@ AllCops:
|
|
|
6
6
|
DisplayCopNames: true
|
|
7
7
|
DisplayStyleGuide: true
|
|
8
8
|
TargetRubyVersion: 3.2
|
|
9
|
-
TargetRailsVersion: 7.
|
|
9
|
+
TargetRailsVersion: 7.2
|
|
10
10
|
NewCops: enable
|
|
11
11
|
Exclude:
|
|
12
12
|
- bin/*
|
|
@@ -25,7 +25,7 @@ AllCops:
|
|
|
25
25
|
- gemfiles/vendor/bundle/**/*
|
|
26
26
|
- vendor/bundle/**/*
|
|
27
27
|
- Guardfile
|
|
28
|
-
-
|
|
28
|
+
- test/dummy/**/*
|
|
29
29
|
- vendor/**/*
|
|
30
30
|
|
|
31
31
|
Layout/LineLength:
|
|
@@ -54,11 +54,7 @@ Metrics/ClassLength:
|
|
|
54
54
|
- "test/**/*"
|
|
55
55
|
|
|
56
56
|
Metrics/MethodLength:
|
|
57
|
-
|
|
58
|
-
Exclude:
|
|
59
|
-
- "demo/db/migrate/*"
|
|
60
|
-
- "demo/test/**/*"
|
|
61
|
-
- "test/**/*"
|
|
57
|
+
Enabled: false
|
|
62
58
|
|
|
63
59
|
Naming/MemoizedInstanceVariableName:
|
|
64
60
|
EnforcedStyleForLeadingUnderscores: optional
|
|
@@ -73,6 +69,10 @@ Rails/ApplicationRecord:
|
|
|
73
69
|
Exclude:
|
|
74
70
|
- "demo/db/migrate/**"
|
|
75
71
|
|
|
72
|
+
Rails/RakeEnvironment:
|
|
73
|
+
Exclude:
|
|
74
|
+
- "Rakefile"
|
|
75
|
+
|
|
76
76
|
Rails/RefuteMethods:
|
|
77
77
|
Enabled: false
|
|
78
78
|
|
|
@@ -94,9 +94,6 @@ Style/DoubleNegation:
|
|
|
94
94
|
Style/EmptyMethod:
|
|
95
95
|
Enabled: false
|
|
96
96
|
|
|
97
|
-
Style/FrozenStringLiteralComment:
|
|
98
|
-
Enabled: false
|
|
99
|
-
|
|
100
97
|
Style/NumericPredicate:
|
|
101
98
|
Enabled: false
|
|
102
99
|
|
|
@@ -105,3 +102,18 @@ Style/StringLiterals:
|
|
|
105
102
|
|
|
106
103
|
Style/TrivialAccessors:
|
|
107
104
|
AllowPredicates: true
|
|
105
|
+
|
|
106
|
+
Style/FrozenStringLiteralComment:
|
|
107
|
+
Enabled: true
|
|
108
|
+
EnforcedStyle: always # or 'always' or 'never' depending on your preference
|
|
109
|
+
SafeAutoCorrect: true # Set to true for safe autocorrection, false if you need to review changes
|
|
110
|
+
Exclude:
|
|
111
|
+
- ./**/Gemfile*
|
|
112
|
+
- bootstrap_form.gemspec
|
|
113
|
+
- Dangerfile
|
|
114
|
+
- demo/config/**/*
|
|
115
|
+
- demo/config.ru
|
|
116
|
+
- demo/db/migrate/**/*
|
|
117
|
+
- gemfiles/*
|
|
118
|
+
- Rakefile
|
|
119
|
+
- Vagrantfile
|
data/CONTRIBUTING.md
CHANGED
|
@@ -6,9 +6,26 @@ We want everyone to feel welcome to contribute. We encourage respectful exchange
|
|
|
6
6
|
|
|
7
7
|
There are a number of ways you can contribute to `bootstrap_form`:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
9
|
+
- Test pre-release versions of the gem, by using the `main` branch.
|
|
10
|
+
- Fix a bug or add a new feature.
|
|
11
|
+
- Add to the documentation.
|
|
12
|
+
- Review pull requests.
|
|
13
|
+
|
|
14
|
+
## Testing
|
|
15
|
+
|
|
16
|
+
`bootstrap_form` has an extensive automated test suite. But there are a multitude of combinations of use cases for this gem. It's impossible to cover all the combinations.
|
|
17
|
+
|
|
18
|
+
You can help us test by using the `main` branch from GitHub, rather than a released version of the gem. To do so, replace the `bootstrap_form` line in your `Gemfile` with:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
gem "bootstrap_form", git: "https://bootstrap-ruby/bootstrap_form"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
and run `bundle update`.
|
|
25
|
+
|
|
26
|
+
If you run into problems, you can always switch back to the released version of `bootstrap_form` from your provider of gems. Please [raise an issue](https://github.com/bootstrap-ruby/bootstrap_form/issues/new/choose) if you think you found a problem.
|
|
27
|
+
|
|
28
|
+
And a big thank-you for helping us test `bootstrap_form`.
|
|
12
29
|
|
|
13
30
|
## Code Contributions
|
|
14
31
|
|
data/Gemfile
CHANGED
|
@@ -4,7 +4,9 @@ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
|
4
4
|
require "#{__dir__}/lib/bootstrap_form/version"
|
|
5
5
|
|
|
6
6
|
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
7
|
+
gem "cssbundling-rails"
|
|
7
8
|
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
9
|
+
gem "jsbundling-rails"
|
|
8
10
|
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
9
11
|
gem "rails", BootstrapForm::REQUIRED_RAILS_VERSION
|
|
10
12
|
gem "propshaft"
|
data/README.md
CHANGED
|
@@ -52,7 +52,7 @@ You can use this gem with other ways of installing Bootstrap, but how to do so i
|
|
|
52
52
|
Once Bootstrap is installed, add the `bootstrap_form` gem to your `Gemfile`:
|
|
53
53
|
|
|
54
54
|
```ruby
|
|
55
|
-
gem "bootstrap_form", "~> 5.
|
|
55
|
+
gem "bootstrap_form", "~> 5.6"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Then:
|
|
@@ -120,6 +120,8 @@ This generates the following HTML:
|
|
|
120
120
|
</form>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
Note: All examples in this README are generated with the configuration option `group_around_collections` set to `true`. See the [Configuration](#configuration) section.
|
|
124
|
+
|
|
123
125
|
### bootstrap_form_tag
|
|
124
126
|
|
|
125
127
|
If your form is not backed by a model, use the `bootstrap_form_tag`. Usage of this helper is the same as `bootstrap_form_for`, except no model object is passed in as the first argument. Here's an example:
|
|
@@ -226,13 +228,14 @@ Generated HTML:
|
|
|
226
228
|
|
|
227
229
|
## Configuration
|
|
228
230
|
|
|
229
|
-
`bootstrap_form` can be used out-of-the-box without any configuration. However, `bootstrap_form` does have an optional configuration file at `config/initializers/bootstrap_form.rb` for setting options that affect all generated forms in an application.
|
|
231
|
+
`bootstrap_form` can be used out-of-the-box without any configuration. However, `bootstrap_form` does have an optional configuration file at `config/initializers/bootstrap_form.rb` for setting options that affect all generated forms in an application, or for enabling or disabling new functionality that might not be compatible with your application.
|
|
230
232
|
|
|
231
233
|
The current configuration options are:
|
|
232
234
|
|
|
233
235
|
| Option | Default value | Description |
|
|
234
236
|
|---------------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
235
|
-
| `default_form_attributes` | | `bootstrap_form` versions 3 and 4 added a role="form" attribute to all forms. The W3C validator will raise a **warning** on forms with a role="form" attribute. `bootstrap_form` version 5 drops this attribute by default. Set this option to `{ role: "form" }` to make forms non-compliant with W3C, but generate the `role="form"` attribute like `bootstrap_form` versions 3 and 4. |
|
|
237
|
+
| `default_form_attributes` | {} | `bootstrap_form` versions 3 and 4 added a role="form" attribute to all forms. The W3C validator will raise a **warning** on forms with a role="form" attribute. `bootstrap_form` version 5 drops this attribute by default. Set this option to `{ role: "form" }` to make forms non-compliant with W3C, but generate the `role="form"` attribute like `bootstrap_form` versions 3 and 4. |
|
|
238
|
+
| `group_around_collections` | false | Historically, `bootstrap_form` generated a wrapper around `collection_checkboxes` and `collection_radio_buttons` using the same `form_group` as individual controls used. This markup caused accessibility problems. Setting `group_around_collections = true` will generate collections of checkboxes and radio buttons wrapper in a `<fieldset>` with the text as a `<legend>` (https://www.w3.org/WAI/tutorials/forms/grouping/). This _will_ make visible changes to pages that use the collection methods.<br/><br/>The default for this option will be changed to `true` in a future version. |
|
|
236
239
|
|
|
237
240
|
Example:
|
|
238
241
|
|
|
@@ -781,8 +784,8 @@ This generates:
|
|
|
781
784
|
This generates:
|
|
782
785
|
|
|
783
786
|
```html
|
|
784
|
-
<div class="mb-3">
|
|
785
|
-
<
|
|
787
|
+
<div aria-labelledby="user_skill_level" class="mb-3" role="group">
|
|
788
|
+
<div class="form-label" id="user_skill_level">Skill level</div>
|
|
786
789
|
<div class="form-check">
|
|
787
790
|
<input class="form-check-input" id="user_skill_level_1" name="user[skill_level]" type="radio" value="1">
|
|
788
791
|
<label class="form-check-label" for="user_skill_level_1">Mind reading</label>
|
|
@@ -793,8 +796,8 @@ This generates:
|
|
|
793
796
|
</div>
|
|
794
797
|
</div>
|
|
795
798
|
<input id="user_skills" name="user[skills][]" type="hidden" value="">
|
|
796
|
-
<div class="mb-3">
|
|
797
|
-
<
|
|
799
|
+
<div aria-labelledby="user_skills" class="mb-3" role="group">
|
|
800
|
+
<div class="form-label" id="user_skills">Skills</div>
|
|
798
801
|
<div class="form-check">
|
|
799
802
|
<input class="form-check-input" id="user_skills_1" name="user[skills][]" type="checkbox" value="1">
|
|
800
803
|
<label class="form-check-label" for="user_skills_1">Mind reading</label>
|
|
@@ -829,8 +832,8 @@ To add `data-` attributes to a collection of radio buttons, map your models to a
|
|
|
829
832
|
This generates:
|
|
830
833
|
|
|
831
834
|
```html
|
|
832
|
-
<div class="mb-3">
|
|
833
|
-
<
|
|
835
|
+
<div aria-labelledby="user_misc" class="mb-3" role="group">
|
|
836
|
+
<div class="form-label" id="user_misc">Misc</div>
|
|
834
837
|
<div class="form-check">
|
|
835
838
|
<input class="form-check-input" id="user_misc_1" name="user[misc]" type="radio" value="1">
|
|
836
839
|
<label class="form-check-label" for="user_misc_1">Foo</label>
|
|
@@ -1417,7 +1420,7 @@ This generates:
|
|
|
1417
1420
|
</form>
|
|
1418
1421
|
```
|
|
1419
1422
|
|
|
1420
|
-
A form-level `layout: :inline` can't be overridden because of the way Bootstrap
|
|
1423
|
+
A form-level `layout: :inline` can't be overridden because of the way Bootstrap implements in-line layouts. One possible work-around is to leave the form-level layout as default, and specify the individual fields as `layout: :inline`, except for the fields(s) that should be other than in-line.
|
|
1421
1424
|
|
|
1422
1425
|
### Floating Labels
|
|
1423
1426
|
|
|
@@ -1490,38 +1493,38 @@ Generated HTML:
|
|
|
1490
1493
|
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
|
|
1491
1494
|
<div class="mb-3">
|
|
1492
1495
|
<label class="form-label required" for="user_email">Email</label>
|
|
1493
|
-
<input class="form-control is-invalid" id="user_email" name="user[email]" required="required" type="email" value="steve.example.com">
|
|
1494
|
-
<div class="invalid-feedback">is invalid</div>
|
|
1496
|
+
<input aria-describedby="user_email_feedback" class="form-control is-invalid" id="user_email" name="user[email]" required="required" type="email" value="steve.example.com">
|
|
1497
|
+
<div class="invalid-feedback" id="user_email_feedback">is invalid</div>
|
|
1495
1498
|
</div>
|
|
1496
|
-
<div class="mb-3">
|
|
1497
|
-
<
|
|
1499
|
+
<div aria-labelledby="user_misc" class="mb-3" role="group">
|
|
1500
|
+
<div class="form-label" id="user_misc">Misc</div>
|
|
1498
1501
|
<div class="form-check">
|
|
1499
|
-
<input checked class="form-check-input is-invalid" id="user_misc_1" name="user[misc]" type="radio" value="1">
|
|
1502
|
+
<input aria-describedby="user_misc_feedback" checked class="form-check-input is-invalid" id="user_misc_1" name="user[misc]" type="radio" value="1">
|
|
1500
1503
|
<label class="form-check-label" for="user_misc_1">Mind reading</label>
|
|
1501
1504
|
</div>
|
|
1502
1505
|
<div class="form-check">
|
|
1503
|
-
<input class="form-check-input is-invalid" id="user_misc_2" name="user[misc]" type="radio" value="2">
|
|
1506
|
+
<input aria-describedby="user_misc_feedback" class="form-check-input is-invalid" id="user_misc_2" name="user[misc]" type="radio" value="2">
|
|
1504
1507
|
<label class="form-check-label" for="user_misc_2">Farming</label>
|
|
1505
|
-
<div class="invalid-feedback">is invalid</div>
|
|
1508
|
+
<div class="invalid-feedback" id="user_misc_feedback">is invalid</div>
|
|
1506
1509
|
</div>
|
|
1507
1510
|
</div>
|
|
1508
1511
|
<input id="user_preferences" name="user[preferences][]" type="hidden" value="">
|
|
1509
|
-
<div class="mb-3">
|
|
1510
|
-
<
|
|
1512
|
+
<div aria-labelledby="user_preferences" class="mb-3" role="group">
|
|
1513
|
+
<div class="form-label" id="user_preferences">Preferences</div>
|
|
1511
1514
|
<div class="form-check">
|
|
1512
|
-
<input checked class="form-check-input is-invalid" id="user_preferences_1" name="user[preferences][]" type="checkbox" value="1">
|
|
1515
|
+
<input aria-describedby="user_preferences_feedback" checked class="form-check-input is-invalid" id="user_preferences_1" name="user[preferences][]" type="checkbox" value="1">
|
|
1513
1516
|
<label class="form-check-label" for="user_preferences_1">Good</label>
|
|
1514
1517
|
</div>
|
|
1515
1518
|
<div class="form-check">
|
|
1516
|
-
<input class="form-check-input is-invalid" id="user_preferences_2" name="user[preferences][]" type="checkbox" value="2">
|
|
1519
|
+
<input aria-describedby="user_preferences_feedback" class="form-check-input is-invalid" id="user_preferences_2" name="user[preferences][]" type="checkbox" value="2">
|
|
1517
1520
|
<label class="form-check-label" for="user_preferences_2">Bad</label>
|
|
1518
|
-
<div class="invalid-feedback">is invalid</div>
|
|
1521
|
+
<div class="invalid-feedback" id="user_preferences_feedback">is invalid</div>
|
|
1519
1522
|
</div>
|
|
1520
1523
|
</div>
|
|
1521
1524
|
<div class="mb-3">
|
|
1522
1525
|
<label class="form-label" for="user_address_attributes_street">Street</label>
|
|
1523
|
-
<input class="form-control is-invalid" id="user_address_attributes_street" name="user[address_attributes][street]" type="text" value="Bar">
|
|
1524
|
-
<div class="invalid-feedback">is invalid</div>
|
|
1526
|
+
<input aria-describedby="user_address_attributes_street_feedback" class="form-control is-invalid" id="user_address_attributes_street" name="user[address_attributes][street]" type="text" value="Bar">
|
|
1527
|
+
<div class="invalid-feedback" id="user_address_attributes_street_feedback">is invalid</div>
|
|
1525
1528
|
</div>
|
|
1526
1529
|
</form>
|
|
1527
1530
|
```
|
|
@@ -1551,8 +1554,8 @@ Generated HTML:
|
|
|
1551
1554
|
```html
|
|
1552
1555
|
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
|
|
1553
1556
|
<div class="mb-3">
|
|
1554
|
-
<label class="form-label required text-danger" for="user_email">Email is invalid</label>
|
|
1555
|
-
<input class="form-control is-invalid" id="user_email" name="user[email]" required="required" type="email" value="steve.example.com">
|
|
1557
|
+
<label class="form-label required text-danger" for="user_email" id="user_email_feedback">Email is invalid</label>
|
|
1558
|
+
<input aria-describedby="user_email_feedback" class="form-control is-invalid" id="user_email" name="user[email]" required="required" type="email" value="steve.example.com">
|
|
1556
1559
|
</div>
|
|
1557
1560
|
</form>
|
|
1558
1561
|
```
|
|
@@ -1649,7 +1652,7 @@ Which outputs:
|
|
|
1649
1652
|
```html
|
|
1650
1653
|
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
|
|
1651
1654
|
<input autocomplete="off" class="is-invalid" disabled type="hidden">
|
|
1652
|
-
<div class="invalid-feedback">Email is invalid</div>
|
|
1655
|
+
<div class="invalid-feedback" id="user_email_feedback">Email is invalid</div>
|
|
1653
1656
|
</form>
|
|
1654
1657
|
```
|
|
1655
1658
|
|
|
@@ -1670,7 +1673,7 @@ Which outputs:
|
|
|
1670
1673
|
```html
|
|
1671
1674
|
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
|
|
1672
1675
|
<input autocomplete="off" class="is-invalid" disabled type="hidden">
|
|
1673
|
-
<div class="invalid-feedback">is invalid</div>
|
|
1676
|
+
<div class="invalid-feedback" id="user_email_feedback">is invalid</div>
|
|
1674
1677
|
</form>
|
|
1675
1678
|
```
|
|
1676
1679
|
|
|
@@ -1689,7 +1692,7 @@ Which outputs:
|
|
|
1689
1692
|
```html
|
|
1690
1693
|
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
|
|
1691
1694
|
<input autocomplete="off" class="is-invalid" disabled type="hidden">
|
|
1692
|
-
<div class="custom-error">Email is invalid</div>
|
|
1695
|
+
<div class="custom-error" id="user_email_feedback">Email is invalid</div>
|
|
1693
1696
|
</form>
|
|
1694
1697
|
```
|
|
1695
1698
|
|
data/RELEASING.md
CHANGED
|
@@ -10,6 +10,8 @@ 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.
|
|
@@ -24,7 +26,6 @@ Follow these steps to release a new version of bootstrap_form to rubygems.org.
|
|
|
24
26
|
|
|
25
27
|
You will have failures in the system tests unless you're running on Linux. Chrome on each operating system renders slightly differently.
|
|
26
28
|
|
|
27
|
-
6. Update the GitHub diff links at the beginning of `CHANGELOG.md` (The pattern should be obvious when you look at them).
|
|
28
29
|
7. Update the installation instructions in `README.md` to use the new version.
|
|
29
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.
|
|
30
31
|
9. Tag, push to GitHub, and publish to rubygems.org:
|
data/Rakefile
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
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]
|
|
@@ -32,22 +29,20 @@ task default: %i[test rubocop:autocorrect]
|
|
|
32
29
|
namespace :test do
|
|
33
30
|
desc "Run tests for all supported Rails versions, with current Ruby version"
|
|
34
31
|
task :all do
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
gemfiles.each do |f|
|
|
38
|
-
ENV["BUNDLE_GEMFILE"] = f
|
|
39
|
-
system("bundle check") || system("bundle install")
|
|
40
|
-
system("bundle exec rake test")
|
|
32
|
+
gemfiles.each do |gemfile|
|
|
33
|
+
system("BUNDLE_GEMFILE=#{gemfile} rake test")
|
|
41
34
|
end
|
|
42
35
|
|
|
43
|
-
original_directory = Dir.pwd
|
|
44
36
|
Dir.chdir("demo")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
system("BUNDLE_GEMFILE= rake test:all")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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")
|
|
52
45
|
end
|
|
53
46
|
end
|
|
47
|
+
|
|
48
|
+
def gemfiles = Dir.glob("gemfiles/*.gemfile").reject { |f| File.basename(f) == "common.gemfile" }
|
data/gemfiles/7.2.gemfile
CHANGED
|
@@ -3,6 +3,7 @@ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
|
3
3
|
|
|
4
4
|
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
5
|
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "minitest", "~> 5.0"
|
|
6
7
|
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
7
8
|
gem "rails", "~> 7.2.0"
|
|
8
9
|
gem "sprockets-rails", require: "sprockets/railtie"
|
data/gemfiles/8.0.gemfile
CHANGED
|
@@ -3,6 +3,7 @@ eval File.read(gems), binding, gems # rubocop: disable Security/Eval
|
|
|
3
3
|
|
|
4
4
|
gem "bigdecimal" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
5
5
|
gem "drb" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
6
|
+
gem "minitest", "~> 5.0"
|
|
6
7
|
gem "mutex_m" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
7
8
|
gem "propshaft"
|
|
8
9
|
gem "rails", "~> 8.0.1"
|
data/gemfiles/common.gemfile
CHANGED
|
@@ -4,22 +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"
|
|
14
|
+
gem "selenium-webdriver"
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
group :development, :test do
|
|
21
18
|
gem "debug"
|
|
19
|
+
gem "htmlbeautifier"
|
|
22
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"
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
group :ci do
|
|
@@ -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)
|
|
@@ -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,14 +61,14 @@ 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
74
|
def get_error_messages(name)
|
|
@@ -84,6 +84,10 @@ module BootstrapForm
|
|
|
84
84
|
safe_join(object.errors[name], ", ")
|
|
85
85
|
end
|
|
86
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
|
|
87
91
|
end
|
|
88
92
|
end
|
|
89
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
|
|
@@ -23,7 +23,7 @@ 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
|
|
@@ -7,6 +7,16 @@ module BootstrapForm
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
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, &)
|
|
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
|
|
@@ -64,20 +69,21 @@ module BootstrapForm
|
|
|
64
69
|
end
|
|
65
70
|
|
|
66
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
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, &)
|
|
84
|
+
def input_with_error(name, id, &)
|
|
79
85
|
input = capture(&)
|
|
80
|
-
input << generate_error(name)
|
|
86
|
+
input << generate_error(name, id)
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
def input_group_content(content)
|
|
@@ -24,6 +24,9 @@ module BootstrapForm
|
|
|
24
24
|
|
|
25
25
|
def bootstrap_select_group(field_name)
|
|
26
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)
|
|
27
30
|
html_options = html_options.reverse_merge(control_class: "form-select")
|
|
28
31
|
form_group_builder(name, options, html_options) do
|
|
29
32
|
form_group_content_tag(name, field_name, "#{field_name}_without_bootstrap", options, html_options)
|
|
@@ -13,7 +13,7 @@ module BootstrapForm
|
|
|
13
13
|
content = tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do
|
|
14
14
|
html = check_box_without_bootstrap(name, check_box_options(name, options), checked_value, unchecked_value)
|
|
15
15
|
html << check_box_label(name, options, checked_value, &block) unless options[:skip_label]
|
|
16
|
-
html << generate_error(name) if options[:error_message]
|
|
16
|
+
html << generate_error(name, options[:id]) if options[:error_message]
|
|
17
17
|
html
|
|
18
18
|
end
|
|
19
19
|
wrapper(content, options)
|
|
@@ -41,6 +41,7 @@ module BootstrapForm
|
|
|
41
41
|
:inline, :label, :label_class, :label_col, :layout, :skip_label,
|
|
42
42
|
:switch, :wrapper, :wrapper_class)
|
|
43
43
|
check_box_options[:class] = check_box_classes(name, options)
|
|
44
|
+
check_box_options[:aria] = { describedby: aria_feedback_id(id: options[:id], name:) } if error?(name)
|
|
44
45
|
check_box_options.merge!(required_field_options(options, name))
|
|
45
46
|
end
|
|
46
47
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module BootstrapForm
|
|
2
4
|
module Inputs
|
|
3
5
|
module InputsCollection
|
|
@@ -5,23 +7,21 @@ module BootstrapForm
|
|
|
5
7
|
|
|
6
8
|
private
|
|
7
9
|
|
|
8
|
-
def inputs_collection(name, collection, value, text, options={})
|
|
9
|
-
options[:label] ||= { class: group_label_class(options
|
|
10
|
+
def inputs_collection(name, collection, value, text, options={}, &)
|
|
11
|
+
options[:label] ||= { class: group_label_class(field_layout(options)) }
|
|
10
12
|
options[:inline] ||= layout_inline?(options[:layout])
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
inputs = ActiveSupport::SafeBuffer.new
|
|
14
|
-
|
|
15
|
-
collection.each_with_index do |obj, i|
|
|
16
|
-
input_value = value.respond_to?(:call) ? value.call(obj) : obj.send(value)
|
|
17
|
-
input_options = form_group_collection_input_options(options, text, obj, i, input_value, collection)
|
|
18
|
-
inputs << yield(name, input_value, input_options)
|
|
19
|
-
end
|
|
14
|
+
return group_inputs_collection(name, collection, value, text, options, &) if BootstrapForm.config.group_around_collections
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
form_group_builder(name, options) do
|
|
17
|
+
render_collection(name, collection, value, text, options, &)
|
|
22
18
|
end
|
|
23
19
|
end
|
|
24
20
|
|
|
21
|
+
def field_layout(options)
|
|
22
|
+
(:inline if options[:inline] == true) || options[:layout]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
25
|
def group_label_class(field_layout)
|
|
26
26
|
if layout_horizontal?(field_layout)
|
|
27
27
|
group_label_class = "col-form-label #{label_col} pt-0"
|
|
@@ -52,6 +52,62 @@ module BootstrapForm
|
|
|
52
52
|
checked == input_value || Array(checked).try(:include?, input_value) ||
|
|
53
53
|
checked == obj || Array(checked).try(:include?, obj)
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
def group_inputs_collection(name, collection, value, text, options={}, &)
|
|
57
|
+
group_builder(name, options) do
|
|
58
|
+
render_collection(name, collection, value, text, options, &)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def render_collection(name, collection, value, text, options={}, &)
|
|
63
|
+
inputs = ActiveSupport::SafeBuffer.new
|
|
64
|
+
|
|
65
|
+
collection.each_with_index do |obj, i|
|
|
66
|
+
input_value = value.respond_to?(:call) ? value.call(obj) : obj.send(value)
|
|
67
|
+
input_options = form_group_collection_input_options(options, text, obj, i, input_value, collection)
|
|
68
|
+
inputs << yield(name, input_value, input_options)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
inputs
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def group_builder(method, options, html_options=nil, &)
|
|
75
|
+
form_group_builder_wrapper(method, options, html_options) do |form_group_options, no_wrapper|
|
|
76
|
+
if no_wrapper
|
|
77
|
+
yield
|
|
78
|
+
else
|
|
79
|
+
field_group(method, form_group_options, &)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def field_group(name, options, &)
|
|
85
|
+
options[:class] = form_group_classes(options)
|
|
86
|
+
|
|
87
|
+
tag.div(
|
|
88
|
+
**options.except(
|
|
89
|
+
:add_control_col_class, :append, :control_col, :floating, :help, :icon, :id,
|
|
90
|
+
:input_group_class, :label, :label_col, :layout, :prepend
|
|
91
|
+
),
|
|
92
|
+
aria: { labelledby: group_label_div_id(id: options[:id], name:) },
|
|
93
|
+
role: :group
|
|
94
|
+
) do
|
|
95
|
+
group_label_div = generate_group_label_div(name, options)
|
|
96
|
+
prepare_label_options(options[:id], name, options[:label], options[:label_col], options[:layout])
|
|
97
|
+
form_group_content(group_label_div, generate_help(name, options[:help]), options, &)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate_group_label_div(name, options)
|
|
102
|
+
group_label_div_class = options.dig(:label, :class) || "form-label"
|
|
103
|
+
|
|
104
|
+
tag.div(
|
|
105
|
+
**{ class: group_label_div_class }.compact,
|
|
106
|
+
id: group_label_div_id(id: options[:id], name:)
|
|
107
|
+
) { label_text(name, options.dig(:label, :text)) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def group_label_div_id(id:, name:) = id || field_id(name)
|
|
55
111
|
end
|
|
56
112
|
end
|
|
57
113
|
end
|
|
@@ -14,7 +14,7 @@ module BootstrapForm
|
|
|
14
14
|
tag.div(**wrapper_attributes) do
|
|
15
15
|
html = radio_button_without_bootstrap(name, value, radio_button_options(name, options))
|
|
16
16
|
html << radio_button_label(name, value, options) unless options[:skip_label]
|
|
17
|
-
html << generate_error(name) if options[:error_message]
|
|
17
|
+
html << generate_error(name, options[:id]) if options[:error_message]
|
|
18
18
|
html
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -28,6 +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[:aria] = { describedby: aria_feedback_id(id: options[:id], name:) } if error?(name)
|
|
31
32
|
radio_button_options.merge!(required_field_options(options, name))
|
|
32
33
|
end
|
|
33
34
|
|
|
@@ -10,7 +10,7 @@ module BootstrapForm
|
|
|
10
10
|
def time_zone_select_with_bootstrap(method, priority_zones=nil, options={}, html_options={})
|
|
11
11
|
html_options = html_options.reverse_merge(control_class: "form-select")
|
|
12
12
|
form_group_builder(method, options, html_options) do
|
|
13
|
-
input_with_error(method) do
|
|
13
|
+
input_with_error(method, options[:id]) do
|
|
14
14
|
time_zone_select_without_bootstrap(method, priority_zones, options, html_options)
|
|
15
15
|
end
|
|
16
16
|
end
|
data/lib/bootstrap_form.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "action_view"
|
|
2
4
|
require "action_pack"
|
|
3
5
|
require "bootstrap_form/action_view_extensions/form_helper"
|
|
6
|
+
require "bootstrap_form/configuration"
|
|
4
7
|
|
|
5
8
|
module BootstrapForm
|
|
6
9
|
extend ActiveSupport::Autoload
|
|
@@ -23,12 +26,8 @@ module BootstrapForm
|
|
|
23
26
|
BootstrapForm::Inputs.eager_load!
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def
|
|
27
|
-
@
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def configure
|
|
31
|
-
yield config
|
|
29
|
+
def deprecator
|
|
30
|
+
@deprecator ||= ActiveSupport::Deprecation.new("a future release", "BootstrapForm")
|
|
32
31
|
end
|
|
33
32
|
end
|
|
34
33
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bootstrap_form
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Potenza
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2026-01-26 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: actionpack
|
|
@@ -53,7 +53,6 @@ files:
|
|
|
53
53
|
- ".github/workflows/ruby.yml"
|
|
54
54
|
- ".gitignore"
|
|
55
55
|
- ".rubocop.yml"
|
|
56
|
-
- ".yarnrc"
|
|
57
56
|
- CHANGELOG.md
|
|
58
57
|
- CODE_OF_CONDUCT.md
|
|
59
58
|
- CONTRIBUTING.md
|