diff --git a/lib/bootstrap_form/form_builder.rb b/lib/bootstrap_form/form_builder.rb index 6089bfc86..f59452255 100644 --- a/lib/bootstrap_form/form_builder.rb +++ b/lib/bootstrap_form/form_builder.rb @@ -38,7 +38,7 @@ def initialize(object_name, object, template, options) define_method(with_method_name) do |name, options = {}| form_group_builder(name, options) do - prepend_and_append_input(options) do + prepend_and_append_input(name, options) do send(without_method_name, name, options) end end @@ -53,7 +53,9 @@ def initialize(object_name, object, template, options) define_method(with_method_name) do |name, options = {}, html_options = {}| form_group_builder(name, options, html_options) do - content_tag(:div, send(without_method_name, name, options, html_options), class: control_specific_class(method_name)) + control_error_help(name, options) do + content_tag(:div, send(without_method_name, name, options, html_options), class: control_specific_class(method_name)) + end end end @@ -63,7 +65,9 @@ def initialize(object_name, object, template, options) def file_field_with_bootstrap(name, options = {}) options = options.reverse_merge(control_class: 'form-control-file') form_group_builder(name, options) do - file_field_without_bootstrap(name, options) + control_error_help(name, options) do + file_field_without_bootstrap(name, options) + end end end @@ -71,7 +75,7 @@ def file_field_with_bootstrap(name, options = {}) def select_with_bootstrap(method, choices = nil, options = {}, html_options = {}, &block) form_group_builder(method, options, html_options) do - prepend_and_append_input(options) do + prepend_and_append_input(method, options) do select_without_bootstrap(method, choices, options, html_options, &block) end end @@ -81,7 +85,9 @@ def select_with_bootstrap(method, choices = nil, options = {}, html_options = {} def collection_select_with_bootstrap(method, collection, value_method, text_method, options = {}, html_options = {}) form_group_builder(method, options, html_options) do - collection_select_without_bootstrap(method, collection, value_method, text_method, options, html_options) + control_error_help(method, options) do + collection_select_without_bootstrap(method, collection, value_method, text_method, options, html_options) + end end end @@ -89,7 +95,9 @@ def collection_select_with_bootstrap(method, collection, value_method, text_meth def grouped_collection_select_with_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) form_group_builder(method, options, html_options) do - grouped_collection_select_without_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + control_error_help(method, options) do + grouped_collection_select_without_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + end end end @@ -97,7 +105,9 @@ def grouped_collection_select_with_bootstrap(method, collection, group_method, g def time_zone_select_with_bootstrap(method, priority_zones = nil, options = {}, html_options = {}) form_group_builder(method, options, html_options) do - time_zone_select_without_bootstrap(method, priority_zones, options, html_options) + control_error_help(method, options) do + time_zone_select_without_bootstrap(method, priority_zones, options, html_options) + end end end @@ -131,6 +141,7 @@ def check_box_with_bootstrap(name, options = {}, checked_value = "1", unchecked_ label_classes = [options[:label_class]] label_classes << hide_class if options[:hide_label] + error_text = generate_help(name, options.delete(:help)).to_s if options[:custom] div_class = ["custom-control", "custom-checkbox"] @@ -143,6 +154,7 @@ def check_box_with_bootstrap(name, options = {}, checked_value = "1", unchecked_ # TODO: Notice we don't seem to pass the ID into the custom control. checkbox_html.concat(label(label_name, label_description, class: label_class)) end + .concat(error_text) end else wrapper_class = "form-check" @@ -157,6 +169,7 @@ def check_box_with_bootstrap(name, options = {}, checked_value = "1", unchecked_ label_description, { class: label_class }.merge(options[:id].present? ? { for: options[:id] } : {}))) end + .concat(error_text) end end end @@ -179,6 +192,7 @@ def radio_button_with_bootstrap(name, value, *args) disabled_class = " disabled" if options[:disabled] label_classes = [options[:label_class]] label_classes << hide_class if options[:hide_label] + error_text = generate_help(name, options.delete(:help)).to_s if options[:custom] div_class = ["custom-control", "custom-radio"] @@ -191,6 +205,7 @@ def radio_button_with_bootstrap(name, value, *args) # TODO: Notice we don't seem to pass the ID into the custom control. radio_html.concat(label(name, options[:label], value: value, class: label_class)) end + .concat(error_text) end else wrapper_class = "form-check" @@ -203,6 +218,7 @@ def radio_button_with_bootstrap(name, value, *args) radio_html .concat(label(name, options[:label], { value: value, class: label_class }.merge(options[:id].present? ? { for: options[:id] } : {}))) end + .concat(error_text) end end end @@ -238,7 +254,6 @@ def form_group(*args, &block) content_tag(:div, options.except(:id, :label, :help, :icon, :label_col, :control_col, :layout)) do label = generate_label(options[:id], name, options[:label], options[:label_col], options[:layout]) if options[:label] control = capture(&block).to_s - control.concat(generate_help(name, options[:help]).to_s) if get_group_layout(options[:layout]) == :horizontal control_class = options[:control_col] || control_col @@ -351,7 +366,7 @@ def form_group_builder(method, options, html_options = nil) wrapper_class = css_options.delete(:wrapper_class) wrapper_options = css_options.delete(:wrapper) - help = options.delete(:help) + help = options[:help] icon = options.delete(:icon) label_col = options.delete(:label_col) control_col = options.delete(:control_col) diff --git a/lib/bootstrap_form/helpers/bootstrap.rb b/lib/bootstrap_form/helpers/bootstrap.rb index 058c9dc05..030301b42 100644 --- a/lib/bootstrap_form/helpers/bootstrap.rb +++ b/lib/bootstrap_form/helpers/bootstrap.rb @@ -66,15 +66,33 @@ def custom_control(*args, &block) form_group_builder(name, options, &block) end - def prepend_and_append_input(options, &block) - options = options.extract!(:prepend, :append, :input_group_class) + ## + # Add prepend and append, if any, and error if any. + # If anything is added, the whole thing is wrapped in an input-group. + def prepend_and_append_input(name, options, &block) + control_error_help(name, + options, + prepend: options.delete(:prepend), + append: options.delete(:append), + &block) + end + + ## + # Render a block, and add its error or help. + # Add prepend and append if provided, and wrap if they were, or if + # an input_group_class was provided. + def control_error_help(name, options, prepend: nil, append: nil, &block) + error_text = generate_help(name, options.delete(:help)).to_s + options = options.extract!(:input_group_class) input_group_class = ["input-group", options[:input_group_class]].compact.join(' ') input = capture(&block) - input = content_tag(:div, input_group_content(options[:prepend]), class: 'input-group-prepend') + input if options[:prepend] - input << content_tag(:div, input_group_content(options[:append]), class: 'input-group-append') if options[:append] - input = content_tag(:div, input, class: input_group_class) unless options.empty? + input = content_tag(:div, input_group_content(prepend), class: 'input-group-prepend') + input if prepend + input << content_tag(:div, input_group_content(append), class: 'input-group-append') if append + input << error_text + # FIXME: TBC The following isn't right yet. Wrap if there were errors. Maybe??? + input = content_tag(:div, input, class: input_group_class) unless options.empty? && prepend.nil? && append.nil? input end diff --git a/test/bootstrap_checkbox_test.rb b/test/bootstrap_checkbox_test.rb index 6798963ed..e1d4b3cff 100644 --- a/test/bootstrap_checkbox_test.rb +++ b/test/bootstrap_checkbox_test.rb @@ -144,8 +144,8 @@ class BootstrapCheckboxTest < ActionView::TestCase
+ With a help!
- With a help! HTML diff --git a/test/bootstrap_fields_test.rb b/test/bootstrap_fields_test.rb index 60c61fff7..98f13801b 100644 --- a/test/bootstrap_fields_test.rb +++ b/test/bootstrap_fields_test.rb @@ -65,6 +65,21 @@ class BootstrapFieldsTest < ActionView::TestCase assert_equivalent_xml expected, @builder.file_field(:misc) end + test "file fields are wrapped correctly with error" do + @user.errors.add(:misc, "error for test") + expected = <<-HTML.strip_heredoc +
+ +
+ + +
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.file_field(:misc) } + end + test "hidden fields are supported" do expected = %{} assert_equivalent_xml expected, @builder.hidden_field(:misc) diff --git a/test/bootstrap_form_group_test.rb b/test/bootstrap_form_group_test.rb index fd4e171cb..346bc8347 100644 --- a/test/bootstrap_form_group_test.rb +++ b/test/bootstrap_form_group_test.rb @@ -147,6 +147,32 @@ class BootstrapFormGroupTest < ActionView::TestCase assert_equivalent_xml expected, @builder.text_field(:email, prepend: '$', append: '.00') end + test "adding both prepend and append text with validation error" do + @user.email = nil + assert @user.invalid? + + expected = <<-HTML.strip_heredoc +
+ +
+ +
+
+ $
+
+ +
+ .00 +
+
can't be blank, is too short (minimum is 5 characters) +
+
+
+ HTML + # TODO: We should build the @builder properly from `bootstrap_form_for`, so it's easier to test errors. + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.text_field :email, prepend: '$', append: '.00' } + end + test "help messages for default forms" do expected = <<-HTML.strip_heredoc
@@ -303,22 +329,26 @@ class BootstrapFormGroupTest < ActionView::TestCase assert_equivalent_xml expected, output end - test 'form_group renders the "error" class and message corrrectly when object is invalid' do - @user.email = nil - assert @user.invalid? - - output = @builder.form_group :email do - %{

Bar

}.html_safe - end - - expected = <<-HTML.strip_heredoc -
-

Bar

-
can't be blank, is too short (minimum is 5 characters)
-
- HTML - assert_equivalent_xml expected, output - end + # test 'form_group renders the "error" class and message corrrectly when object is invalid' do + # # It could be said that the meaning of "form-group" has changed in Bootstrap 4, + # # and that's why it shouldn't be outputting the error message anymore. Which + # # would make this test case no longer valid. + # # THIS TEST WAS REMOVED FROM v2.7. + # @user.email = nil + # assert @user.invalid? + # + # output = @builder.form_group :email do + # %{

Bar

}.html_safe + # end + # + # expected = <<-HTML.strip_heredoc + #
+ #

Bar

+ #
can't be blank, is too short (minimum is 5 characters)
+ #
+ # HTML + # assert_equivalent_xml expected, output + # end test "adds class to wrapped form_group by a field" do expected = <<-HTML.strip_heredoc diff --git a/test/bootstrap_radio_button_test.rb b/test/bootstrap_radio_button_test.rb index 8cb8a627c..165691b73 100644 --- a/test/bootstrap_radio_button_test.rb +++ b/test/bootstrap_radio_button_test.rb @@ -99,8 +99,8 @@ class BootstrapRadioButtonTest < ActionView::TestCase + With a help!
- With a help! HTML @@ -172,8 +172,8 @@ class BootstrapRadioButtonTest < ActionView::TestCase
+ With a help!
- With a help! HTML @@ -188,8 +188,8 @@ class BootstrapRadioButtonTest < ActionView::TestCase
+ With a help!
- With a help! HTML @@ -242,8 +242,8 @@ class BootstrapRadioButtonTest < ActionView::TestCase
+ With a help!
- With a help! HTML @@ -258,8 +258,8 @@ class BootstrapRadioButtonTest < ActionView::TestCase
+ With a help!
- With a help! HTML diff --git a/test/bootstrap_selects_test.rb b/test/bootstrap_selects_test.rb index 216119e21..2ccfa7c8c 100644 --- a/test/bootstrap_selects_test.rb +++ b/test/bootstrap_selects_test.rb @@ -24,6 +24,21 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) assert_equivalent_xml expected, @builder.time_zone_select(:misc) end + test "time zone selects are wrapped correctly with error" do + @user.errors.add(:misc, "error for test") + expected = <<-HTML.strip_heredoc +
+ +
+ + +
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.time_zone_select(:misc) } + end + test "selects are wrapped correctly" do expected = <<-HTML.strip_heredoc
@@ -150,6 +165,21 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) assert_equivalent_xml expected, @builder.collection_select(:status, [], :id, :name) end + test "collection_selects are wrapped correctly with error" do + @user.errors.add(:status, "error for test") + expected = <<-HTML.strip_heredoc +
+ +
+ + +
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.collection_select(:status, [], :id, :name) } + end + test "collection_selects with options are wrapped correctly" do expected = <<-HTML.strip_heredoc
@@ -184,6 +214,21 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) assert_equivalent_xml expected, @builder.grouped_collection_select(:status, [], :last, :first, :to_s, :to_s) end + test "grouped_collection_selects are wrapped correctly with error" do + @user.errors.add(:status, "error for test") + expected = <<-HTML.strip_heredoc +
+ +
+ + +
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.grouped_collection_select(:status, [], :last, :first, :to_s, :to_s) } + end + test "grouped_collection_selects with options are wrapped correctly" do expected = <<-HTML.strip_heredoc
@@ -230,6 +275,33 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "date selects are wrapped correctly with error" do + @user.errors.add(:misc, "error for test") + Timecop.freeze(Time.utc(2012, 2, 3)) do + expected = <<-HTML.strip_heredoc +
+ +
+ +
+ + + +
+
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.date_select(:misc) } + end + end + test "date selects with options are wrapped correctly" do Timecop.freeze(Time.utc(2012, 2, 3)) do expected = <<-HTML.strip_heredoc @@ -304,6 +376,34 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "time selects are wrapped correctly with error" do + @user.errors.add(:misc, "error for test") + Timecop.freeze(Time.utc(2012, 2, 3, 12, 0, 0)) do + expected = <<-HTML.strip_heredoc +
+ +
+ +
+ + + + + : + +
+
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.time_select(:misc) } + end + end + test "time selects with options are wrapped correctly" do Timecop.freeze(Time.utc(2012, 2, 3, 12, 0, 0)) do expected = <<-HTML.strip_heredoc @@ -384,6 +484,41 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "datetime selects are wrapped correctly with error" do + @user.errors.add(:misc, "error for test") + Timecop.freeze(Time.utc(2012, 2, 3, 12, 0, 0)) do + expected = <<-HTML.strip_heredoc +
+ +
+ +
+ + + + — + + : + +
+
error for test
+
+
+ HTML + assert_equivalent_xml expected, bootstrap_form_for(@user) { |f| f.datetime_select(:misc) } + end + end + test "datetime selects with options are wrapped correctly" do Timecop.freeze(Time.utc(2012, 2, 3, 12, 0, 0)) do expected = <<-HTML.strip_heredoc