Rails 3: opakowanie „field-with-errors” zmienia wygląd strony. Jak tego uniknąć?


132

Pole e-mail:

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

wygląda tak:

bez błędu

Jeśli jednak weryfikacja adresu e-mail się nie powiedzie, stanie się:

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

który wygląda tak:

with_error

Jak mogę uniknąć tej zmiany wyglądu?


Cześć @ misha-moroshko, próbuję dodać klasę błędu na poziomie nadrzędnym, jak opisano tutaj . Próbowałem zagłębić się w kod szyn za pomocą narzędzia byebug, ale od razu się zgubiłem. Chciałem ustawić to zachowanie w trochę sprytny sposób, sprawdzając, czy te pola mają rodzica ..
— SanjiBukai

Odpowiedzi:


235

Powinieneś zastąpić ActionView::Base.field_error_proc. Obecnie jest zdefiniowany jako następujący w ActionView::Base:

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

Możesz to zmienić, umieszczając to w klasie swojej aplikacji wewnątrz config/application.rb:

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

Zrestartuj serwer rails, aby zmiana odniosła skutek.


4
Jedno małe pytanie: dlaczego zarówno te, jak labeli inputsą opakowane? W jaki sposób Railsy decydują, co owinąć?
— Misha Moroshko

4
Jest to prawdopodobnie zrobione w celu nadania stylu etykiety pola z błędami. Również szynach wie, co owinąć bo to powiedzieć, które należą do dziedziny, co atrybut zasobu swoje robią formularz na: f.label :passwordi f.password_field :passwordw @resource.errorsnie byłby [:password]zestaw błędów.
— Mosselman

3
Jeśli pracujesz z bootstrapem na Twitterze lub chcesz inny przykład tego, co możesz zrobić w field_error_proc, zapoznaj się z tym niesamowitym streszczeniem
— Ryan Sandridge

2
Dlaczego ktoś miałby zrobić „# {html_tag}”. Html_safe, a nie html_tag.html_safe?
— Anurag,

3
Anurag: jeśli zdarzy się, że html_tag ma wartość zero lub cokolwiek innego niż ciąg znaków, to zwykły html_tag.html_safe spowoduje błąd. Umieszczenie go w „# {html_tag}” niejawnie wywołuje html_tag.to_s, co, miejmy nadzieję, zwróci ciąg, który będzie mógł odpowiedzieć na html_safe
— sockmonk

100

Widoczna różnica wizualna ma miejsce, ponieważ divelement jest elementem blokowym. Dodaj ten styl do pliku CSS, aby zachowywał się jak element wbudowany:

.field_with_errors { display: inline; }

2
W najlepszym przypadku jest to hack, ponieważ neguje każdą display:używaną właściwość (i inne style układu) w html_tag.
— Ryan

1
Nie uważam tego za włamanie. displayWłaściwość jest stosowany przed tym CSS dodaje blockktóry powoduje różnicę wizualny, który nie jest pożądany. Nie neguje żadnych innych stylów układu w tagu. Jednak odpowiedź Ryana Bigga jest idealna, jeśli chcesz zmienić / usunąć tag, który otacza pole z błędami.
— dontangg

Próbowałem tego jednak, jeśli twoje pola są wewnątrz tagów <p>, wydaje się, że nie działa (przynajmniej nie w Firefoksie), ponieważ <div> w <p> łamie wiersze bez względu na wszystko. Używając rozwiązania Biggsa, tylko zastąpienie <div przez <span wydaje się załatwiać sprawę.
— jpw

72

Obecnie korzystam z tego rozwiązania, umieszczonego w inicjatorze:

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

Dzięki temu mogę po prostu dodać nazwę klasy do odpowiedniego tagu, bez tworzenia dodatkowych elementów.


2
To niesamowite, gdy używasz pól błędów w dyskretny sposób.
— Ryan

1
Działa na Railsach 4.0.3.
— Yuki Matsukura

1
Pracował dla mnie. Musiałem zrestartować serwer rails, żeby zauważyć zmiany :)
— Jezen Thomas.

1
Podobało mi się to rozwiązanie, ale nie zadziała, jeśli masz inny tag w pliku label.
— Caio Tarifa

Cześć @Phobetron, rzeczywiście to fajne rozwiązanie. Szukam sposobu, aby dodać tę klasę na poziomie rodzica, zamiast tego, jak opisano tutaj . Zanurzyłem się w kodzie railsów, ale od razu się zatraciłem, nie mogąc śledzić procesu renderowania za pomocą byebug .. Czy myślisz, że to jest rzeczywiście możliwe?
— SanjiBukai

21

Dodatkowy kod jest dodawany przez ActionView::Base.field_error_proc. Jeśli nie używasz field_with_errorsstylu do swojego formularza, możesz to zmienić w application.rb:

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

Alternatywnie możesz zmienić to na coś, co pasuje do Twojego interfejsu użytkownika:

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

To działa dobrze dla mnie; wydaje się być najbardziej eleganckim rozwiązaniem do użytku z Twitter Bootstrap
— Avishai

5

Pracuję z Railsami 5 i Materialise-Sass i dostaję pewne problemy z domyślnym zachowaniem Railsów, aby traktować nieudane walidacje pól, jak na obrazku poniżej, a było to z powodu dodatkowych divdodanych do pól wejściowych, w których walidacja się nie powiodła.

