Skip to content

Instantly share code, notes, and snippets.

@telwell
Last active May 18, 2023 16:54
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save telwell/db42a4dafbe9cc3b7988debe358c88ad to your computer and use it in GitHub Desktop.
Save telwell/db42a4dafbe9cc3b7988debe358c88ad to your computer and use it in GitHub Desktop.
Customize Field Errors with Rails 5 and Bootstrap
# 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
@simonneutert
Copy link

simonneutert commented Jul 21, 2021

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 🦀

@saxxi
Copy link

saxxi commented Mar 31, 2022

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

Screenshot 2022-03-31 at 18 25 54

Screenshot 2022-03-31 at 18 28 52

@gabrielso
Copy link

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