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