bootstrap_form 4.4.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +48 -0
- data/.gitignore +28 -3
- data/.rubocop.yml +19 -15
- data/CHANGELOG.md +82 -1
- data/CONTRIBUTING.md +73 -12
- data/Dangerfile +5 -7
- data/Dockerfile +21 -0
- data/Gemfile +8 -11
- data/README.md +829 -82
- data/RELEASING.md +5 -10
- data/UPGRADE-4.0.md +1 -1
- data/UPGRADE-5.0.md +25 -0
- data/bootstrap_form.gemspec +8 -5
- data/demo/.ruby-version +1 -0
- data/demo/Gemfile +80 -0
- data/demo/Gemfile.lock +261 -0
- data/demo/Procfile.dev +2 -0
- data/demo/app/assets/builds/.keep +0 -0
- data/demo/app/assets/builds/application.js.LICENSE.txt +9 -0
- data/demo/app/assets/config/manifest.js +2 -2
- data/demo/app/assets/stylesheets/actiontext.css +31 -0
- data/demo/app/assets/stylesheets/application.scss +1 -1
- data/demo/app/controllers/bootstrap_controller.rb +17 -2
- data/demo/app/controllers/users_controller.rb +9 -0
- data/demo/app/helpers/bootstrap_helper.rb +5 -5
- data/demo/app/javascript/application.js +3 -0
- data/demo/app/models/skill.rb +15 -0
- data/demo/app/models/user.rb +14 -0
- data/demo/app/views/active_storage/blobs/_blob.html.erb +1 -1
- data/demo/app/views/bootstrap/form.html.erb +13 -0
- data/demo/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/demo/app/views/layouts/application.html.erb +28 -20
- data/demo/bin/dev +9 -0
- data/demo/config/environments/development.rb +3 -3
- data/demo/config/puma.rb +2 -2
- data/demo/config/routes.rb +1 -0
- data/demo/db/schema.rb +31 -16
- data/demo/doc/screenshots/bootstrap/index/00_horizontal_form.png +0 -0
- data/demo/doc/screenshots/bootstrap/index/01_with_validation_error.png +0 -0
- data/demo/doc/screenshots/bootstrap/index/02_inline_form.png +0 -0
- data/demo/doc/screenshots/bootstrap/index/03_simple_action_text_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/index/04_floating_labels.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/00_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/01_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/02_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/03_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/04_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/05_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/06_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/07_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/08_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/09_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/10_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/11_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/12_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/13_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/14_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/15_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/16_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/17_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/18_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/19_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/20_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/21_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/22_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/23_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/24_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/25_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/26_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/27_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/28_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/29_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/30_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/31_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/32_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/33_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/34_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/35_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/36_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/37_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/38_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/39_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/40_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/41_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/42_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/43_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/44_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/45_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/46_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/47_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/48_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/49_example.png +0 -0
- data/demo/doc/screenshots/bootstrap/readme/50_example.png +0 -0
- data/demo/package.json +10 -6
- data/demo/test/application_system_test_case.rb +8 -0
- data/demo/test/controllers/bootstrap_controller_test.rb +8 -0
- data/demo/test/controllers/users_controller_test.rb +13 -0
- data/demo/test/fixtures/users.yml +2 -0
- data/demo/test/system/bootstrap_test.rb +84 -0
- data/demo/test/test_helper.rb +10 -0
- data/demo/webpack.config.js +20 -0
- data/demo/yarn.lock +4063 -3144
- data/docker-compose.yml +49 -0
- data/gemfiles/5.2.gemfile +2 -15
- data/gemfiles/6.0.gemfile +2 -17
- data/gemfiles/6.1.gemfile +4 -0
- data/gemfiles/7.0.gemfile +6 -0
- data/gemfiles/edge.gemfile +2 -17
- data/lib/bootstrap_form/action_view_extensions/form_helper.rb +1 -1
- data/lib/bootstrap_form/components/hints.rb +13 -4
- data/lib/bootstrap_form/components/labels.rb +2 -2
- data/lib/bootstrap_form/components/validation.rb +1 -1
- data/lib/bootstrap_form/configuration.rb +22 -0
- data/lib/bootstrap_form/form_builder.rb +10 -12
- data/lib/bootstrap_form/form_group.rb +26 -11
- data/lib/bootstrap_form/form_group_builder.rb +6 -8
- data/lib/bootstrap_form/helpers/bootstrap.rb +17 -12
- data/lib/bootstrap_form/inputs/base.rb +5 -5
- data/lib/bootstrap_form/inputs/check_box.rb +11 -23
- data/lib/bootstrap_form/inputs/collection_check_boxes.rb +5 -1
- data/lib/bootstrap_form/inputs/collection_select.rb +2 -1
- data/lib/bootstrap_form/inputs/file_field.rb +3 -15
- data/lib/bootstrap_form/inputs/grouped_collection_select.rb +2 -1
- data/lib/bootstrap_form/inputs/radio_button.rb +17 -30
- data/lib/bootstrap_form/inputs/select.rb +1 -0
- data/lib/bootstrap_form/inputs/time_zone_select.rb +1 -0
- data/lib/bootstrap_form/version.rb +1 -1
- data/lib/bootstrap_form.rb +17 -7
- metadata +94 -16
- data/.travis.yml +0 -42
- data/demo/config/initializers/assets.rb +0 -14
- data/gemfiles/5.0.gemfile +0 -18
- data/gemfiles/5.1.gemfile +0 -17
data/docker-compose.yml
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
data/gemfiles/edge.gemfile
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
24
|
-
|
|
23
|
+
partial_scope = if object_class.respond_to?(:model_name)
|
|
24
|
+
object_class.model_name.name
|
|
25
25
|
else
|
|
26
|
-
|
|
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.
|
|
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
|
-
|
|
60
|
+
add_default_form_attributes_and_form_inline options
|
|
62
61
|
super
|
|
63
62
|
end
|
|
64
|
-
# rubocop:enable Metrics/AbcSize
|
|
65
63
|
|
|
66
|
-
def
|
|
64
|
+
def add_default_form_attributes_and_form_inline(options)
|
|
67
65
|
options[:html] ||= {}
|
|
68
|
-
options[:html]
|
|
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] =
|
|
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
|
-
"
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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}
|
|
28
|
-
|
|
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(
|
|
37
|
+
concat(label).concat(tag.div(capture(&block) + help_text, class: form_group_control_class(options)))
|
|
38
38
|
else
|
|
39
|
-
|
|
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 = [
|
|
54
|
-
classes << "row" if
|
|
55
|
-
classes << "
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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] ||
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
42
|
+
tag.ul class: "rails-bootstrap-forms-error-summary" do
|
|
39
43
|
object.errors.full_messages.each do |error|
|
|
40
|
-
concat
|
|
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
|
-
|
|
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 =
|
|
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
|
|
103
|
+
return content if /btn/.match?(content)
|
|
99
104
|
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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,
|
|
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, :
|
|
13
|
+
:inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
|
|
14
14
|
check_box_options[:class] = check_box_classes(name, options)
|
|
15
15
|
|
|
16
|
-
|
|
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 =
|
|
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 =
|
|
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[:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
11
|
+
options = options.reverse_merge(control_class: "form-control")
|
|
12
12
|
form_group_builder(name, options) do
|
|
13
|
-
|
|
14
|
-
|
|
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
|