Rails 3: "field-with-errors" wrapper changes the page appearance. How to avoid this?

Ruby on-RailsRuby on-Rails-3ValidationField with-Errors

Ruby on-Rails Problem Overview


Email field:

<label for="job_client_email">Email: </label> 
<input type="email" name="job[client_email]" id="job_client_email">
            

looks like this:

without_error

But, if the email validation fails, it becomes:

<div class="field_with_errors">
  <label for="job_client_email">Email: </label>
</div> 
<div class="field_with_errors">
  <input type="email" value="wrong email" name="job[client_email]" id="job_client_email">
</div>

which looks like this:

with_error

How could I avoid this appearance change ?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

You should override ActionView::Base.field_error_proc. It's currently defined as this within ActionView::Base:

 @@field_error_proc = Proc.new{ |html_tag, instance| 
   "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe
 }

You can override it by putting this in your application's class inside config/application.rb:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| 
  html_tag
}

Restart rails server for this change to take effect.

Solution 2 - Ruby on-Rails

The visual difference you are seeing is happening because the div element is a block element. Add this style to your CSS file to make it behave like an inline element:

.field_with_errors { display: inline; }

Solution 3 - Ruby on-Rails

I currently use this solution, placed in an initializer:

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  class_attr_index = html_tag.index 'class="'

  if class_attr_index
    html_tag.insert class_attr_index+7, 'error '
  else
    html_tag.insert html_tag.index('>'), ' class="error"'
  end
end

This allows me to merely add a class name to the appropriate tag, without creating additional elements.

Solution 4 - Ruby on-Rails

The extra code is being added by ActionView::Base.field_error_proc. If you're not using field_with_errors to style your form, you can override it in application.rb:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag.html_safe }

Alternatively, you can change it to something that suits your UI:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| "<span class='field_with_errors'>#{html_tag}</span>".html_safe }

Solution 5 - Ruby on-Rails

I am working with Rails 5 and Materialize-Sass and I am getting some issues with the default behavior from Rails to treat failed field validations as in the image below and it was because of the extra div added to the input fields where validation failed.

enter image description here

Working with @Phobetron answer and modifying Hugo Demiglio's answer too. I made some adjustments to those blocks of code and I get something working well in the following cases:

  • If both input and label has their own class attribute anywhere
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • If the input or label tags does not have a class attribute
    • <input type="my-field">
    • <label for="...">My field</label>
  • if the label tag has another tag inside with the class attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

In all those cases the error class will be added to the existing classes in the class attribute if exist or it will be created if it is not present in the label or input tags.

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
	class_attr_index = html_tag.index('class="')
	first_tag_end_index = html_tag.index('>')

	# Just to inspect variables in the console
	puts 'šŸ˜Ž ' * 50
	pp(html_tag)
	pp(class_attr_index)
	pp(first_tag_end_index)

	if class_attr_index.nil? || class_attr_index > first_tag_end_index
		html_tag.insert(first_tag_end_index, ' class="error"')
	else
		html_tag.insert(class_attr_index + 7, 'error ')
	end

	# Just to see resulting tag in the console
	pp(html_tag)
end

I hope it could be useful for someone with the same conditions like me.

Solution 6 - Ruby on-Rails

In addition of @phobetron answer, which doesn't work when you have other tag with class attribute, like <label for="..."><i class="icon my-icon"></i>My field</label>.

I did some changes on his solution:

# config/initializers/field_with_error.rb

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  class_attr_index = html_tag.index('class="')
  first_tag_end_index = html_tag.index('>')

  if class_attr_index.nil? || first_tag_end_index > class_attr_index
    html_tag.insert(class_attr_index + 7, 'error ')
  else
    html_tag.insert(first_tag_end_index, ' class="error"')
  end
end

Solution 7 - Ruby on-Rails

If for some reason you are still working on Rails 2 (like I am) check out the SO post here.

It offers a script to put in initializers.

Solution 8 - Ruby on-Rails

