bootstrap_form 5.2.3 → 5.3.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 +2 -5
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +3 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +339 -155
- data/lib/bootstrap_form/action_view_extensions/form_helper.rb +10 -0
- data/lib/bootstrap_form/components/labels.rb +6 -2
- data/lib/bootstrap_form/components/layout.rb +1 -5
- data/lib/bootstrap_form/components/validation.rb +24 -15
- data/lib/bootstrap_form/form_builder.rb +5 -10
- data/lib/bootstrap_form/form_group.rb +2 -4
- data/lib/bootstrap_form/helpers/bootstrap.rb +0 -30
- data/lib/bootstrap_form/helpers/field.rb +1 -2
- data/lib/bootstrap_form/inputs/base.rb +10 -0
- data/lib/bootstrap_form/inputs/check_box.rb +16 -4
- data/lib/bootstrap_form/inputs/collection_check_boxes.rb +9 -1
- data/lib/bootstrap_form/inputs/inputs_collection.rb +13 -0
- data/lib/bootstrap_form/inputs/submit.rb +42 -0
- data/lib/bootstrap_form/inputs.rb +1 -0
- data/lib/bootstrap_form/version.rb +2 -2
- metadata +7 -7
- data/gemfiles/6.0.gemfile +0 -4
@@ -39,6 +39,16 @@ module BootstrapForm
|
|
39
39
|
bootstrap_form_for("", options, &block)
|
40
40
|
end
|
41
41
|
|
42
|
+
def bootstrap_fields_for(record_name, record_object=nil, options={}, &block)
|
43
|
+
options[:builder] = BootstrapForm::FormBuilder
|
44
|
+
fields_for(record_name, record_object, options, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def bootstrap_fields(scope=nil, model: nil, **options, &block)
|
48
|
+
options[:builder] = BootstrapForm::FormBuilder
|
49
|
+
fields(scope, model: model, **options, &block)
|
50
|
+
end
|
51
|
+
|
42
52
|
private
|
43
53
|
|
44
54
|
def with_bootstrap_form_field_error_proc
|
@@ -23,7 +23,9 @@ module BootstrapForm
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def label_classes(name, options, custom_label_col, group_layout)
|
26
|
-
classes = [
|
26
|
+
classes = [options[:class] || label_layout_classes(custom_label_col, group_layout)]
|
27
|
+
add_class = options.delete(:add_class)
|
28
|
+
classes.prepend(add_class) if add_class
|
27
29
|
classes << "required" if required_field_options(options, name)[:required]
|
28
30
|
options.delete(:required)
|
29
31
|
classes << "text-danger" if label_errors && error?(name)
|
@@ -34,7 +36,9 @@ module BootstrapForm
|
|
34
36
|
if layout_horizontal?(group_layout)
|
35
37
|
["col-form-label", (custom_label_col || label_col)]
|
36
38
|
elsif layout_inline?(group_layout)
|
37
|
-
[
|
39
|
+
%w[form-label me-sm-2]
|
40
|
+
else
|
41
|
+
"form-label"
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
@@ -7,10 +7,6 @@ module BootstrapForm
|
|
7
7
|
|
8
8
|
private
|
9
9
|
|
10
|
-
def layout_default?(field_layout=nil)
|
11
|
-
layout_in_effect(field_layout) == :default
|
12
|
-
end
|
13
|
-
|
14
10
|
def layout_horizontal?(field_layout=nil)
|
15
11
|
layout_in_effect(field_layout) == :horizontal
|
16
12
|
end
|
@@ -27,7 +23,7 @@ module BootstrapForm
|
|
27
23
|
# and those don't have a :horizontal layout
|
28
24
|
def layout_in_effect(field_layout)
|
29
25
|
field_layout = :inline if field_layout == true
|
30
|
-
field_layout = :
|
26
|
+
field_layout = :vertical if field_layout == false
|
31
27
|
field_layout || layout
|
32
28
|
end
|
33
29
|
|
@@ -26,29 +26,34 @@ module BootstrapForm
|
|
26
26
|
target = obj.instance_of?(Class) ? obj : obj.class
|
27
27
|
return false unless target.respond_to? :validators_on
|
28
28
|
|
29
|
-
|
30
|
-
required_association?(target, attribute)
|
29
|
+
presence_validators?(target, obj, attribute) || required_association?(target, obj, attribute)
|
31
30
|
end
|
32
31
|
|
33
|
-
def required_association?(target, attribute)
|
34
|
-
target.try(:reflections)&.
|
32
|
+
def required_association?(target, object, attribute)
|
33
|
+
target.try(:reflections)&.any? do |name, a|
|
35
34
|
next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
36
35
|
next unless a.foreign_key == attribute.to_s
|
37
36
|
|
38
|
-
|
37
|
+
presence_validators?(target, object, name)
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
42
|
-
def
|
43
|
-
target.validators_on(attribute)
|
44
|
-
|
45
|
-
|
41
|
+
def presence_validators?(target, object, attribute)
|
42
|
+
target.validators_on(attribute).select { |v| presence_validator?(v.class) }.any? do |validator|
|
43
|
+
if_option = validator.options[:if]
|
44
|
+
unless_opt = validator.options[:unless]
|
45
|
+
(!if_option || call_with_self(object, if_option)) && (!unless_opt || !call_with_self(object, unless_opt))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def call_with_self(object, proc)
|
50
|
+
object.instance_exec(*[(object if proc.arity >= 1)].compact, &proc)
|
46
51
|
end
|
47
52
|
|
48
|
-
def presence_validator?(
|
49
|
-
|
53
|
+
def presence_validator?(validator_class)
|
54
|
+
validator_class == ActiveModel::Validations::PresenceValidator ||
|
50
55
|
(defined?(ActiveRecord::Validations::PresenceValidator) &&
|
51
|
-
|
56
|
+
validator_class == ActiveRecord::Validations::PresenceValidator)
|
52
57
|
end
|
53
58
|
|
54
59
|
def inline_error?(name)
|
@@ -65,16 +70,20 @@ module BootstrapForm
|
|
65
70
|
content_tag(help_tag, help_text, class: help_klass)
|
66
71
|
end
|
67
72
|
|
73
|
+
# rubocop:disable Metrics/AbcSize
|
68
74
|
def get_error_messages(name)
|
69
|
-
messages = object.errors[name]
|
70
75
|
object.class.try(:reflections)&.each do |association_name, a|
|
71
76
|
next unless a.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
72
77
|
next unless a.foreign_key == name.to_s
|
73
78
|
|
74
|
-
|
79
|
+
object.errors[association_name].each do |error|
|
80
|
+
object.errors.add(name, error)
|
81
|
+
end
|
75
82
|
end
|
76
|
-
|
83
|
+
|
84
|
+
object.errors[name].join(", ")
|
77
85
|
end
|
86
|
+
# rubocop:enable Metrics/AbcSize
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# require 'bootstrap_form/aliasing'
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/ClassLength
|
4
3
|
module BootstrapForm
|
5
4
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
6
5
|
attr_reader :layout, :label_col, :control_col, :has_error, :inline_errors,
|
@@ -36,6 +35,7 @@ module BootstrapForm
|
|
36
35
|
include BootstrapForm::Inputs::RichTextArea
|
37
36
|
include BootstrapForm::Inputs::SearchField
|
38
37
|
include BootstrapForm::Inputs::Select
|
38
|
+
include BootstrapForm::Inputs::Submit
|
39
39
|
include BootstrapForm::Inputs::TelephoneField
|
40
40
|
include BootstrapForm::Inputs::TextArea
|
41
41
|
include BootstrapForm::Inputs::TextField
|
@@ -48,16 +48,12 @@ module BootstrapForm
|
|
48
48
|
delegate :content_tag, :capture, :concat, :tag, to: :@template
|
49
49
|
|
50
50
|
def initialize(object_name, object, template, options)
|
51
|
+
warn_deprecated_layout_value(options)
|
51
52
|
@layout = options[:layout] || default_layout
|
52
53
|
@label_col = options[:label_col] || default_label_col
|
53
54
|
@control_col = options[:control_col] || default_control_col
|
54
55
|
@label_errors = options[:label_errors] || false
|
55
|
-
|
56
|
-
@inline_errors = if options[:inline_errors].nil?
|
57
|
-
@label_errors != true
|
58
|
-
else
|
59
|
-
options[:inline_errors] != false
|
60
|
-
end
|
56
|
+
@inline_errors = options[:inline_errors].nil? ? @label_errors != true : options[:inline_errors] != false
|
61
57
|
@acts_like_form_tag = options[:acts_like_form_tag]
|
62
58
|
add_default_form_attributes_and_form_inline options
|
63
59
|
super
|
@@ -99,8 +95,8 @@ module BootstrapForm
|
|
99
95
|
end
|
100
96
|
|
101
97
|
def default_layout
|
102
|
-
# :
|
103
|
-
:
|
98
|
+
# :vertical, :horizontal or :inline
|
99
|
+
:vertical
|
104
100
|
end
|
105
101
|
|
106
102
|
def default_label_col
|
@@ -132,4 +128,3 @@ module BootstrapForm
|
|
132
128
|
end
|
133
129
|
end
|
134
130
|
end
|
135
|
-
# rubocop:enable Metrics/ClassLength
|
@@ -13,10 +13,8 @@ module BootstrapForm
|
|
13
13
|
tag.div(**options.except(:append, :id, :label, :help, :icon,
|
14
14
|
:input_group_class, :label_col, :control_col,
|
15
15
|
:add_control_col_class, :layout, :prepend, :floating)) do
|
16
|
-
|
17
|
-
|
18
|
-
generate_help(name, options[:help]), options, &block
|
19
|
-
)
|
16
|
+
label = generate_label(options[:id], name, options[:label], options[:label_col], options[:layout])
|
17
|
+
form_group_content(label, generate_help(name, options[:help]), options, &block)
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
@@ -1,27 +1,6 @@
|
|
1
1
|
module BootstrapForm
|
2
2
|
module Helpers
|
3
3
|
module Bootstrap
|
4
|
-
def button(value=nil, options={}, &block)
|
5
|
-
setup_css_class "btn btn-secondary", options
|
6
|
-
super
|
7
|
-
end
|
8
|
-
|
9
|
-
def submit(name=nil, options={})
|
10
|
-
setup_css_class "btn btn-secondary", options
|
11
|
-
layout == :inline ? form_group { super } : super
|
12
|
-
end
|
13
|
-
|
14
|
-
def primary(name=nil, options={}, &block)
|
15
|
-
setup_css_class "btn btn-primary", options
|
16
|
-
|
17
|
-
if options[:render_as_button] || block
|
18
|
-
options.except! :render_as_button
|
19
|
-
button(name, options, &block)
|
20
|
-
else
|
21
|
-
submit(name, options)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
4
|
def alert_message(title, options={})
|
26
5
|
css = options[:class] || "alert alert-danger"
|
27
6
|
return unless object.respond_to?(:errors) && object.errors.full_messages.any?
|
@@ -116,15 +95,6 @@ module BootstrapForm
|
|
116
95
|
end
|
117
96
|
ActiveSupport::SafeBuffer.new(tags.join)
|
118
97
|
end
|
119
|
-
|
120
|
-
def setup_css_class(the_class, options={})
|
121
|
-
return if options.key? :class
|
122
|
-
|
123
|
-
if (extra_class = options.delete(:extra_class))
|
124
|
-
the_class = "#{the_class} #{extra_class}"
|
125
|
-
end
|
126
|
-
options[:class] = the_class
|
127
|
-
end
|
128
98
|
end
|
129
99
|
end
|
130
100
|
end
|
@@ -4,8 +4,7 @@ module BootstrapForm
|
|
4
4
|
def required_field_options(options, method)
|
5
5
|
required = required_field?(options, method)
|
6
6
|
{}.tap do |option|
|
7
|
-
option[:required] = required
|
8
|
-
option[:aria] = { required: true } if required
|
7
|
+
option[:required] = true if required
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
@@ -8,6 +8,7 @@ module BootstrapForm
|
|
8
8
|
class_methods do
|
9
9
|
def bootstrap_field(field_name, control_class: nil)
|
10
10
|
define_method "#{field_name}_with_bootstrap" do |name, options={ control_class: control_class }.compact|
|
11
|
+
warn_deprecated_layout_value(options)
|
11
12
|
form_group_builder(name, options) do
|
12
13
|
prepend_and_append_input(name, options) do
|
13
14
|
options[:placeholder] ||= name if options[:floating]
|
@@ -35,6 +36,15 @@ module BootstrapForm
|
|
35
36
|
alias_method field_name, "#{field_name}_with_bootstrap".to_sym
|
36
37
|
end
|
37
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def warn_deprecated_layout_value(options)
|
43
|
+
return unless options[:layout] == :default
|
44
|
+
|
45
|
+
warn "Layout `:default` is deprecated, use `:vertical` instead."
|
46
|
+
options[:layout] = :vertical
|
47
|
+
end
|
38
48
|
end
|
39
49
|
end
|
40
50
|
end
|
@@ -10,12 +10,13 @@ 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
|
|
13
|
-
tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do
|
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
16
|
html << generate_error(name) if options[:error_message]
|
17
17
|
html
|
18
18
|
end
|
19
|
+
wrapper(content, options)
|
19
20
|
end
|
20
21
|
|
21
22
|
bootstrap_alias :check_box
|
@@ -23,9 +24,20 @@ module BootstrapForm
|
|
23
24
|
|
24
25
|
private
|
25
26
|
|
27
|
+
def wrapper(content, options)
|
28
|
+
if layout == :inline && !options[:multiple]
|
29
|
+
tag.div(class: "col") { content }
|
30
|
+
elsif layout == :horizontal && !options[:multiple]
|
31
|
+
form_group(layout: layout_in_effect(options[:layout]), label_col: options[:label_col]) { content }
|
32
|
+
else
|
33
|
+
content
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
26
37
|
def check_box_options(name, options)
|
27
|
-
check_box_options = options.except(:class, :
|
28
|
-
:inline, :
|
38
|
+
check_box_options = options.except(:class, :control_col, :error_message, :help, :hide_label,
|
39
|
+
:inline, :label, :label_class, :label_col, :layout, :skip_label,
|
40
|
+
:switch, :wrapper, :wrapper_class)
|
29
41
|
check_box_options[:class] = check_box_classes(name, options)
|
30
42
|
check_box_options.merge!(required_field_options(options, name))
|
31
43
|
end
|
@@ -71,7 +83,7 @@ module BootstrapForm
|
|
71
83
|
def check_box_wrapper_class(options)
|
72
84
|
classes = ["form-check"]
|
73
85
|
classes << "form-check-inline" if layout_inline?(options[:inline])
|
74
|
-
classes << "mb-3" unless options[:multiple] || layout
|
86
|
+
classes << "mb-3" unless options[:multiple] || %i[horizontal inline].include?(layout)
|
75
87
|
classes << "form-switch" if options[:switch]
|
76
88
|
classes << options.dig(:wrapper, :class).presence
|
77
89
|
classes << options[:wrapper_class].presence
|
@@ -15,12 +15,20 @@ module BootstrapForm
|
|
15
15
|
end
|
16
16
|
|
17
17
|
if args.extract_options!.symbolize_keys!.delete(:include_hidden) { true }
|
18
|
-
html.prepend hidden_field(args.first, value: "", multiple: true)
|
18
|
+
html.prepend hidden_field(args.first, value: "", name: field_name(args[0], multiple: true))
|
19
19
|
end
|
20
20
|
html
|
21
21
|
end
|
22
22
|
|
23
23
|
bootstrap_alias :collection_check_boxes
|
24
|
+
|
25
|
+
if Rails::VERSION::MAJOR < 7
|
26
|
+
def field_name(method, *methods, multiple: false, index: @options[:index])
|
27
|
+
object_name = @options.fetch(:as) { @object_name }
|
28
|
+
|
29
|
+
@template.field_name(object_name, method, *methods, index: index, multiple: multiple)
|
30
|
+
end
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
end
|
@@ -6,6 +6,7 @@ module BootstrapForm
|
|
6
6
|
private
|
7
7
|
|
8
8
|
def inputs_collection(name, collection, value, text, options={})
|
9
|
+
options[:label] ||= { class: group_label_class(options[:layout]) }
|
9
10
|
options[:inline] ||= layout_inline?(options[:layout])
|
10
11
|
|
11
12
|
form_group_builder(name, options) do
|
@@ -21,6 +22,15 @@ module BootstrapForm
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
def group_label_class(field_layout)
|
26
|
+
if layout_horizontal?(field_layout)
|
27
|
+
group_label_class = "col-form-label #{label_col} pt-0"
|
28
|
+
elsif layout_inline?(field_layout)
|
29
|
+
group_label_class = "form-check form-check-inline ps-0"
|
30
|
+
end
|
31
|
+
group_label_class
|
32
|
+
end
|
33
|
+
|
24
34
|
# FIXME: Find a way to reduce the parameter list size
|
25
35
|
# rubocop:disable Metrics/ParameterLists
|
26
36
|
def form_group_collection_input_options(options, text, obj, index, input_value, collection)
|
@@ -29,6 +39,9 @@ module BootstrapForm
|
|
29
39
|
input_options[:checked] = form_group_collection_input_checked?(checked, obj, input_value)
|
30
40
|
end
|
31
41
|
|
42
|
+
# add things like 'data-' attributes to the HTML
|
43
|
+
obj.each { |inner_obj| input_options.merge!(inner_obj) if inner_obj.is_a?(Hash) } if obj.respond_to?(:each)
|
44
|
+
|
32
45
|
input_options[:error_message] = index == collection.size - 1
|
33
46
|
input_options.except!(:class)
|
34
47
|
input_options
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BootstrapForm
|
2
|
+
module Inputs
|
3
|
+
module Submit
|
4
|
+
def button(value=nil, options={}, &block)
|
5
|
+
value = setup_css_class "btn btn-secondary", value, options
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def submit(value=nil, options={})
|
10
|
+
value = setup_css_class "btn btn-secondary", value, options
|
11
|
+
layout == :inline ? form_group { super } : super
|
12
|
+
end
|
13
|
+
|
14
|
+
def primary(value=nil, options={}, &block)
|
15
|
+
value = setup_css_class "btn btn-primary", value, options
|
16
|
+
|
17
|
+
if options[:render_as_button] || block
|
18
|
+
options.except! :render_as_button
|
19
|
+
button(value, options, &block)
|
20
|
+
else
|
21
|
+
submit(value, options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def setup_css_class(the_class, value, options)
|
28
|
+
if value.is_a?(Hash)
|
29
|
+
options.merge!(value)
|
30
|
+
value = nil
|
31
|
+
end
|
32
|
+
unless options.key? :class
|
33
|
+
if (extra_class = options.delete(:extra_class))
|
34
|
+
the_class = "#{the_class} #{extra_class}"
|
35
|
+
end
|
36
|
+
options[:class] = the_class
|
37
|
+
end
|
38
|
+
value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
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.3.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: 2023-
|
12
|
+
date: 2023-09-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionpack
|
@@ -17,28 +17,28 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '6.
|
20
|
+
version: '6.1'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '6.
|
27
|
+
version: '6.1'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: activemodel
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '6.
|
34
|
+
version: '6.1'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '6.
|
41
|
+
version: '6.1'
|
42
42
|
description: bootstrap_form is a rails form builder that makes it super easy to create
|
43
43
|
beautiful-looking forms using Bootstrap 5
|
44
44
|
email:
|
@@ -69,7 +69,6 @@ files:
|
|
69
69
|
- app/assets/stylesheets/rails_bootstrap_forms.css
|
70
70
|
- bootstrap_form.gemspec
|
71
71
|
- docker-compose.yml
|
72
|
-
- gemfiles/6.0.gemfile
|
73
72
|
- gemfiles/6.1.gemfile
|
74
73
|
- gemfiles/7.0.gemfile
|
75
74
|
- gemfiles/common.gemfile
|
@@ -114,6 +113,7 @@ files:
|
|
114
113
|
- lib/bootstrap_form/inputs/rich_text_area.rb
|
115
114
|
- lib/bootstrap_form/inputs/search_field.rb
|
116
115
|
- lib/bootstrap_form/inputs/select.rb
|
116
|
+
- lib/bootstrap_form/inputs/submit.rb
|
117
117
|
- lib/bootstrap_form/inputs/telephone_field.rb
|
118
118
|
- lib/bootstrap_form/inputs/text_area.rb
|
119
119
|
- lib/bootstrap_form/inputs/text_field.rb
|
data/gemfiles/6.0.gemfile
DELETED