Rails 3: o invólucro "campo com erros" altera a aparência da página. Como evitar isso?

131

E-mail:

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

se parece com isso:

sem erro

Mas, se a validação do email falhar, ela se tornará:

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

que se parece com isso:

with_error

Como eu poderia evitar essa mudança de aparência?

Misha Moroshko
fonte
Oi @ misha-moroshko, eu tento adicionar a classe de erro no nível pai, conforme descrito aqui . Tentei mergulhar no código usando byebug trilhos, mas eu estava perdido imediatamente .. Eu queria configurar esse comportamento em um pouco de forma inteligente, verificando se os campo tem um pai ..
SanjiBukai

Respostas:

235

Você deve substituir ActionView::Base.field_error_proc. Atualmente, é definido como este em ActionView::Base:

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

Você pode substituí-lo colocando isso na classe do seu aplicativo dentro config/application.rb:

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

Reinicie o servidor rails para que essa alteração entre em vigor.

Ryan Bigg
fonte
4
Uma pequena pergunta: por que tanto o labelquanto o inputsão embrulhados? Como o Rails decide o que embrulhar?
Misha Moroshko 11/03/2019
4
Provavelmente, isso é feito para que você também possa estilizar o rótulo de um campo com erros. Além disso, o Rails sabe o que quebrar, porque você diz quais campos pertencem a qual atributo do recurso você está criando o formulário: f.label :passworde f.password_field :passwordno caso @resource.errorshaveria um [:password]conjunto de erros.
21412 Mosselman
3
Se você estiver trabalhando com o bootstrap do twitter, ou quiser outro exemplo do que pode fazer no field_error_proc, verifique esta incrível essência: gist.github.com/1464315
Ryan Sandridge
2
Por que alguém faria "# {html_tag}". Html_safe, e não html_tag.html_safe?
Anurag 16/09
3
Anurag: se html_tag for nulo, ou qualquer coisa que não seja uma string, um html_tag.html_safe comum causaria um erro. Colocá-lo em "# {html_tag}" chama implicitamente html_tag.to_s, que, esperançosamente, retornará uma string, que poderá responder a html_safe
sockmonk
100

A diferença visual que você está vendo está acontecendo porque o divelemento é um elemento de bloco. Adicione este estilo ao seu arquivo CSS para que ele se comporte como um elemento embutido:

.field_with_errors { display: inline; }
dontangg
fonte
2
Este é um truque, na melhor das hipóteses, porque nega qualquer display:propriedade que esteja sendo usada (e outros estilos de layout) no html_tag.
Ryan
1
Eu não vejo isso como um hack. A displaypropriedade que está sendo usada antes da adição deste css é a blockque está causando a diferença visual que não é desejada. Ele não nega nenhum outro estilo de layout na tag. No entanto, a resposta de Ryan Bigg é perfeita se você deseja alterar / remover a tag que envolve o campo com erros.
Dontangg
Eu tentei isso, no entanto, se seus campos estiverem dentro de tags <p>, ele parece não funcionar (pelo menos não no Firefox), pois um <div> dentro de um <p> quebra as linhas, não importa o quê. Usando a solução Biggs, apenas substituir <div por <span parece fazer o truque.
Jspw
72

Atualmente, uso esta solução, colocada em um inicializador:

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

Isso me permite apenas adicionar um nome de classe à tag apropriada, sem criar elementos adicionais.

Phobetron
fonte
2
Isso é incrível para usar os campos de erro de maneira discreta.
Ryan
1
Funciona no Rails 4.0.3.
Yuki Matsukura
1
Trabalhou para mim. Tive que reiniciar trilhos do servidor a notar mudanças embora :)
Jezen Thomas
1
Eu amei essa solução, mas ela não funcionará se você tiver outra tag dentro da label.
Caio Tarifa
Oi @ Phobetron, essa é realmente uma boa solução. Eu procuro uma maneira de adicionar essa classe no nível pai, como descrito aqui . Eu mergulhei no código dos trilhos, mas me perdi imediatamente, não sendo capaz de seguir o processo de renderização com o byebug. Você acha que isso é realmente possível?
SanjiBukai
20

O código extra está sendo adicionado por ActionView::Base.field_error_proc. Se você não estiver usando field_with_errorspara estilizar seu formulário, poderá substituí-lo em application.rb:

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

