From ad98ae412a8c8bbcd6cb2dc6e61cff1d618be575 Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 19:43:07 +0530 Subject: [PATCH 1/7] Added support to wrap `field` in `input group` --- lib/rails_bootstrap_form.rb | 2 + .../bootstrap_form_builder.rb | 1 + .../bootstrap_form_options.rb | 20 ++++++++ .../field_wrapper_builder.rb | 4 +- .../input_group_builder.rb | 46 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 lib/rails_bootstrap_form/input_group_builder.rb diff --git a/lib/rails_bootstrap_form.rb b/lib/rails_bootstrap_form.rb index be24e8c..d15be95 100644 --- a/lib/rails_bootstrap_form.rb +++ b/lib/rails_bootstrap_form.rb @@ -14,6 +14,7 @@ module RailsBootstrapForm autoload :BootstrapFormOptions autoload :BootstrapFormBuilder autoload :FieldWrapperBuilder + autoload :InputGroupBuilder autoload :Components autoload :Inputs end @@ -21,6 +22,7 @@ module RailsBootstrapForm class << self def eager_load! super + RailsBootstrapForm::InputGroupBuilder.eager_load! RailsBootstrapForm::Components.eager_load! end diff --git a/lib/rails_bootstrap_form/bootstrap_form_builder.rb b/lib/rails_bootstrap_form/bootstrap_form_builder.rb index 4396d28..5c90f01 100644 --- a/lib/rails_bootstrap_form/bootstrap_form_builder.rb +++ b/lib/rails_bootstrap_form/bootstrap_form_builder.rb @@ -6,6 +6,7 @@ module RailsBootstrapForm class BootstrapFormBuilder < ActionView::Helpers::FormBuilder include RailsBootstrapForm::FieldWrapperBuilder + include RailsBootstrapForm::InputGroupBuilder include RailsBootstrapForm::Components include RailsBootstrapForm::Inputs diff --git a/lib/rails_bootstrap_form/bootstrap_form_options.rb b/lib/rails_bootstrap_form/bootstrap_form_options.rb index b16d17a..dcc9a98 100644 --- a/lib/rails_bootstrap_form/bootstrap_form_options.rb +++ b/lib/rails_bootstrap_form/bootstrap_form_options.rb @@ -55,6 +55,26 @@ class BootstrapFormOptions # `label_class` of the label. Default is nil. attr_accessor :additional_label_class + # Input group specific options. Input groups allow prepending and appending + # arbitrary html or text to the field. + # + # Example: + # + # form.text_field :dollars, bootstrap_form: {input_group: {prepend: "$", append: ".00"}} + # form.text_field :search, bootstrap_form: {input_group: {append: button_tag("Go", type: :submit, class: "btn btn-secondary")}} + # + # Raw or HTML content to be prepended to the field. + # Default is `nil`. + attr_accessor :prepend + + # Raw or HTML content to be appended to the field. + # Default is `nil`. + attr_accessor :append + + # Append additional CSS class added to the input group wrapper. + # Default is `nil`. + attr_accessor :additional_input_group_class + def initialize(options = {}) set_defaults set_bootstrap_form_options(options) diff --git a/lib/rails_bootstrap_form/field_wrapper_builder.rb b/lib/rails_bootstrap_form/field_wrapper_builder.rb index c0c5fb1..442e460 100644 --- a/lib/rails_bootstrap_form/field_wrapper_builder.rb +++ b/lib/rails_bootstrap_form/field_wrapper_builder.rb @@ -18,7 +18,9 @@ def field_wrapper(attribute, bootstrap_options, options, &block) tag.div(class: field_wrapper_classes) do concat(label) - concat(capture(&block)) + concat(input_group_wrapper(attribute, bootstrap_options) do + capture(&block) + end) concat(help_text) end end diff --git a/lib/rails_bootstrap_form/input_group_builder.rb b/lib/rails_bootstrap_form/input_group_builder.rb new file mode 100644 index 0000000..55c14c4 --- /dev/null +++ b/lib/rails_bootstrap_form/input_group_builder.rb @@ -0,0 +1,46 @@ +# -*- encoding: utf-8 -*- +# -*- frozen_string_literal: true -*- +# -*- warn_indent: true -*- + +module RailsBootstrapForm + module InputGroupBuilder + extend ActiveSupport::Concern + + def self.included(base_class) + def input_group_wrapper(attribute, bootstrap_options, &block) + input = capture(&block) || ActiveSupport::SafeBuffer.new + + prepend = attach_input(bootstrap_options, :prepend) + append = attach_input(bootstrap_options, :append) + + input = prepend + input + append + + input = tag.div(input, class: input_group_classes(attribute, bootstrap_options)) + + input + end + + def input_group_classes(attribute, bootstrap_options) + classes = ["input-group", bootstrap_options.additional_input_group_class] + classes.flatten.compact + end + + def attach_input(bootstrap_options, key) + tags = [*bootstrap_options.send(key)].map do |item| + input_group_content(item) + end + + ActiveSupport::SafeBuffer.new(tags.join) + end + + def input_group_content(content) + return content if /button|submit/.match?(content) + + tag.span(content.html_safe, class: "input-group-text") + end + + private :input_group_wrapper, :input_group_classes, :attach_input, + :input_group_content + end + end +end From ab0e4bb09ed0340de14325c92abf4045687935ee Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:07:42 +0530 Subject: [PATCH 2/7] Added support to display `error messages` below the `field` --- lib/rails_bootstrap_form.rb | 4 +- .../bootstrap_form_builder.rb | 2 +- lib/rails_bootstrap_form/components.rb | 2 + lib/rails_bootstrap_form/components/errors.rb | 67 +++++++++++++++++++ lib/rails_bootstrap_form/components/labels.rb | 1 + .../field_wrapper_builder.rb | 1 + .../input_group_builder.rb | 3 + 7 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 lib/rails_bootstrap_form/components/errors.rb diff --git a/lib/rails_bootstrap_form.rb b/lib/rails_bootstrap_form.rb index d15be95..904c4ab 100644 --- a/lib/rails_bootstrap_form.rb +++ b/lib/rails_bootstrap_form.rb @@ -13,17 +13,17 @@ module RailsBootstrapForm autoload :Configuration autoload :BootstrapFormOptions autoload :BootstrapFormBuilder + autoload :Components autoload :FieldWrapperBuilder autoload :InputGroupBuilder - autoload :Components autoload :Inputs end class << self def eager_load! super - RailsBootstrapForm::InputGroupBuilder.eager_load! RailsBootstrapForm::Components.eager_load! + RailsBootstrapForm::InputGroupBuilder.eager_load! end def config diff --git a/lib/rails_bootstrap_form/bootstrap_form_builder.rb b/lib/rails_bootstrap_form/bootstrap_form_builder.rb index 5c90f01..fde7888 100644 --- a/lib/rails_bootstrap_form/bootstrap_form_builder.rb +++ b/lib/rails_bootstrap_form/bootstrap_form_builder.rb @@ -6,8 +6,8 @@ module RailsBootstrapForm class BootstrapFormBuilder < ActionView::Helpers::FormBuilder include RailsBootstrapForm::FieldWrapperBuilder - include RailsBootstrapForm::InputGroupBuilder include RailsBootstrapForm::Components + include RailsBootstrapForm::InputGroupBuilder include RailsBootstrapForm::Inputs delegate :capture, :concat, :tag, to: :@template diff --git a/lib/rails_bootstrap_form/components.rb b/lib/rails_bootstrap_form/components.rb index 8a73735..94079c2 100644 --- a/lib/rails_bootstrap_form/components.rb +++ b/lib/rails_bootstrap_form/components.rb @@ -9,9 +9,11 @@ module Components autoload :HelpText autoload :Labels autoload :RequiredField + autoload :Errors include HelpText include Labels include RequiredField + include Errors end end diff --git a/lib/rails_bootstrap_form/components/errors.rb b/lib/rails_bootstrap_form/components/errors.rb new file mode 100644 index 0000000..2288a47 --- /dev/null +++ b/lib/rails_bootstrap_form/components/errors.rb @@ -0,0 +1,67 @@ +# -*- encoding: utf-8 -*- +# -*- frozen_string_literal: true -*- +# -*- warn_indent: true -*- + +module RailsBootstrapForm + module Components + module Errors + extend ActiveSupport::Concern + + def self.included(base_class) + def is_invalid?(attribute) + (attribute && object.respond_to?(:errors) && object.errors[attribute].any?) || + has_association_error?(attribute) + end + + def input_with_error(attribute, &block) + input = capture(&block) + input << generate_error(attribute) + input + end + + def generate_error(attribute) + if is_invalid?(attribute) + error_text = error_messages(attribute) + error_klass = "invalid-feedback" + + tag.div(error_text, class: error_klass) + end + end + + def has_association_error?(attribute) + object.class.reflections.any? do |association_name, association| + next unless is_belongs_to_association?(association) + next unless is_association_same?(attribute, association) + + object.errors[association_name].any? + end + end + + def error_messages(attribute) + messages = object.errors[attribute] + + object.class.reflections.each do |association_name, association| + next unless is_belongs_to_association?(association) + next unless is_association_same?(attribute, association) + + messages << object.errors[association_name] + end + + messages.flatten.to_sentence + end + + def is_belongs_to_association?(association) + association.is_a?(ActiveRecord::Reflection::BelongsToReflection) + end + + def is_association_same?(attribute, association) + (association.foreign_key == attribute.to_s) + end + + private :is_invalid?, :input_with_error, :generate_error, + :has_association_error?, :error_messages, + :is_belongs_to_association?, :is_association_same? + end + end + end +end diff --git a/lib/rails_bootstrap_form/components/labels.rb b/lib/rails_bootstrap_form/components/labels.rb index 151b728..8d3fe7f 100644 --- a/lib/rails_bootstrap_form/components/labels.rb +++ b/lib/rails_bootstrap_form/components/labels.rb @@ -21,6 +21,7 @@ def label_classes(attribute, bootstrap_options) classes = [bootstrap_options.label_class, bootstrap_options.additional_label_class] classes << bootstrap_options.hide_class if bootstrap_options.hide_label classes << "required" if is_attribute_required?(attribute) + classes << "is-invalid" if is_invalid?(attribute) classes.flatten.compact end diff --git a/lib/rails_bootstrap_form/field_wrapper_builder.rb b/lib/rails_bootstrap_form/field_wrapper_builder.rb index 442e460..62a7eb7 100644 --- a/lib/rails_bootstrap_form/field_wrapper_builder.rb +++ b/lib/rails_bootstrap_form/field_wrapper_builder.rb @@ -44,6 +44,7 @@ def field_css_options(attribute, bootstrap_options, options, html_options) bootstrap_options.field_class, bootstrap_options.additional_field_class ] + field_classes << "is-invalid" if is_invalid?(attribute) css_options[:class] = field_classes.flatten.compact css_options diff --git a/lib/rails_bootstrap_form/input_group_builder.rb b/lib/rails_bootstrap_form/input_group_builder.rb index 55c14c4..2ff07cc 100644 --- a/lib/rails_bootstrap_form/input_group_builder.rb +++ b/lib/rails_bootstrap_form/input_group_builder.rb @@ -14,6 +14,7 @@ def input_group_wrapper(attribute, bootstrap_options, &block) append = attach_input(bootstrap_options, :append) input = prepend + input + append + input += generate_error(attribute) input = tag.div(input, class: input_group_classes(attribute, bootstrap_options)) @@ -22,6 +23,8 @@ def input_group_wrapper(attribute, bootstrap_options, &block) def input_group_classes(attribute, bootstrap_options) classes = ["input-group", bootstrap_options.additional_input_group_class] + # Require `has-validation` class if field has errors. + classes << "has-validation" if is_invalid?(attribute) classes.flatten.compact end From 11be03364ae816d63d8f565477885c0d1feb42ed Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:08:02 +0530 Subject: [PATCH 3/7] Added styles and SVG required for `error` classes --- app/assets/images/exclamation-triangle.svg | 5 +++++ app/assets/stylesheets/rails_bootstrap_form.css | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 app/assets/images/exclamation-triangle.svg diff --git a/app/assets/images/exclamation-triangle.svg b/app/assets/images/exclamation-triangle.svg new file mode 100644 index 0000000..9526c89 --- /dev/null +++ b/app/assets/images/exclamation-triangle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/assets/stylesheets/rails_bootstrap_form.css b/app/assets/stylesheets/rails_bootstrap_form.css index 2c8a441..ef50248 100644 --- a/app/assets/stylesheets/rails_bootstrap_form.css +++ b/app/assets/stylesheets/rails_bootstrap_form.css @@ -8,3 +8,9 @@ label.required::after { top: -2px; font-weight: bolder; } +.form-control.is-invalid, .form-select.is-invalid { + background-image: url("exclamation-triangle.svg") !important; + background-repeat: no-repeat !important; + background-position: right 0.5rem center, center right 2rem !important; + background-size: 24px 24px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important; +} From cd7761425166dec0ca0c1f5ac3c51ec746d91cd9 Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:14:41 +0530 Subject: [PATCH 4/7] Added `required` field options for the `field` --- lib/rails_bootstrap_form/components/required_field.rb | 2 +- lib/rails_bootstrap_form/field_wrapper_builder.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rails_bootstrap_form/components/required_field.rb b/lib/rails_bootstrap_form/components/required_field.rb index fa100e0..048ba59 100644 --- a/lib/rails_bootstrap_form/components/required_field.rb +++ b/lib/rails_bootstrap_form/components/required_field.rb @@ -18,7 +18,7 @@ def is_field_required?(attribute, options) end end - def required_field_options(options, attribute) + def required_field_options(attribute, options) required = is_field_required?(attribute, options) {}.tap do |option| diff --git a/lib/rails_bootstrap_form/field_wrapper_builder.rb b/lib/rails_bootstrap_form/field_wrapper_builder.rb index 62a7eb7..5825430 100644 --- a/lib/rails_bootstrap_form/field_wrapper_builder.rb +++ b/lib/rails_bootstrap_form/field_wrapper_builder.rb @@ -45,6 +45,9 @@ def field_css_options(attribute, bootstrap_options, options, html_options) bootstrap_options.additional_field_class ] field_classes << "is-invalid" if is_invalid?(attribute) + + css_options.merge!(required_field_options(attribute, options)) + css_options[:class] = field_classes.flatten.compact css_options From fb5d55a1510106726aa41d7278bf60658341bf17 Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:25:02 +0530 Subject: [PATCH 5/7] Changes to support `floating labels` --- .../bootstrap_form_options.rb | 13 +++++- .../field_wrapper_builder.rb | 40 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/lib/rails_bootstrap_form/bootstrap_form_options.rb b/lib/rails_bootstrap_form/bootstrap_form_options.rb index dcc9a98..8d3c13d 100644 --- a/lib/rails_bootstrap_form/bootstrap_form_options.rb +++ b/lib/rails_bootstrap_form/bootstrap_form_options.rb @@ -35,12 +35,15 @@ class BootstrapFormOptions attr_accessor :help_text # An option to override automatically generated label text. + # Default is `nil`. attr_accessor :label_text # An option to custmize whether the label is to be displayed or not. + # Default is `false`. attr_accessor :skip_label # An option to customize whether the label is only visible to screen readers. + # Default is `false`. attr_accessor :hide_label # The CSS class that will be used when the label is only accessible by screen @@ -52,7 +55,7 @@ class BootstrapFormOptions attr_accessor :label_class # An additional CSS class that will be added along with the existing - # `label_class` of the label. Default is nil. + # `label_class` of the label. Default is `nil`. attr_accessor :additional_label_class # Input group specific options. Input groups allow prepending and appending @@ -75,6 +78,10 @@ class BootstrapFormOptions # Default is `nil`. attr_accessor :additional_input_group_class + # Option to control whether the field should have a floating label. + # Default is false. + attr_accessor :floating + def initialize(options = {}) set_defaults set_bootstrap_form_options(options) @@ -125,12 +132,14 @@ def set_defaults @help_text = nil - @label_text = "" + @label_text = nil @skip_label = false @hide_label = false @hide_class = "visually-hidden" @label_class = "form-label" @additional_label_class = nil + + @floating = false end private :set_defaults diff --git a/lib/rails_bootstrap_form/field_wrapper_builder.rb b/lib/rails_bootstrap_form/field_wrapper_builder.rb index 5825430..48ff325 100644 --- a/lib/rails_bootstrap_form/field_wrapper_builder.rb +++ b/lib/rails_bootstrap_form/field_wrapper_builder.rb @@ -16,12 +16,24 @@ def field_wrapper(attribute, bootstrap_options, options, &block) label = label(attribute, bootstrap_options) help_text = help_text(attribute, bootstrap_options) - tag.div(class: field_wrapper_classes) do - concat(label) - concat(input_group_wrapper(attribute, bootstrap_options) do - capture(&block) - end) - concat(help_text) + if bootstrap_options.floating + tag.div(class: field_wrapper_classes) do + concat(input_group_wrapper(attribute, bootstrap_options) do + tag.div(class: floating_label_classes(attribute)) do + concat(capture(&block)) + concat(label) + end + end) + concat(help_text) + end + else + tag.div(class: field_wrapper_classes) do + concat(label) + concat(input_group_wrapper(attribute, bootstrap_options) do + capture(&block) + end) + concat(help_text) + end end end @@ -46,14 +58,26 @@ def field_css_options(attribute, bootstrap_options, options, html_options) ] field_classes << "is-invalid" if is_invalid?(attribute) + css_options[:class] = field_classes.flatten.compact + css_options.merge!(required_field_options(attribute, options)) - css_options[:class] = field_classes.flatten.compact + if bootstrap_options.floating + css_options[:placeholder] ||= label_text(attribute, bootstrap_options) + end css_options end + def floating_label_classes(attribute) + classes = Array("form-floating") + # Floating label fields with input group requires `is-invalid` class in + # order to display error messages. + classes << "is-invalid" if is_invalid?(attribute) + classes + end + private :field_wrapper, :field_wrapper_classes, :form_wrapper_default_class, - :field_css_options + :field_css_options, :floating_label_classes end end From f5183bd060fc79ccc35eecb23fe96e497a13e09a Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:26:36 +0530 Subject: [PATCH 6/7] Fixed `eager_loading` issue --- lib/rails_bootstrap_form.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rails_bootstrap_form.rb b/lib/rails_bootstrap_form.rb index 904c4ab..4f6881f 100644 --- a/lib/rails_bootstrap_form.rb +++ b/lib/rails_bootstrap_form.rb @@ -23,7 +23,6 @@ class << self def eager_load! super RailsBootstrapForm::Components.eager_load! - RailsBootstrapForm::InputGroupBuilder.eager_load! end def config From 37a3ed3464cfdf7e9796eeffb61e29e4aa49da20 Mon Sep 17 00:00:00 2001 From: Harshal LADHE Date: Mon, 15 May 2023 20:27:49 +0530 Subject: [PATCH 7/7] Bump version to `0.3.0` --- Gemfile.lock | 2 +- lib/rails_bootstrap_form/version.rb | 2 +- spec/rails_bootstrap_form_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8156dd4..0f00e9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rails_bootstrap_form (0.2.3) + rails_bootstrap_form (0.3.0) GEM remote: http://rubygems.org/ diff --git a/lib/rails_bootstrap_form/version.rb b/lib/rails_bootstrap_form/version.rb index 22cb0fe..baf4df7 100644 --- a/lib/rails_bootstrap_form/version.rb +++ b/lib/rails_bootstrap_form/version.rb @@ -3,6 +3,6 @@ # -*- warn_indent: true -*- module RailsBootstrapForm - VERSION = "0.2.3".freeze + VERSION = "0.3.0".freeze REQUIRED_RAILS_VERSION = "~> 7.0".freeze end diff --git a/spec/rails_bootstrap_form_spec.rb b/spec/rails_bootstrap_form_spec.rb index 35c4210..2be3b56 100644 --- a/spec/rails_bootstrap_form_spec.rb +++ b/spec/rails_bootstrap_form_spec.rb @@ -6,7 +6,7 @@ RSpec.describe RailsBootstrapForm do it "has a valid version number" do - expect(RailsBootstrapForm::VERSION).to eq("0.2.3") + expect(RailsBootstrapForm::VERSION).to eq("0.3.0") end it "has a valid rails version number" do