wprowadź opis obrazu tutaj

Praca z odpowiedzią @Phobetron i modyfikacja odpowiedzi Hugo Demiglio. Dokonałem pewnych poprawek w tych blokach kodu i coś działa dobrze w następujących przypadkach:

  • Jeśli oba inputi labelmają classgdziekolwiek swój własny atrybut
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Jeśli tagi inputlub labelnie mają classatrybutu
    • <input type="my-field">
    • <label for="...">My field</label>
  • jeśli labeltag ma w środku inny tag z rozszerzeniemclass attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

We wszystkich tych przypadkach errorklasa zostanie dodana do istniejących klas w classatrybucie, jeśli istnieją, lub zostanie utworzona, jeśli nie ma jej w etykiecie lub znacznikach wejściowych .

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

Mam nadzieję, że będzie to przydatne dla kogoś z takimi samymi schorzeniami jak ja.


4

Oprócz odpowiedzi @phobetron, która nie działa, gdy masz inny tag z atrybutem klasy, np <label for="..."><i class="icon my-icon"></i>My field</label>.

Zrobiłem kilka zmian w jego rozwiązaniu:

# 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

Ale to nie zadziała, jeśli twoje pole nie ma atrybutu klasy
— mizurnix

2

Jeśli z jakiegoś powodu nadal pracujesz na Railsach 2 (tak jak ja), zajrzyj do wpisu SO tutaj .

Oferuje skrypt do umieszczania inicjatorów.


2

Jedną rzeczą, o której należy pamiętać (jak odkryłem, pracując nad tym dzisiaj), jest to, że jeśli zmienisz etykietę lub pola wejściowe (przestawiam wszystkie pola wejściowe w prawo), css zepsuje się, nawet jeśli zastąpisz ActionView :: Base.field_error_proc.

Alternatywą jest zejście o jeden poziom głębiej w formatowaniu CSS w następujący sposób:

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

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

2

Zrobiłem opcję wyłączenia tej strasznej rzeczy dla niektórych obiektów

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

Więc możesz go użyć w ten sposób:

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

1

To jest moje rozwiązanie bazujące na odpowiedzi @ Phobetron. Umieszczając ten kod w application.rb, tagi <p>i <span>wygenerowane przez odpowiednie form.error :pwywołania otrzymają fields_with_errorstag css. Reszta otrzyma errorklasę 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
}

W ten sposób znalazłem najbardziej elastyczny i dyskretny sposób ze wszystkich poprzednich, aby nadać styl odpowiedzi w moich formularzach.


1

Jeśli jest to tylko do celów stylizacji (nie przeszkadza ci div), możesz po prostu dodać to do swojego css:

div.field_with_errors {
 display: inline;
}

divBędą zachowywać się jak spani nie będzie kolidować z projektu (ponieważ divjest elementem bloku - display: block;- domyślnie, spowoduje to nową linię po to zamyka, spanjest inline, więc nie robi).


1

Jeśli chcesz tylko wyłączyć błędy dla niektórych elementów, np. Pól wyboru , możesz to zrobić:

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

Jeśli chodzi tylko o problemy ze stylem, możemy nadpisać „field_with_errors”. Ale ponieważ może to wpłynąć na inne formularze w naszej aplikacji, lepiej jest nadpisać klasę „field_with_errors” tylko w tej formie.

Biorąc pod uwagę, że „parent_class” jest jedną z klas nadrzędnych dla pola błędu formularza (albo klasa formularza, albo klasa dowolnego elementu nadrzędnego dla pola błędu), to

  .parent_class .field_with_errors {
    display: inline;
  }

Naprawi to problem, a także nie zakłóci żadnych innych formularzy w naszej aplikacji.

LUB

Jeśli musimy zastąpić styl „field_with_errors” dla całej aplikacji, to jak powiedział @dontangg,

.field_with_errors { display: inline; } 

załatwi sprawę. Mam nadzieję, że to pomoże :)


0

Jeśli nie chcesz zmieniać field_error_proccałej aplikacji, rozpakowanie jQuery może zapewnić bardziej ukierunkowane rozwiązanie dla określonych obszarów problemowych, np.

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

0

Można łatwo wyłączyć dodatkowy <div class="field_with_errors">div całkowicie, jeśli nie chcą go w ogóle dla poszczególnych elementów formularza. Np. Jeśli nie chcesz tego przez <label>'s, użyj niestandardowego FormBuilder .

Na przykład:

class MyFormBuilder < ActionView::Helpers::FormBuilder
  # Strip the containing div for labels associated with invalid fields:
  def label(method, text = nil, options = {}, &block)
    super(method, text, options, &block).gsub(%r{<div.*?>|<\/div>}, '').html_safe
  end
end

a następnie albo dodać , builder: MyFormBuilderdo form_with/ form_forz punktu widzenia, OR dodać default_form_builder MyFormBuilderdo kontrolera lub sterownika bazowego (jeśli chcesz to globalne zachowanie).

Możesz zrobić podobnie dla danych wejściowych i innych elementów formularza.

Podziękowania dla Jacka Caseya za tę odpowiedź.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.