Rails 3: оболочка «field-with-errors» меняет внешний вид страницы. Как этого избежать?

131

Поле электронной почты:

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

выглядит так:

without_error

Но, если проверка адреса электронной почты завершится неудачно, он станет:

<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, но я сразу потерялся .. Я хотел настроить это поведение немного умным способом, проверив, есть ли у этого поля родительский
элемент

Ответы:

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
Один маленький вопрос: Почему как labelи inputзаворачивают? Как Rails решает, что обернуть?
Миша Морошко
4
Вероятно, это сделано для того, чтобы вы также могли стилизовать метку поля с ошибками. Кроме того, rails знает, что обернуть, потому что вы сообщаете ему, какие поля принадлежат какому атрибуту ресурса, для которого вы создаете форму: f.label :passwordи f.password_field :passwordв этом @resource.errorsбудет установлена [:password]ошибка.
Mosselman
3
Если вы работаете с загрузкой twitter или вам нужен еще один пример того, что вы можете сделать в field_error_proc, ознакомьтесь с этой замечательной сутью
Райан Сэндридж
2
Зачем делать "# {html_tag}". Html_safe, а не html_tag.html_safe?
Anurag
3
Anurag: если html_tag оказывается равным нулю или чем-то другим, кроме строки, тогда простой html_tag.html_safe вызовет ошибку. Помещение его в "# {html_tag}" неявно вызывает html_tag.to_s, который, надеюсь, вернет строку, которая затем сможет отвечать на html_safe
sockmonk
100

Визуальная разница, которую вы видите, происходит потому, что divэлемент является блочным. Добавьте этот стиль в свой файл CSS, чтобы он вел себя как встроенный элемент:

.field_with_errors { display: inline; }
dontangg
источник
2
В лучшем случае это хитрость, потому что она сводит на нет все display:используемые свойства (и другие стили макета) в html_tag.
Райан
1
Я не вижу в этом взлома. displayСвойство используется перед этим КСС добавляемого , blockкоторая вызывает визуальное различие, не желательно. Это не отменяет никаких других стилей макета тега. Однако ответ Райана Бигга идеален, если вы хотите изменить / удалить тег, который обертывает поле с ошибками.
dontangg
Я пробовал это, однако, если ваши поля находятся внутри тегов if <p>, это не работает (по крайней мере, не в Firefox), поскольку <div> внутри <p> разрывает строки, несмотря ни на что. При использовании решения Биггса только замена <div на <span, похоже, помогает.
jpw
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

Это позволяет мне просто добавить имя класса к соответствующему тегу, не создавая дополнительных элементов.

Phobetron
источник
2
Это замечательно для ненавязчивого использования полей ошибок.
Райан
1
Работает на Rails 4.0.3.
Юки Мацукура
1
Работал на меня. Пришлось перезапустить сервер rails, чтобы заметить изменения :)
Джезен Томас
1
Мне понравилось это решение, но оно не будет работать, если у вас есть другой тег внутри label.
Кайо Тарифа
Привет @Phobetron, это действительно хорошее решение. Я ищу способ добавить этот класс на родительский уровень, а не как описано здесь . Я погрузился в код рельсов, но я сразу потерял себя, не имея возможности следить за процессом рендеринга с помощью 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 }

Кроме того, вы можете изменить его на то, что подходит вашему пользовательскому интерфейсу:

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 и изменение ответа Хьюго Демильо. Я внес некоторые изменения в эти блоки кода, и у меня что-то работает хорошо в следующих случаях:

  • Если у обоих inputи labelесть собственный classатрибут где угодно
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Если теги inputили labelне имеют 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

Я надеюсь, что это может быть полезно для кого-то с такими же условиями, как я.

alexventuraio
источник
4

В дополнение к ответу @phobetron, который не работает, когда у вас есть другой тег с атрибутом класса, например <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 здесь .

Он предлагает сценарий для установки инициализаторов.

ScottJShea
источник
2

Одна вещь, о которой следует помнить (как я обнаружил, работая над этим сегодня), заключается в том, что если вы разместите либо метку, либо поля ввода (я плаваю все поля ввода правильно), css сломается, даже если вы переопределите ActionView :: 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 тег. Остальные получат errorкласс CSS.

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
}

Я нашел этот способ наиболее гибким и ненавязчивым из всех предыдущих для стилизации ответа в моих формах.

dgilperez
источник
1

Если это просто для стилизации (вы не против div), вы можете просто добавить это в свой css:

div.field_with_errors {
 display: inline;
}

Он divбудет действовать как a spanи не будет мешать вашему дизайну (поскольку divэто блочный элемент - display: block;- по умолчанию он вызовет новую строку после закрытия; spanесть inline, поэтому это не так).

user2985898
источник
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
Tintin81
источник
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; } 

сделаю исправление. Надеюсь, поможет :)

Prem
источник
0

Если вы не хотите вносить изменения field_error_procдля всего приложения, развертка jQuery может предоставить более целенаправленное решение для конкретных проблемных областей, например,

$('FORM .field_with_errors > INPUT[type="checkbox"]').unwrap();
Стив
источник