Rails 3: l'encapsuleur «champ avec erreurs» modifie l'apparence de la page. Comment éviter cela?

131

Champ Email:

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

ressemble à ça:

sans erreur

Mais, si la validation de l'e-mail échoue, cela devient:

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

qui ressemble à ceci:

with_error

Comment éviter ce changement d'apparence?

Misha Moroshko
la source
Salut @ misha-moroshko, j'essaie d'ajouter la classe d'erreur au niveau parent comme décrit ici . J'ai essayé de plonger dans le code des rails en utilisant byebug mais j'ai été perdu immédiatement .. Je voulais configurer ce comportement de manière un peu intelligente en vérifiant si ces champs ont un parent ..
SanjiBukai

Réponses:

235

Vous devriez passer outre ActionView::Base.field_error_proc. Il est actuellement défini comme ceci dans ActionView::Base:

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

Vous pouvez le remplacer en le mettant dans la classe de votre application à l'intérieur config/application.rb:

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

Redémarrez le serveur rails pour que cette modification prenne effet.

Ryan Bigg
la source
4
Une petite question: pourquoi le labelet le inputsont emballés? Comment Rails décide de ce qu'il faut emballer?
Misha Moroshko
4
Ceci est probablement fait pour que vous puissiez également styliser l'étiquette d'un champ avec des erreurs. De plus, rails sait quoi encapsuler parce que vous lui indiquez quels champs appartiennent à l'attribut de la ressource pour laquelle vous créez le formulaire: f.label :passwordet f.password_field :passworddans le @resource.errorsil y aurait un [:password]jeu d'erreurs.
Mosselman
3
Si vous travaillez avec Twitter bootstrap, ou si vous voulez un autre exemple de ce que vous pouvez faire dans field_error_proc, jetez un œil à ce génial: gist.github.com/1464315
Ryan Sandridge
2
Pourquoi ferait-on "# {html_tag}". Html_safe, et pas html_tag.html_safe?
Anurag
3
Anurag: si html_tag s'avère être nul, ou autre chose qu'une chaîne, alors un simple html_tag.html_safe déclencherait une erreur. Le mettre dans "# {html_tag}" appelle implicitement html_tag.to_s, qui, espérons-le, renverra une chaîne, qui pourra alors répondre à html_safe
sockmonk
100

La différence visuelle que vous voyez se produit parce que l' divélément est un élément de bloc. Ajoutez ce style à votre fichier CSS pour qu'il se comporte comme un élément en ligne:

.field_with_errors { display: inline; }
dontangg
la source
2
Il s'agit au mieux d'un hack car il annule la display:propriété utilisée (et les autres styles de mise en page) sur le html_tag.
Ryan
1
Je ne vois pas cela comme un hack. La displaypropriété utilisée avant l'ajout de ce css est la blockcause de la différence visuelle qui n'est pas souhaitée. Cela n'annule aucun autre style de mise en page sur la balise. Cependant, la réponse de Ryan Bigg est parfaite si vous souhaitez modifier / supprimer la balise qui entoure le champ avec des erreurs.
dontangg
J'ai essayé cela, cependant, si vos champs sont à l'intérieur des balises if <p>, cela ne semble pas fonctionner (du moins pas sur Firefox) car un <div> dans un <p> coupe les lignes quoi qu'il arrive. En utilisant la solution Biggs, seul le remplacement de <div par <span semble faire l'affaire.
jpw
72

J'utilise actuellement cette solution, placée dans un initialiseur:

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

Cela me permet simplement d'ajouter un nom de classe à la balise appropriée, sans créer d'éléments supplémentaires.

Phobétron
la source
2
C'est génial pour utiliser les champs d'erreur de manière discrète.
Ryan
1
Fonctionne sur les rails 4.0.3.
Yuki Matsukura
1
A travaillé pour moi. Il a fallu redémarrer le serveur de rails pour remarquer les changements :)
Jezen Thomas
1
J'ai adoré cette solution, mais cela ne fonctionnera pas si vous avez une autre balise dans le fichier label.
Caio Tarifa
Salut @Phobetron, en effet c'est une bonne solution. Je cherche un moyen d'ajouter cette classe au niveau des parents, comme décrit ici . J'ai plongé dans le code des rails mais je me suis tout de suite perdu de ne pas pouvoir suivre le processus de rendu avec byebug .. Pensez-vous que c'est réellement possible?
SanjiBukai
20

Le code supplémentaire est ajouté par ActionView::Base.field_error_proc. Si vous n'utilisez pas field_with_errorspour styliser votre formulaire, vous pouvez le remplacer dans application.rb:

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

