bootstrap_form 5.2.3 → 5.3.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.
@@ -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 = ["form-label", options[:class], label_layout_classes(custom_label_col, group_layout)]
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
- ["me-sm-2"]
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 = :default if field_layout == false
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
- presence_validator?(target_unconditional_validators(target, attribute)) ||
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)&.find do |name, a|
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
- presence_validator?(target_unconditional_validators(target, name))
37
+ presence_validators?(target, object, name)
39
38
  end
40
39
  end
41
40
 
42
- def target_unconditional_validators(target, attribute)
43
- target.validators_on(attribute)
44
- .reject { |validator| validator.options[:if].present? || validator.options[:unless].present? }
45
- .map(&:class)
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?(target_validators)
49
- target_validators.include?(ActiveModel::Validations::PresenceValidator) ||
53
+ def presence_validator?(validator_class)
54
+ validator_class == ActiveModel::Validations::PresenceValidator ||
50
55
  (defined?(ActiveRecord::Validations::PresenceValidator) &&
51
- target_validators.include?(ActiveRecord::Validations::PresenceValidator))
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
- messages << object.errors[association_name]
79
+ object.errors[association_name].each do |error|
80
+ object.errors.add(name, error)
81
+ end
75
82
  end
76
- messages.join(", ")
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
- # :default, :horizontal or :inline
103
- :default
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
- form_group_content(
17
- generate_label(options[:id], name, options[:label], options[:label_col], options[:layout]),
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, :label, :label_class, :error_message, :help,
28
- :inline, :hide_label, :skip_label, :wrapper, :wrapper_class, :switch)
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 == :horizontal
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
@@ -28,6 +28,7 @@ module BootstrapForm
28
28
  autoload :RichTextArea
29
29
  autoload :SearchField
30
30
  autoload :Select
31
+ autoload :Submit
31
32
  autoload :TelephoneField
32
33
  autoload :TextArea
33
34
  autoload :TextField
@@ -1,4 +1,4 @@
1
1
  module BootstrapForm
2
- VERSION = "5.2.3".freeze
3
- REQUIRED_RAILS_VERSION = ">= 6.0".freeze
2
+ VERSION = "5.3.0".freeze
3
+ REQUIRED_RAILS_VERSION = ">= 6.1".freeze
4
4
  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.2.3
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-06-18 00:00:00.000000000 Z
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.0'
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.0'
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.0'
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.0'
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
@@ -1,4 +0,0 @@
1
- gems = "#{__dir__}/common.gemfile"
2
- eval File.read(gems), binding, gems # rubocop: disable Security/Eval
3
-
4
- gem "rails", "~> 6.0.0"