From cb4255d52ef35cee78e95673208aafbada035573 Mon Sep 17 00:00:00 2001 From: Larry Reid Date: Thu, 18 Jun 2020 18:41:09 -0700 Subject: [PATCH 01/12] Start README for Bootstrap 5 --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 92b976afc..090c755c6 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,9 @@ -If you are using Bootstrap v3, refer to the legacy [legacy-2.7](https://github.com/bootstrap-ruby/bootstrap_form/tree/legacy-2.7) branch. - -This is a new take on the `bootstrap_form` README. Please leave comments at: #520. You can go back to the traditional [README](/OLD-README.md). - ---- - # bootstrap_form [![Build Status](https://travis-ci.org/bootstrap-ruby/bootstrap_form.svg?branch=master)](https://travis-ci.org/bootstrap-ruby/bootstrap_form) [![Gem Version](https://badge.fury.io/rb/bootstrap_form.svg)](https://rubygems.org/gems/bootstrap_form) -`bootstrap_form` is a Rails form builder that makes it super easy to integrate Bootstrap v4-style forms into your Rails application. It provides form helpers that augment the Rails form helpers. `bootstrap_forms`'s form helpers generate the form field and its label and all the Bootstrap mark-up required for proper Bootstrap display. `bootstrap_form` also provides: +`bootstrap_form` is a Rails form builder that makes it super easy to integrate Bootstrap v5-style forms into your Rails application. It provides form helpers that augment the Rails form helpers. `bootstrap_forms`'s form helpers generate the form field and its label and all the Bootstrap mark-up required for proper Bootstrap display. `bootstrap_form` also provides: * [Validation error messages](#validation-and-errors) below the field they correspond to, by default. You can also put the error messages after the label, or turn off `bootstrap_form`'s validation error handling and do it yourself. * Automatic [mark-up for the `required` attribute](#required-fields) on required fields. @@ -33,14 +27,14 @@ Some other nice things that `bootstrap_form` does for you are: * Ruby 2.5+ * Rails 5.2+ -* Bootstrap 4.0+ +* Bootstrap 5.0+ ## Installation Add it to your Gemfile: ```ruby -gem "bootstrap_form", "~> 4.0" +gem "bootstrap_form", git: "https://github.com/bootstrap-ruby/bootstrap_form.git", branch: "bootstrap-5" ``` Then: @@ -163,6 +157,7 @@ The current configuration options are: Example: + ```ruby # config/initializers/bootstrap_form.rb BootstrapForm.configure do |c| From c2039e004eef9d402265a3205573a7974f383ffc Mon Sep 17 00:00:00 2001 From: Taylor Thurlow Date: Sat, 20 Jun 2020 17:04:52 -0700 Subject: [PATCH 02/12] Preliminary support for Bootstrap 5 (#567) * Switch demo bootstrap JS include URL * Remove "custom-control" support from radios and check boxes In 5.0 they are consolidated into the .form-check class, and no longer need to be treated as separate cases. Relevant tests will be fixed or removed in the next commit. * Remove "custom-control" tests for radios and check boxes * Rename ".custom-file" to ".form-file" * Change all ".form-group" uses to ".mb-3" Bootstrap 5 is now recommending using bottom margin utilities to control group spacing, and has removed ".form-group" entirely. * Replace ".form-row" with gutter utils (".g-3") Some methods in FormGroup were refactored to meet a RuboCop lint complexity requirement. I don't personally think the refactor is necessary but it won't pass CI without it. * Replace ".form-inline" where necessary Bootstrap 5 removes ".form-inline" in favor of the usage of ".col-auto" and the new gutter utilities. * Remove wrapper divs for elements prepended/appended to input groups Bootstrap 5 removes ".input-group-prepend" and ".input-group-append", they are no longer necessary. You can now add buttons and ".input-group-text" as direct children of the input groups. * Add ".form-label" to all generated label tags * Fix broken tests after label class change * Fix switch style check boxes My heavy-handedness with the previous commits removed the ability to use switch style checkboxes when I removed the custom check box classes. * Update switch checkbox syntax in README.md * Add anchors for stricter #classes_include_gutters? * Handle string or array of classes for check boxes and radio buttons The Rails view helpers that accept a `class` option accept both single strings and arrays of strings, so this change makes the CSS class array builder agnostic to the input type. I'm now also noticing that the result already had `#flatten` called on it, so this probably wasn't an issue, but this is nicer! * Use only form-check-label class for check box and radio button labels Thanks to @thimo for catching this one. * Change select box class from "form-control" to "form-select" This required some extra effort because all the select forms inherited their control class from the base input class. --- README.md | 38 +-- demo/app/views/layouts/application.html.erb | 2 +- lib/bootstrap_form/components/labels.rb | 2 +- lib/bootstrap_form/form_builder.rb | 2 +- lib/bootstrap_form/form_group.rb | 16 +- lib/bootstrap_form/helpers/bootstrap.rb | 2 +- lib/bootstrap_form/inputs/base.rb | 1 + lib/bootstrap_form/inputs/check_box.rb | 26 +- .../inputs/collection_select.rb | 1 + lib/bootstrap_form/inputs/file_field.rb | 6 +- .../inputs/grouped_collection_select.rb | 1 + lib/bootstrap_form/inputs/radio_button.rb | 30 +- lib/bootstrap_form/inputs/select.rb | 1 + lib/bootstrap_form/inputs/time_zone_select.rb | 1 + test/bootstrap_checkbox_test.rb | 183 ++--------- test/bootstrap_fields_test.rb | 163 +++++----- test/bootstrap_form_group_test.rb | 184 ++++++----- test/bootstrap_form_test.rb | 150 ++++----- test/bootstrap_other_components_test.rb | 36 +-- test/bootstrap_radio_button_test.rb | 170 ++--------- test/bootstrap_rich_text_area_test.rb | 4 +- test/bootstrap_selects_test.rb | 286 +++++++++--------- test/special_form_class_models_test.rb | 12 +- 23 files changed, 519 insertions(+), 798 deletions(-) diff --git a/README.md b/README.md index 090c755c6..0bf87da09 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ This generates the following HTML: ```html
-
- +
+
-
- +
+
@@ -121,12 +121,12 @@ This generates: ```html -
- +
+
-
- +
+ A good password should be at least six characters long
@@ -314,7 +314,7 @@ To add a class to the input group wrapper, use the `:input_group_class` option. ### Additional Form Group Attributes -Bootstrap mark-up dictates that most input field types have the label and input wrapped in a `div.form-group`. +Bootstrap mark-up dictates that most input field types have the label and input wrapped in a `div.mb-3`. If you want to add an additional CSS class or any other attribute to the form group div, you can use the `wrapper: { class: 'additional-class', data: { foo: 'bar' } }` option. @@ -325,8 +325,8 @@ If you want to add an additional CSS class or any other attribute to the form gr Which produces the following output: ```erb -
- +
+
``` @@ -399,10 +399,10 @@ Check boxes and radio buttons are wrapped in a `div.form-check`. You can add cla ### Switches -To render checkboxes as switches with Bootstrap 4.2+, use `custom: :switch`: +To render checkboxes as switches with Bootstrap 4.2+, use `switch: true`: ```erb -<%= f.check_box :remember_me, custom: :switch %> +<%= f.check_box :remember_me, switch: true %> ``` ### Collections @@ -435,8 +435,8 @@ You can create a static control like this: Here's the output for a horizontal layout: ```html -
- +
+
@@ -548,8 +548,8 @@ If you're using Rails 6, `bootstrap_form` supports the `rich_text_area` helper. will be rendered as: ```html -
- +
+ @@ -705,8 +705,8 @@ By default, fields that have validation errors will be outlined in red and the error will be displayed below the field. Here's an example: ```html -
- +
+ can't be blank
diff --git a/demo/app/views/layouts/application.html.erb b/demo/app/views/layouts/application.html.erb index 9712b54ca..e397d6d2f 100644 --- a/demo/app/views/layouts/application.html.erb +++ b/demo/app/views/layouts/application.html.erb @@ -6,7 +6,7 @@ - + diff --git a/lib/bootstrap_form/components/labels.rb b/lib/bootstrap_form/components/labels.rb index dd213600f..a209ce143 100644 --- a/lib/bootstrap_form/components/labels.rb +++ b/lib/bootstrap_form/components/labels.rb @@ -23,7 +23,7 @@ def generate_label(id, name, options, custom_label_col, group_layout) end def label_classes(name, options, custom_label_col, group_layout) - classes = [options[:class], label_layout_classes(custom_label_col, group_layout)] + classes = ["form-label", options[:class], label_layout_classes(custom_label_col, group_layout)] case options.delete(:required) when true diff --git a/lib/bootstrap_form/form_builder.rb b/lib/bootstrap_form/form_builder.rb index 429802c78..3badafabf 100644 --- a/lib/bootstrap_form/form_builder.rb +++ b/lib/bootstrap_form/form_builder.rb @@ -69,7 +69,7 @@ def add_default_form_attributes_and_form_inline(options) return unless options[:layout] == :inline - options[:html][:class] = [options[:html][:class], "form-inline"].compact.join(" ") + options[:html][:class] = [options[:html][:class], "col-auto", "g-3"].compact.join(" ") end def fields_for_with_bootstrap(record_name, record_object=nil, fields_options={}, &block) diff --git a/lib/bootstrap_form/form_group.rb b/lib/bootstrap_form/form_group.rb index b611323b1..d326c3dda 100644 --- a/lib/bootstrap_form/form_group.rb +++ b/lib/bootstrap_form/form_group.rb @@ -24,7 +24,7 @@ def form_group(*args, &block) def form_group_content_tag(name, field_name, without_field_name, options, html_options) html_class = control_specific_class(field_name) - html_class = "#{html_class} form-inline" if @layout == :horizontal && options[:skip_inline].blank? + html_class = "#{html_class} col-auto g-3" if @layout == :horizontal && options[:skip_inline].blank? tag.div(class: html_class) do input_with_error(name) do send(without_field_name, name, options, html_options) @@ -50,15 +50,23 @@ def form_group_control_class(options) end def form_group_classes(options) - classes = ["form-group", options[:class].try(:split)].flatten.compact - classes << "row" if group_layout_horizontal?(options[:layout]) && classes.exclude?("form-row") - classes << "form-inline" if field_inline_override?(options[:layout]) + classes = ["mb-3", options[:class].try(:split)].flatten.compact + classes << "row" if horizontal_group_with_gutters?(options[:layout], classes) + classes << "col-auto g-3" if field_inline_override?(options[:layout]) classes << feedback_class if options[:icon] classes end + def horizontal_group_with_gutters?(layout, classes) + group_layout_horizontal?(layout) && !classes_include_gutters?(classes) + end + def group_layout_horizontal?(layout) get_group_layout(layout) == :horizontal end + + def classes_include_gutters?(classes) + classes.any? { |c| c =~ /^g-\d+$/ } + end end end diff --git a/lib/bootstrap_form/helpers/bootstrap.rb b/lib/bootstrap_form/helpers/bootstrap.rb index 3e5554632..9cb1a8f95 100644 --- a/lib/bootstrap_form/helpers/bootstrap.rb +++ b/lib/bootstrap_form/helpers/bootstrap.rb @@ -108,7 +108,7 @@ def static_class def attach_input(options, key) tags = [*options[key]].map do |item| - tag.div(input_group_content(item), class: "input-group-#{key}") + input_group_content(item) end ActiveSupport::SafeBuffer.new(tags.join) end diff --git a/lib/bootstrap_form/inputs/base.rb b/lib/bootstrap_form/inputs/base.rb index d84c4e9b3..e4ee5490e 100644 --- a/lib/bootstrap_form/inputs/base.rb +++ b/lib/bootstrap_form/inputs/base.rb @@ -22,6 +22,7 @@ def bootstrap_select_group(field_name) with_field_name = "#{field_name}_with_bootstrap" without_field_name = "#{field_name}_without_bootstrap" define_method(with_field_name) do |name, options={}, html_options={}| + html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(name, options, html_options) do form_group_content_tag(name, field_name, without_field_name, options, html_options) end diff --git a/lib/bootstrap_form/inputs/check_box.rb b/lib/bootstrap_form/inputs/check_box.rb index abfd44bfd..2d0612539 100644 --- a/lib/bootstrap_form/inputs/check_box.rb +++ b/lib/bootstrap_form/inputs/check_box.rb @@ -10,7 +10,7 @@ module CheckBox def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_value="0", &block) options = options.symbolize_keys! check_box_options = options.except(:class, :label, :label_class, :error_message, :help, - :inline, :custom, :hide_label, :skip_label, :wrapper_class) + :inline, :hide_label, :skip_label, :wrapper_class, :switch) check_box_options[:class] = check_box_classes(name, options) tag.div(class: check_box_wrapper_class(options)) do @@ -50,40 +50,26 @@ def check_box_value(name, value) end def check_box_classes(name, options) - classes = [options[:class]] - classes << (options[:custom] ? "custom-control-input" : "form-check-input") + classes = Array(options[:class]) << "form-check-input" classes << "is-invalid" if error?(name) classes << "position-static" if options[:skip_label] || options[:hide_label] classes.flatten.compact end def check_box_label_class(options) - classes = [] - classes << (options[:custom] ? "custom-control-label" : "form-check-label") + classes = ["form-check-label"] classes << options[:label_class] classes << hide_class if options[:hide_label] classes.flatten.compact end def check_box_wrapper_class(options) - classes = [] - if options[:custom] - classes << custom_check_box_wrapper_class(options) - else - classes << "form-check" - classes << "form-check-inline" if layout_inline?(options[:inline]) - end + classes = ["form-check"] + classes << "form-check-inline" if layout_inline?(options[:inline]) + classes << "form-switch" if options[:switch] classes << options[:wrapper_class] if options[:wrapper_class].present? classes.flatten.compact end - - def custom_check_box_wrapper_class(options) - classes = [] - classes << "custom-control" - classes << (options[:custom] == :switch ? "custom-switch" : "custom-checkbox") - classes << "custom-control-inline" if layout_inline?(options[:inline]) - classes - end end end end diff --git a/lib/bootstrap_form/inputs/collection_select.rb b/lib/bootstrap_form/inputs/collection_select.rb index b5ac1472c..74f7ffd81 100644 --- a/lib/bootstrap_form/inputs/collection_select.rb +++ b/lib/bootstrap_form/inputs/collection_select.rb @@ -10,6 +10,7 @@ module CollectionSelect # Disabling Metrics/ParameterLists because the upstream Rails method has the same parameters # rubocop:disable Metrics/ParameterLists def collection_select_with_bootstrap(method, collection, value_method, text_method, options={}, html_options={}) + html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do input_with_error(method) do collection_select_without_bootstrap(method, collection, value_method, text_method, options, html_options) diff --git a/lib/bootstrap_form/inputs/file_field.rb b/lib/bootstrap_form/inputs/file_field.rb index 297a74140..94c92935b 100644 --- a/lib/bootstrap_form/inputs/file_field.rb +++ b/lib/bootstrap_form/inputs/file_field.rb @@ -8,9 +8,9 @@ module FileField included do def file_field_with_bootstrap(name, options={}) - options = options.reverse_merge(control_class: "custom-file-input") + options = options.reverse_merge(control_class: "form-file-input") form_group_builder(name, options) do - tag.div(class: "custom-file") do + tag.div(class: "form-file") do input_with_error(name) do file_field_input(name, options) end @@ -25,7 +25,7 @@ def file_field_with_bootstrap(name, options={}) def file_field_input(name, options) placeholder = options.delete(:placeholder) || "Choose file" - placeholder_opts = { class: "custom-file-label" } + placeholder_opts = { class: "form-label form-file-label" } placeholder_opts[:for] = options[:id] if options[:id].present? file_field_without_bootstrap(name, options) + label(name, placeholder, placeholder_opts) diff --git a/lib/bootstrap_form/inputs/grouped_collection_select.rb b/lib/bootstrap_form/inputs/grouped_collection_select.rb index 7ed37d347..a62a9b864 100644 --- a/lib/bootstrap_form/inputs/grouped_collection_select.rb +++ b/lib/bootstrap_form/inputs/grouped_collection_select.rb @@ -12,6 +12,7 @@ module GroupedCollectionSelect def grouped_collection_select_with_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options={}, html_options={}) + html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do input_with_error(method) do grouped_collection_select_without_bootstrap(method, collection, group_method, diff --git a/lib/bootstrap_form/inputs/radio_button.rb b/lib/bootstrap_form/inputs/radio_button.rb index a4c4d595b..c0bfa647c 100644 --- a/lib/bootstrap_form/inputs/radio_button.rb +++ b/lib/bootstrap_form/inputs/radio_button.rb @@ -10,7 +10,7 @@ module RadioButton def radio_button_with_bootstrap(name, value, *args) options = args.extract_options!.symbolize_keys! radio_button_options = options.except(:class, :label, :label_class, :error_message, :help, - :inline, :custom, :hide_label, :skip_label, :wrapper_class) + :inline, :hide_label, :skip_label, :wrapper_class) radio_button_options[:class] = radio_button_classes(name, options) @@ -34,43 +34,25 @@ def radio_button_label(name, value, options) end def radio_button_classes(name, options) - classes = [options[:class]] - classes << (options[:custom] ? "custom-control-input" : "form-check-input") + classes = Array(options[:class]) << "form-check-input" classes << "is-invalid" if error?(name) classes << "position-static" if options[:skip_label] || options[:hide_label] classes.flatten.compact end def radio_button_label_class(options) - classes = [] - classes << (options[:custom] ? "custom-control-label" : "form-check-label") + classes = ["form-check-label"] classes << options[:label_class] classes << hide_class if options[:hide_label] classes.flatten.compact end def radio_button_wrapper_class(options) - classes = [] - classes << if options[:custom] - custom_radio_button_wrapper_class(options) - else - standard_radio_button_wrapper_class(options) - end - classes << options[:wrapper_class] if options[:wrapper_class].present? - classes.flatten.compact - end - - def standard_radio_button_wrapper_class(options) - classes = %w[form-check] + classes = ["form-check"] classes << "form-check-inline" if layout_inline?(options[:inline]) classes << "disabled" if options[:disabled] - classes - end - - def custom_radio_button_wrapper_class(options) - classes = %w[custom-control custom-radio] - classes << "custom-control-inline" if layout_inline?(options[:inline]) - classes + classes << options[:wrapper_class] if options[:wrapper_class].present? + classes.flatten.compact end end end diff --git a/lib/bootstrap_form/inputs/select.rb b/lib/bootstrap_form/inputs/select.rb index abceaee56..5a20ae5ff 100644 --- a/lib/bootstrap_form/inputs/select.rb +++ b/lib/bootstrap_form/inputs/select.rb @@ -8,6 +8,7 @@ module Select included do def select_with_bootstrap(method, choices=nil, options={}, html_options={}, &block) + html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do prepend_and_append_input(method, options) do select_without_bootstrap(method, choices, options, html_options, &block) diff --git a/lib/bootstrap_form/inputs/time_zone_select.rb b/lib/bootstrap_form/inputs/time_zone_select.rb index 45bd9a5e6..555c2902d 100644 --- a/lib/bootstrap_form/inputs/time_zone_select.rb +++ b/lib/bootstrap_form/inputs/time_zone_select.rb @@ -8,6 +8,7 @@ module TimeZoneSelect included do def time_zone_select_with_bootstrap(method, priority_zones=nil, options={}, html_options={}) + html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do input_with_error(method) do time_zone_select_without_bootstrap(method, priority_zones, options, html_options) diff --git a/test/bootstrap_checkbox_test.rb b/test/bootstrap_checkbox_test.rb index 8e22ce2c2..4f5e84115 100644 --- a/test/bootstrap_checkbox_test.rb +++ b/test/bootstrap_checkbox_test.rb @@ -123,7 +123,7 @@ class BootstrapCheckboxTest < ActionView::TestCase test "inline checkboxes from form layout" do expected = <<-HTML.strip_heredoc - + #{'' unless ::Rails::VERSION::STRING >= '6'}
@@ -171,8 +171,8 @@ class BootstrapCheckboxTest < ActionView::TestCase collection = [Address.new(id: 1, street: "Foobar")] expected = <<-HTML.strip_heredoc -
- +
+
@@ -189,8 +189,8 @@ class BootstrapCheckboxTest < ActionView::TestCase collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] expected = <<-HTML.strip_heredoc -
- +
+