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?

Misha Moroshko
źródło
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.

Ryan Bigg
źródło
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; }
dontangg
źródło
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.

Fobetron
źródło
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 }
Dan Cheail
źródło
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.

alexventuraio
źródło
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
Hugo Demiglio
źródło
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.

ScottJShea
źródło
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;
}
Kevin Reeth
źródło
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
Pavel Evstigneev
źródło
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.

dgilperez
źródło
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).

user2985898
źródło
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
Tintin81
źródło
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 :)

Prem
źródło
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();
Steve
źródło
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ź.

stwr667
źródło