One thing to keep in mind (as I discovered working through this today) is that if you float either the label or input fields (I'm floating all of the input fields right), the css will break even if you override ActionView::Base.field_error_proc.

An alternative is to drop a level deeper in the CSS formatting like so:

.field_with_errors label {
  padding: 2px;
  background-color: red;
}

.field_with_errors input[type="text"] {
  padding: 3px 2px;
  border: 2px solid red;
}

Solution 9 - Ruby on-Rails

I made an option to disable this terrible thing for some objects

# config/initializers/field_error_proc.rb

module ActiveModel::Conversion
  attr_accessor :skip_field_error_wrapper
end

ActionView::Base.field_error_proc = Proc.new {|html_tag, instance|
  if instance.object && instance.object.skip_field_error_wrapper
    html_tag.html_safe
  else
    "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe
  end
}

So can use it like this:

@user.skip_field_error_wrapper = true
form_for(@user) do |f|
  ...
end

Solution 10 - Ruby on-Rails

This is my solution building on top of @Phobetron's answer. Placing this code in application.rb, your <p> and <span> tags generated by the corresponding form.error :p calls will receive the fields_with_errors css tag. The rest will receive the error CSS class.

config.action_view.field_error_proc = Proc.new { |html_tag, instance|
  class_attr_index = html_tag.index 'class="'

  if class_attr_index
    # target only p's and span's with class error already there
    error_class = if html_tag =~ /^<(p|span).*error/
      'field_with_errors '
    else
      'error '
    end

    html_tag.insert class_attr_index + 7, error_class
  else
    html_tag.insert html_tag.index('>'), ' class="error"'
  end
}

I found this way the most flexible and unobstrusive of all previous to style the response across my forms.

Solution 11 - Ruby on-Rails

If it's just for styling purposes (you don't mind the div), you can just add this to your css:

div.field_with_errors {
 display: inline;
}

The div will act like a span and it won't interfere with your design (since div is a block element ā€“ display: block;ā€“ by default, it will cause a new line after it closes; span is inline, so it does not).

Solution 12 - Ruby on-Rails

If you just want to turn off errors for certain elements, e.g. checkboxes, you can do this:

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  doc = Nokogiri::HTML::Document.parse(html_tag)
  if doc.xpath("//*[@type='checkbox']").any?
    html_tag
  else
    "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe
  end
end

Solution 13 - Ruby on-Rails

If its only about styling issues, we can overwrite "field_with_errors". But as that might effect other forms in our application, it's better to overwrite the "field_with_errors" class with in that form only.

Considering 'parent_class' is one of the parent class for form's error field (either form's class or class of any of the parent element for error field), then

  .parent_class .field_with_errors {
    display: inline;
  }

It will fix the issue as well as, it won't disturb any other forms in our applicaiton as well.

OR

If we need to override the style of "field_with_errors" for whole applicaiton, then as @dontangg said,

.field_with_errors { display: inline; } 

will do the fix. Hope it helps :)

Solution 14 - Ruby on-Rails

If you don't want to change field_error_proc for your entire application, jQuery's unwrap can provide a more targeted solution for specific problem areas, e.g.,

$('FORM .field_with_errors > INPUT[type="checkbox"]').unwrap();

Solution 15 - Ruby on-Rails

You can easily disable the extra <div class="field_with_errors"> div completely if you don't want it at all for particular form elements. E.g. if you don't want it for <label>'s, use a custom FormBuilder.

E.g:

class MyFormBuilder < ActionView::Helpers::FormBuilder
  # Strip the containing div for labels associated with invalid fields:
  def label(method, text = nil, options = {}, &block)
    super(method, text, options, &block).gsub(%r{<div.*?>|<\/div>}, '').html_safe
  end
end

and then either add , builder: MyFormBuilder to your form_with/form_for in the view, OR add default_form_builder MyFormBuilder to your controller (or base controller if you want it global behaviour).

You could do similarly for inputs and other form elements too.

Credit to Jack Casey for this answer.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMisha MoroshkoView Question on Stackoverflow
Solution 1 - Ruby on-RailsRyan BiggView Answer on Stackoverflow
Solution 2 - Ruby on-RailsdontanggView Answer on Stackoverflow
Solution 3 - Ruby on-RailsPhobetronView Answer on Stackoverflow
Solution 4 - Ruby on-RailsdnchView Answer on Stackoverflow
Solution 5 - Ruby on-RailsalexventuraioView Answer on Stackoverflow
Solution 6 - Ruby on-RailsHugo DemiglioView Answer on Stackoverflow
Solution 7 - Ruby on-RailsScottJSheaView Answer on Stackoverflow
Solution 8 - Ruby on-RailsKevin ReethView Answer on Stackoverflow
Solution 9 - Ruby on-RailsPavel EvstigneevView Answer on Stackoverflow
Solution 10 - Ruby on-RailsdgilperezView Answer on Stackoverflow
Solution 11 - Ruby on-Railsuser2985898View Answer on Stackoverflow
Solution 12 - Ruby on-RailsTintin81View Answer on Stackoverflow
Solution 13 - Ruby on-RailsPremView Answer on Stackoverflow
Solution 14 - Ruby on-RailsSteveView Answer on Stackoverflow
Solution 15 - Ruby on-Railsstwr667View Answer on Stackoverflow