Como alternativa, você pode alterá-lo para algo adequado à sua interface do usuário:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| "<span class='field_with_errors'>#{html_tag}</span>".html_safe }
Dan Cheail
fonte
Isso está funcionando bem para mim; parece ser a solução mais elegante para uso com o Twitter Bootstrap
Avishai
5

Estou trabalhando com o Rails 5 e o Materialize-Sass e estou tendo alguns problemas com o comportamento padrão do Rails para tratar validações de campo com falha, como na imagem abaixo, e foi por causa do acréscimo divadicionado aos campos de entrada em que a validação falhou.

insira a descrição da imagem aqui

Trabalhando com a resposta @Phobetron e modificando a resposta de Hugo Demiglio também. Fiz alguns ajustes nesses blocos de código e recebo algo funcionando bem nos seguintes casos:

  • Se ambos inpute labeltiver seu próprio classatributo em qualquer lugar
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Se as tags inputou labelnão tiverem um classatributo
    • <input type="my-field">
    • <label for="...">My field</label>
  • se a labeltag tiver outra tag dentro com oclass attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

Em todos esses casos, a errorclasse será adicionada às classes existentes no classatributo, se existir, ou será criada se não estiver presente no rótulo ou nas tags de entrada .

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

Espero que possa ser útil para alguém com as mesmas condições que eu.

alexventuraio
fonte
4

Além da resposta @phobetron, que não funciona quando você tem outra tag com o atributo class, como <label for="..."><i class="icon my-icon"></i>My field</label>.

Eu fiz algumas mudanças em sua solução:

# 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
Hugo Demiglio
fonte
Mas isso não vai funcionar se o seu campo não tem um atributo de classe
mizurnix
2

Se, por algum motivo, você ainda estiver trabalhando no Rails 2 (como eu sou), confira o post aqui .

Ele oferece um script para colocar inicializadores.

ScottJShea
fonte
2

Uma coisa a ter em mente (como eu descobri trabalhando nisso hoje) é que, se você flutuar nos campos label ou de entrada (estou flutuando todos os campos de entrada corretamente), o css será quebrado mesmo se você substituir o ActionView :: Base.field_error_proc.

Uma alternativa é reduzir um nível mais profundo na formatação CSS da seguinte forma:

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

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

Eu fiz uma opção para desativar essa coisa terrível para alguns objetos

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

Então, pode usá-lo assim:

@user.skip_field_error_wrapper = true
form_for(@user) do |f|
  ...
end
Pavel Evstigneev
fonte
1

Esta é a minha solução baseada na resposta do @ Phobetron. Ao inserir esse código application.rb, suas tags <p>e <span>geradas pelas form.error :pchamadas correspondentes receberão a fields_with_errorstag css. O restante receberá a errorclasse 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
}

Achei assim o mais flexível e discreto de todos os anteriores para modelar a resposta em meus formulários.

dgilperez
fonte
1

Se for apenas para fins de estilo (você não se importa div), basta adicionar isso ao seu css:

div.field_with_errors {
 display: inline;
}

Ele divfuncionará como um spane não interferirá no seu design (já que divé um elemento de bloco - display: block;- por padrão, causará uma nova linha após o fechamento; spané inline, portanto, não).

user2985898
fonte
1

Se você quiser apenas desativar os erros de certos elementos, por exemplo , caixas de seleção , faça o seguinte:

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

Se for apenas sobre problemas de estilo, podemos substituir "field_with_errors". Mas como isso pode afetar outros formulários em nosso aplicativo, é melhor substituir a classe "field_with_errors" somente nesse formulário.

Considerando 'parent_class' é uma das classes pai para o campo de erro do formulário (classe do formulário ou classe de qualquer elemento pai para o campo de erro),

  .parent_class .field_with_errors {
    display: inline;
  }

Ele corrigirá o problema e também não perturbará outras formas em nosso aplicativo.

OU

Se precisarmos substituir o estilo de "field_with_errors" para todo o aplicativo, como @dontangg disse,

.field_with_errors { display: inline; } 

fará a correção. Espero que ajude :)

Prem
fonte
0

Se você não deseja alterar field_error_procpara todo o aplicativo, o desembrulhar do jQuery pode fornecer uma solução mais direcionada para áreas de problemas específicas, por exemplo,

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