Rails 3:“有错误的字段”包装器更改页面外观。如何避免这种情况?


131

电子邮件字段:

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

看起来像这样:

没有错误

但是,如果电子邮件验证失败,它将变为:

<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>

看起来像这样:

with_error

如何避免这种外观变化?


@ misha-moroshko,您好,我尝试按此处所述在父级添加错误类。我试图使用byebug深入研究Rails代码,但我立即迷路了。.我想通过检查那些字段是否有父级来以一种聪明的方式设置此行为
。– SanjiBukai

Answers:


235

您应该覆盖ActionView::Base.field_error_proc。目前在内定义为ActionView::Base

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

您可以通过将其放在应用程序的类中来覆盖它config/application.rb

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

重新启动Rails服务器以使此更改生效。


4
一个小问题:为什么labelinput都被包裹?Rails如何决定包装什么?
Misha Moroshko 2011年

4
这样做可能是为了使您也可以为带有错误的字段标签设置样式。另外,Rails知道要包装的内容,因为您告诉它哪些字段属于您要为其创建表单的资源的哪个属性: f.label :password并且f.password_field :password@resource.errors其中将出现[:password]错误集。
Mosselman 2012年

3
如果您使用的是Twitter Bootstrap,或者想要在field_error_proc中执行其他操作的示例,请查看以下出色信息:gist.github.com/1464315
Ryan Sandridge

2
为什么要执行“#{html_tag}”。html_safe而不是html_tag.html_safe?
阿努拉格

3
Anurag:如果html_tag恰好为nil或除字符串以外的任何其他东西,则纯html_tag.html_safe会引发错误。将其放在“#{html_tag}”中会隐式调用html_tag.to_s,希望它将返回一个字符串,然后该字符串将能够响应html_safe
sockmonk

100

您正在看到的视觉差异正在发生,因为该div元素是一个块元素。将此样式添加到CSS文件中,使其表现得像内联元素:

.field_with_errors { display: inline; }

2
充其量是骇客,因为它会否定display:上使用的任何属性(和其他布局样式)html_tag
瑞安

1
我不认为这是hack。display在添加此css之前使用的属性block造成了不希望的视觉差异。它不会否定标记上的其他任何布局样式。但是,如果您想更改/删除用错误包装字段的标签,Ryan Bigg的答案是完美的。
dontangg

我试过了,但是,如果您的字段位于<p>标记内,则它似乎不起作用(至少在Firefox上不行),因为<p>中的<div>会中断行。使用Biggs解决方案时,仅用<span替换<div似乎可以解决问题。
jpw 2012年

72

我目前使用的是放置在初始化程序中的此解决方案:

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

这使我可以仅将类名添加到适当的标记中,而无需创建其他元素。


2
这对于以不引人注目的方式使用错误字段非常有用。
瑞安

1
在Rails 4.0.3上工作。
松仓由纪(Yuki Matsukura)

1
为我工作。不得不重启rails服务器以注意到更改:)
Jezen Thomas

1
我喜欢这种解决方案,但是如果您在中有其他标签,它将无法正常工作label
塔奥塔法

嗨@Phobetron,确实这是一个不错的解决方案。我搜索一种在父级别添加该类的方法,而不是像这里描述的那样。我沉迷于Rails代码,但我立即迷失了自己,无法使用Byebug进行渲染过程。
SanjiBukai

20

额外的代码由添加ActionView::Base.field_error_proc。如果您不习惯field_with_errors为表单设置样式,则可以在中进行覆盖application.rb

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

或者,您可以将其更改为适合您的UI的内容:

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

这对我来说很好。似乎是与Twitter Bootstrap结合使用的最优雅的解决方案
Avishai

5

我正在使用Rails 5和Materialize-Sass,并且从Rails收到默认行为的一些问题,以处理失败的字段验证,如下图所示,这是因为div在验证失败的输入字段中添加了额外的内容。

在此处输入图片说明

使用@Phobetron答案并修改Hugo Demiglio的答案。我对这些代码块进行了一些调整,并且在以下情况下可以正常工作:

  • 如果同时inputlabel有自己的class属性在任何地方
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • 如果inputlabel标记没有class属性
    • <input type="my-field">
    • <label for="...">My field</label>
  • 如果label标签内有另一个标签,class attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

在所有这些情况下,error该类都将添加到class属性中现有的类(如果存在)中,或者如果标签输入标签中不存在该类,则将创建该类。

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

我希望它对像我这样具有相同条件的人有用。


4

除了@phobetron答案外,当您具有带有class属性的其他标记(如)时,该答案将无效<label for="..."><i class="icon my-icon"></i>My field</label>

我对他的解决方案做了一些更改:

# 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

但是,如果您的字段没有类属性,则此方法将不起作用
mizurnix

2

如果由于某种原因您仍在使用Rails 2(例如我),请在此处查看SO帖子。

它提供了一个放置初始化程序的脚本。


2

要记住的一件事(正如我今天发现的那样)是,如果浮动标签或输入字段(我将所有输入字段都浮动),则即使您覆盖ActionView,css也会中断: Base.field_error_proc。

一种替代方法是像这样在CSS格式中更深一层:

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

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

2

我选择了禁用某些对象的可怕功能

# 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
}

因此可以像这样使用它:

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

1

这是我在@Phobetron的答案之上构建的解决方案。将此代码放在中application.rb,由相应调用生成的<p><span>标签form.error :p将收到fields_with_errorscss标签。其余的将接收errorCSS类。

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
}

我发现这种方式是以往所有方式中最灵活,最不引人注意的,可以在表单中设置响应样式。


1

如果只是出于样式目的(您不必介意div),则可以将其添加到CSS中:

div.field_with_errors {
 display: inline;
}

div作用就像a span并且不会干扰您的设计(由于div是block元素– display: block;默认情况下,它将在关闭后引起新的一行;spaninline,所以不会)。


1

如果您只想关闭某些元素的错误(例如复选框),则可以执行以下操作:

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

0

如果仅涉及样式问题,则可以覆盖“ field_with_errors”。但这可能会影响应用程序中的其他形式,因此最好仅以该形式覆盖“ field_with_errors”类。

考虑到“ parent_class”是表单错误字段的父类之一(表单的类或错误字段的任何父元素的类),则

  .parent_class .field_with_errors {
    display: inline;
  }

它将解决该问题,也不会干扰我们申请中的任何其他形式。

要么

如果整个应用程序都需要覆盖“ field_with_errors”样式,那么就像@dontangg所说的那样,

.field_with_errors { display: inline; } 

将解决此问题。希望能帮助到你 :)


0

如果您不想field_error_proc为整个应用程序进行更改,则jQuery的展开可以为特定问题区域提供更具针对性的解决方案,例如,

$('FORM .field_with_errors > INPUT[type="checkbox"]').unwrap();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.