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.
@@ -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"