Last active
May 18, 2023 16:54
-
-
Save telwell/db42a4dafbe9cc3b7988debe358c88ad to your computer and use it in GitHub Desktop.
Customize Field Errors with Rails 5 and Bootstrap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Adapted from https://rubyplus.com/articles/3401-Customize-Field-Error-in-Rails-5 | |
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| | |
html = '' | |
form_fields = [ | |
'textarea', | |
'input', | |
'select' | |
] | |
elements = Nokogiri::HTML::DocumentFragment.parse(html_tag).css "label, " + form_fields.join(', ') | |
elements.each do |e| | |
if e.node_name.eql? 'label' | |
html = %(#{e}).html_safe | |
elsif form_fields.include? e.node_name | |
e['class'] += ' is-invalid' | |
if instance.error_message.kind_of?(Array) | |
html = %(#{e}<div class="invalid-feedback">#{instance.error_message.uniq.join(', ')}</div>).html_safe | |
else | |
html = %(#{e}<div class="invalid-feedback">#{instance.error_message}</div>).html_safe | |
end | |
end | |
end | |
html | |
end |
I've create a custom initializer to have each field having its own errors right below it
# app/config/initializers/bootstrap_form_errors_customizer.rb ActionView::Base.field_error_proc = proc do |html_tag, instance| is_label_tag = html_tag =~ /^<label/ class_attr_index = html_tag.index 'class="' def format_error_message_to_html_list(error_msg) html_list_errors = "<ul></ul>" if error_msg.is_a?(Array) error_msg.each do |msg| html_list_errors.insert(-6,"<li>#{msg}</li>") end else html_list_errors.insert(-6,"<li>#{msg}</li>") end html_list_errors end invalid_div = "<div class='invalid-feedback'>#{format_error_message_to_html_list(instance.error_message)}</div>" if class_attr_index && !is_label_tag html_tag.insert(class_attr_index + 7, 'is-invalid ') html_tag + invalid_div.html_safe elsif !class_attr_index && !is_label_tag html_tag.insert(html_tag.index('>'), ' class="is-invalid"') html_tag + invalid_div.html_safe else html_tag.html_safe end end
guided me perfectly thank you very much @Ggs91
—-
I edited the code example to work with rails 6 and webpackered bootstrap 5
precisely: rails (6.1.3.2)
and bootstrap@^5.0.1
# app/config/initializers/bootstrap_form_errors_customizer.rb
ActionView::Base.field_error_proc = proc do |html_tag, instance|
is_label_tag = html_tag =~ /^<label/
class_attr_index = html_tag.index 'class="'
def format_error_message_to_html_list(error_msg)
html_list_errors = '<ul></ul>'
if error_msg.is_a?(Array) || error_msg.is_a?(ActiveModel::DeprecationHandlingMessageArray)
error_msg.each do |msg|
html_list_errors.insert(-6, "<li>#{msg}</li>")
end
else
html_list_errors.insert(-6, "<li>#{msg}</li>")
end
html_list_errors
end
invalid_div =
"<div class='invalid-feedback'>#{format_error_message_to_html_list(instance.error_message)}</div>"
if class_attr_index && !is_label_tag
html_tag.insert(class_attr_index + 7, 'is-invalid ')
html_tag + invalid_div.html_safe
elsif !class_attr_index && !is_label_tag
html_tag.insert(html_tag.index('>'), ' class="is-invalid"')
html_tag + invalid_div.html_safe
else
html_tag.html_safe
end
end
and don't forget to restart rails server when tinkering with initializers 😎 like i did 🦀
Thanks @simonneutert I've reduce it even further:
ActionView::Base.field_error_proc = proc do |html_tag, instance|
def format_error_message_to_html_list(instance)
messages = [*instance.error_message].map { |msg| "<li>#{msg}</li>" }
return unless messages.present?
"<div class='invalid-feedback'><ul>#{messages.join('')}</ul></div>"
end
html_tag + format_error_message_to_html_list(instance).html_safe
end
Is there a reason not to use Nokogiri (since it's a Rails dependency)? Maybe it's slower?
In Rails 7 I could reduce this whole String manipulation with RegExes with:
# config/application.rb
...
config.action_view.field_error_proc = proc do |html_tag, instance|
input_tag = Nokogiri::HTML5::DocumentFragment.parse(html_tag).at_css('.form-control')
if input_tag
input_tag.add_class('is-invalid').to_s.html_safe
else
html_tag
end
end
Next step for me is adding the error messages on a sibling '.invalid-feedback' element.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's my version, it's made to support MaterializeCss form fields for which you only need the correct classes, logic goes like this:
return/next
it at the end.