-
-
Save telwell/db42a4dafbe9cc3b7988debe358c88ad to your computer and use it in GitHub Desktop.
# 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 |
Here's an alternative version to the bootstrap_form_errors_customizer.rb above for bootstrap 4.
- It assumes you're using form-control for inputs and textareas
- it sets the error message after the label if you're using form-check-label and form-check-input
- it only creates a html list if there's multiple errors
# app/config/initializers/bootstrap_form_errors_customizer.rb
ActionView::Base.field_error_proc = proc do |html_tag, instance|
def invalid_feedback_msg(error_msg)
if error_msg.is_a?(Array) && error_msg.size > 1
html_list_errors = "<ul class='pl-3'></ul>"
error_msg.each do |msg|
html_list_errors.insert(-6,"<li>#{msg}</li>")
end
%{<div class='invalid-feedback'>#{html_list_errors}</div>}.html_safe
elsif error_msg.is_a?(Array)
%{<div class='invalid-feedback mb-1'>#{error_msg.first}</div>}.html_safe
else
%{<div class='invalid-feedback mb-1'>#{error_msg}</div>}.html_safe
end
end
if html_tag =~ /^<label/
if html_tag =~ /form-check-label/
html_tag.html_safe + invalid_feedback_msg(instance.error_message)
else
html_tag.html_safe
end
else
html_tag.gsub!("form-check-input", "form-check-input is-invalid")
html_tag.gsub!("form-control", "form-control is-invalid")
if html_tag =~ /form-check-input/
html_tag.html_safe
else
html_tag.html_safe + invalid_feedback_msg(instance.error_message)
end
end
end
Here's my version, it's made to support MaterializeCss form fields for which you only need the correct classes, logic goes like this:
- Check if a class attribute is not present, then set the classes in the first space available and next the tag out of the proc (I believe this is called negative programming, dealing with edge cases first).
- Last blocks only execute if the class is present, in which case we replace the start of it with the same + the classes at the start and a space, then just
return/next
it at the 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.
I've create a custom initializer to have each field having its own errors below it