Schienen 3: Der Wrapper "Feld mit Fehlern" ändert das Erscheinungsbild der Seite. Wie vermeide ich das?

131

E-Mail-Feld:

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

sieht aus wie das:

ohne Fehler

Wenn die E-Mail-Validierung jedoch fehlschlägt, wird Folgendes angezeigt:

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

das sieht so aus:

with_error

Wie kann ich diese Änderung des Erscheinungsbilds vermeiden?

Mischa Moroshko
quelle
Hallo @ misha-moroshko, ich versuche, die Fehlerklasse auf der übergeordneten Ebene hinzuzufügen, wie hier beschrieben . Ich habe versucht, mit byebug in den Rails-Code einzutauchen, aber ich war sofort verloren. Ich wollte dieses Verhalten auf eine etwas kluge Art und Weise einrichten, indem ich überprüfe, ob diese Felder einen Elternteil haben.
SanjiBukai

Antworten:

235

Sie sollten überschreiben ActionView::Base.field_error_proc. Es ist derzeit wie folgt definiert in ActionView::Base:

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

Sie können es überschreiben, indem Sie dies in die Klasse Ihrer Anwendung einfügen config/application.rb:

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

Starten Sie den Rails-Server neu, damit diese Änderung wirksam wird.

Ryan Bigg
quelle
4
Eine kleine Frage: Warum sind sowohl die labelals auch die inputverpackt? Wie entscheidet Rails, was verpackt werden soll?
Mischa Moroshko
4
Dies geschieht wahrscheinlich, damit Sie auch die Beschriftung eines Feldes mit Fehlern formatieren können. Außerdem weiß Rails, was zu verpacken ist, da Sie angeben, welche Felder zu welchem ​​Attribut der Ressource gehören, für die Sie das Formular erstellen : f.label :passwordund f.password_field :passwordin der @resource.errorsein [:password]Fehler auftreten würde .
Mosselman
3
Wenn Sie mit Twitter Bootstrap arbeiten oder ein weiteres Beispiel dafür wünschen, was Sie in field_error_proc tun können, sehen Sie sich diese großartige Zusammenfassung an: gist.github.com/1464315
Ryan Sandridge
2
Warum sollte man "# {html_tag}". Html_safe und nicht html_tag.html_safe machen?
Anurag
3
Anurag: Wenn html_tag null oder etwas anderes als eine Zeichenfolge ist, würde ein einfaches html_tag.html_safe einen Fehler auslösen. Wenn Sie es in "# {html_tag}" einfügen, wird implizit html_tag.to_s aufgerufen, das hoffentlich eine Zeichenfolge zurückgibt, die dann um
sockmonk
100

Der visuelle Unterschied, den Sie sehen, tritt auf, weil das divElement ein Blockelement ist. Fügen Sie diesen Stil Ihrer CSS-Datei hinzu, damit sie sich wie ein Inline-Element verhält:

.field_with_errors { display: inline; }
dontangg
quelle
2
Dies ist bestenfalls ein Hack, da dadurch alle display:Eigenschaften (und andere Layoutstile), die auf dem verwendet werden, negiert werden html_tag.
Ryan
1
Ich sehe es nicht als Hack. Die displayEigenschaft, die vor dem Hinzufügen dieses CSS verwendet wird, blockverursacht den visuellen Unterschied, der nicht erwünscht ist. Es werden keine anderen Layoutstile auf dem Tag negiert. Die Antwort von Ryan Bigg ist jedoch perfekt, wenn Sie das Tag ändern / entfernen möchten, das das Feld mit Fehlern umschließt.
Dontangg
Ich habe dies jedoch versucht, wenn sich Ihre Felder in <p> -Tags befinden, scheint es nicht zu funktionieren (zumindest nicht in Firefox), da ein <div> innerhalb eines <p> Zeilen bricht, egal was passiert. Bei Verwendung der Biggs-Lösung scheint es der Trick zu sein, nur <div durch <span zu ersetzen.
JPW
72

Ich verwende derzeit diese Lösung in einem Initialisierer:

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

Auf diese Weise kann ich dem entsprechenden Tag lediglich einen Klassennamen hinzufügen, ohne zusätzliche Elemente zu erstellen.

Phobetron
quelle
2
Dies ist fantastisch, um die Fehlerfelder unauffällig zu verwenden.
Ryan
1
Funktioniert auf Schienen 4.0.3.
Yuki Matsukura
1
Hat für mich gearbeitet. Musste Rails Server neu starten, um Änderungen zu bemerken :)
Jezen Thomas
1
Ich habe diese Lösung geliebt, aber sie funktioniert nicht, wenn Sie ein anderes Tag in der haben label.
Caio Tarifa
Hallo @Phobetron, in der Tat ist dies eine schöne Lösung. Ich suche nach einer Möglichkeit, diese Klasse auf der übergeordneten Ebene hinzuzufügen, wie hier beschrieben . Ich habe mich in den Rails-Code vertieft, aber ich habe mich sofort verloren, weil ich nicht in der Lage war, den Rendering-Prozess mit byebug zu verfolgen. Ist das tatsächlich möglich?
SanjiBukai
20

Der zusätzliche Code wird von hinzugefügt ActionView::Base.field_error_proc. Wenn Sie field_with_errorsIhr Formular nicht zum Stylen verwenden, können Sie es überschreiben in application.rb:

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