Vous pouvez également le changer en quelque chose qui convient à votre interface utilisateur:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| "<span class='field_with_errors'>#{html_tag}</span>".html_safe }
Dan Cheail
la source
Cela fonctionne bien pour moi; semble être la solution la plus élégante à utiliser avec Twitter Bootstrap
Avishai
5

Je travaille avec Rails 5 et Materialise-Sass et j'obtiens des problèmes avec le comportement par défaut de Rails pour traiter les validations de champ ayant échoué comme dans l'image ci-dessous et c'était à cause du supplément divajouté aux champs de saisie où la validation a échoué.

entrez la description de l'image ici

Travailler avec @Phobetron answer et modifier la réponse d'Hugo Demiglio aussi. J'ai apporté quelques ajustements à ces blocs de code et j'obtiens quelque chose qui fonctionne bien dans les cas suivants:

  • Si les deux inputet labelont leur propre classattribut n'importe où
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Si les balises inputou labeln'ont pas d' classattribut
    • <input type="my-field">
    • <label for="...">My field</label>
  • si la labelbalise a une autre balise à l'intérieur avec leclass attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

Dans tous ces cas, la errorclasse sera ajoutée aux classes existantes dans l' classattribut si elles existent ou elle sera créée si elle n'est pas présente dans l' étiquette ou les balises d' entrée .

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

J'espère que cela pourrait être utile pour quelqu'un avec les mêmes conditions que moi.

alexventuraio
la source
4

En plus de @phobetron answer, qui ne fonctionne pas lorsque vous avez une autre balise avec un attribut de classe, comme <label for="..."><i class="icon my-icon"></i>My field</label>.

J'ai fait quelques changements sur sa 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
Hugo Demiglio
la source
Mais cela ne fonctionnera pas si votre champ n'a pas d'attribut de classe
mizurnix
2

Si pour une raison quelconque vous travaillez toujours sur Rails 2 (comme moi), consultez le message SO ici .

Il propose un script à mettre dans les initialiseurs.

ScottJShea
la source
2

Une chose à garder à l'esprit (comme je l'ai découvert aujourd'hui) est que si vous faites flotter soit l'étiquette, soit les champs d'entrée (je fais flotter tous les champs d'entrée à droite), le css se cassera même si vous remplacez ActionView :: Base.field_error_proc.

Une alternative consiste à abaisser un niveau plus profond dans le formatage CSS comme ceci:

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

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

J'ai fait une option pour désactiver cette chose terrible pour certains objets

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

Alors peut l'utiliser comme ceci:

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

C'est ma solution qui s'ajoute à la réponse de @ Phobetron. En plaçant ce code dans application.rb, vos balises <p>et <span>générées par les form.error :pappels correspondants recevront la fields_with_errorsbalise css. Le reste recevra la 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
}

J'ai trouvé de cette façon la plus flexible et la plus discrète de toutes les précédentes pour styliser la réponse sur mes formulaires.

dgilperez
la source
1

Si c'est juste à des fins de style (cela ne vous dérange pas div), vous pouvez simplement ajouter ceci à votre css:

div.field_with_errors {
 display: inline;
}

Le divagira comme un spanet il ne sera pas interférer avec votre conception (depuis divest un élément de bloc - display: block;- par défaut, il provoquera une nouvelle ligne après sa fermeture, spanest inline, donc il ne fonctionne pas).

user2985898
la source
1

Si vous souhaitez simplement désactiver les erreurs pour certains éléments, par exemple les cases à cocher , vous pouvez le faire:

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
la source
0

S'il ne s'agit que de problèmes de style, nous pouvons écraser "field_with_errors". Mais comme cela pourrait affecter d'autres formulaires dans notre application, il est préférable d'écraser la classe "field_with_errors" par sous cette forme uniquement.

Considérant que 'parent_class' est l'une des classes parentes du champ d'erreur du formulaire (soit la classe du formulaire, soit la classe de l'un des éléments parent du champ d'erreur), alors

  .parent_class .field_with_errors {
    display: inline;
  }

Cela résoudra le problème et ne perturbera pas non plus les autres formulaires de notre application.

OU

Si nous devons remplacer le style de "field_with_errors" pour toute l'application, alors comme @dontangg l'a dit,

.field_with_errors { display: inline; } 

fera le correctif. J'espère que ça aide :)

Prem
la source
0

Si vous ne voulez pas changer field_error_procpour l'ensemble de votre application, le dépliage de jQuery peut fournir une solution plus ciblée pour des problèmes spécifiques, par exemple,

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