Alternativ können Sie es in etwas ändern, das zu Ihrer Benutzeroberfläche passt:

config.action_view.field_error_proc = Proc.new { |html_tag, instance| "<span class='field_with_errors'>#{html_tag}</span>".html_safe }
Dan Cheail
quelle
Das funktioniert gut für mich; scheint die eleganteste Lösung für die Verwendung mit Twitter Bootstrap zu sein
Avishai
5

Ich arbeite mit Rails 5 und Materialise-Sass und bekomme einige Probleme mit dem Standardverhalten von Rails, um fehlgeschlagene Feldvalidierungen wie in der Abbildung unten zu behandeln. Dies lag an den zusätzlichen divHinzufügungen zu den Eingabefeldern, in denen die Validierung fehlgeschlagen ist.

Geben Sie hier die Bildbeschreibung ein

Arbeiten mit der Antwort von @Phobetron und Ändern der Antwort von Hugo Demiglio. Ich habe einige Anpassungen an diesen Codeblöcken vorgenommen und in den folgenden Fällen funktioniert etwas gut:

  • Wenn beides inputund irgendwo ein labeleigenes classAttribut hat
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Wenn die Tags inputoder labelkein classAttribut haben
    • <input type="my-field">
    • <label for="...">My field</label>
  • Wenn das labelTag ein anderes Tag mit dem enthältclass attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

In all diesen Fällen wird die errorKlasse zu den vorhandenen Klassen im classAttribut hinzugefügt, falls vorhanden, oder sie wird erstellt, wenn sie nicht in der Bezeichnung oder den Eingabe- Tags vorhanden ist.

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

Ich hoffe, es könnte für jemanden mit den gleichen Bedingungen wie ich nützlich sein.

alexventuraio
quelle
4

Zusätzlich zu @phobetron Antwort, die nicht funktioniert, wenn Sie ein anderes Tag mit Klassenattribut haben, wie <label for="..."><i class="icon my-icon"></i>My field</label>.

Ich habe einige Änderungen an seiner Lösung vorgenommen:

# 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
quelle
Dies funktioniert jedoch nicht, wenn Ihr Feld kein Klassenattribut hat
Mizurnix
2

Wenn Sie aus irgendeinem Grund immer noch an Rails 2 arbeiten (wie ich), lesen Sie den SO-Beitrag hier .

Es bietet ein Skript zum Einfügen von Initialisierern.

ScottJShea
quelle
2

Eine Sache, die Sie beachten sollten (wie ich heute festgestellt habe), ist, dass wenn Sie entweder die Beschriftungs- oder die Eingabefelder schweben lassen (ich schwebe alle Eingabefelder nach rechts), das CSS die Gewinnschwelle überschreitet, wenn Sie ActionView :: überschreiben. Base.field_error_proc.

Eine Alternative besteht darin, die CSS-Formatierung wie folgt zu vertiefen:

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

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

Ich habe eine Option gewählt, um diese schreckliche Sache für einige Objekte zu deaktivieren

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

Kann es also so verwenden:

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

Dies ist meine Lösung, die auf der Antwort von @ Phobetron aufbaut. Wenn Sie diesen Code eingeben application.rb, erhalten Ihre <p>und <span>Tags, die durch die entsprechenden form.error :pAufrufe generiert wurden , das fields_with_errorsCSS-Tag. Der Rest erhält die errorCSS-Klasse.

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
}

Ich fand diesen Weg am flexibelsten und unauffälligsten, um die Antwort über meine Formulare hinweg zu gestalten.

dgilperez
quelle
1

Wenn es nur für Stylingzwecke ist (das macht Ihnen nichts aus div), können Sie dies einfach zu Ihrem CSS hinzufügen:

div.field_with_errors {
 display: inline;
}

Das divwird sich wie ein spanverhalten und Ihr Design nicht beeinträchtigen (da dives sich um ein Blockelement handelt - display: block;- wird standardmäßig nach dem Schließen eine neue Zeile verursacht; spanist inline, also nicht).

user2985898
quelle
1

Wenn Sie nur Fehler für bestimmte Elemente deaktivieren möchten, z. B. Kontrollkästchen , können Sie Folgendes tun:

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
Tim und Struppi81
quelle
0

Wenn es nur um Stylingprobleme geht, können wir "field_with_errors" überschreiben. Da sich dies jedoch auf andere Formulare in unserer Anwendung auswirken kann, ist es besser, die Klasse "field_with_errors" nur in diesem Formular zu überschreiben.

Wenn man bedenkt, dass 'parent_class' eine der übergeordneten Klassen für das Fehlerfeld des Formulars ist (entweder die Klasse des Formulars oder die Klasse eines der übergeordneten Elemente für das Fehlerfeld)

  .parent_class .field_with_errors {
    display: inline;
  }

Es wird das Problem beheben und auch keine anderen Formen in unserer Anwendung stören.

ODER

Wenn wir den Stil von "field_with_errors" für die gesamte Anwendung überschreiben müssen, dann, wie @dontangg sagte,

.field_with_errors { display: inline; } 

wird das Update machen. Ich hoffe es hilft :)

Prem
quelle
0

Wenn Sie nicht field_error_procfür Ihre gesamte Anwendung Änderungen vornehmen möchten, bietet jQuerys Unwrap eine gezieltere Lösung für bestimmte Problembereiche, z.

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