Rails 3: Trường bọc có lỗi với Trình bao bọc thay đổi giao diện trang. Làm thế nào để tránh điều này?


131

Trường email:

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

trông như thế này:

không có lỗi

Nhưng, nếu xác thực email không thành công, nó sẽ trở thành:

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

trông như thế này:

with_error

Làm thế nào tôi có thể tránh sự thay đổi ngoại hình này?


Xin chào @ misha-moroshko, tôi thử thêm lớp lỗi ở cấp độ cha mẹ như được mô tả ở đây . Tôi đã cố gắng đi sâu vào mã đường ray bằng cách sử dụng byebug nhưng tôi đã bị mất ngay lập tức .. Tôi muốn thiết lập hành vi này một cách thông minh bằng cách kiểm tra xem các trường đó có cha mẹ không ..
SanjiBukai

Câu trả lời:


235

Bạn nên ghi đè ActionView::Base.field_error_proc. Hiện tại nó được định nghĩa như thế này trong ActionView::Base:

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

Bạn có thể ghi đè lên nó bằng cách đặt cái này vào lớp ứng dụng của bạn bên trong config/application.rb:

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

Khởi động lại máy chủ rails để thay đổi này có hiệu lực.


4
Một câu hỏi nhỏ: Tại sao cả hai labelinputđược bọc? Làm thế nào Rails quyết định những gì để bọc?
Misha Moroshko

4
Điều này có thể được thực hiện để bạn có thể tạo kiểu cho nhãn của một trường có lỗi. Ngoài ra, đường ray biết phải quấn bởi vì bạn nói với nó mà các lĩnh vực thuộc về những gì thuộc tính của tài nguyên của bạn đang làm cho hình thức cho: f.label :passwordf.password_field :passwordtrong @resource.errorsđó sẽ là một [:password]bộ lỗi.
Mosselman

3
Nếu bạn đang làm việc với twitter bootstrap hoặc bạn muốn một ví dụ khác về những gì bạn có thể làm trong field_error_proc, hãy khám phá ý chính tuyệt vời này: gist.github.com/1464315
Ryan Sandridge

2
Tại sao một người sẽ làm "# {html_tag}". Html_safe chứ không phải html_tag.html_safe?
Anurag

3
Anurag: nếu html_tag xảy ra là không, hoặc bất kỳ thứ gì khác ngoài chuỗi, thì html_tag.html_safe đơn giản sẽ gây ra lỗi. Đặt nó trong "# {HTML_TAG}" ngầm gọi html_tag.to_s, mà hy vọng sẽ trả về một chuỗi, sau đó sẽ có thể đáp ứng với html_safe
sockmonk

100

Sự khác biệt trực quan mà bạn đang thấy đang xảy ra vì divyếu tố này là một yếu tố khối. Thêm kiểu này vào tệp CSS của bạn để làm cho nó hoạt động như một phần tử nội tuyến:

.field_with_errors { display: inline; }

2
Đây là một hack lúc tốt nhất bởi vì nó phủ nhận bất cứ điều gì display:bất động sản đang được sử dụng (và phong cách bố trí khác) trên html_tag.
Ryan

1
Tôi không thấy nó là một hack. Các displaytài sản đang được sử dụng trước khi css này được thêm vào là blockđược gây ra sự khác biệt hình ảnh không được như mong muốn. Nó không phủ nhận bất kỳ kiểu bố trí nào khác trên thẻ. Tuy nhiên, câu trả lời của Ryan Bigg là hoàn hảo nếu bạn muốn thay đổi / xóa thẻ bao bọc trường có lỗi.
dontangg

Tuy nhiên, tôi đã thử điều này nếu các trường của bạn nằm trong các thẻ <p>, nó dường như không hoạt động (ít nhất là không phải trên Firefox) vì <div> trong <p> ngắt dòng bất kể là gì. Sử dụng giải pháp Bigss, chỉ thay thế <div bằng <span dường như thực hiện thủ thuật.
jpw

72

Tôi hiện đang sử dụng giải pháp này, được đặt trong một trình khởi tạo:

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

Điều này cho phép tôi chỉ thêm một tên lớp vào thẻ thích hợp mà không cần tạo các thành phần bổ sung.


2
Điều này thật tuyệt vời khi sử dụng các trường lỗi theo cách không phô trương.
Ryan

1
Hoạt động trên Rails 4.0.3.
Yuki Matsukura

1
Đã làm cho tôi. Phải khởi động lại máy chủ rails để nhận thấy những thay đổi mặc dù :)
Jezen Thomas

1
Tôi thích giải pháp này, nhưng nó sẽ không hoạt động nếu bạn có thẻ khác bên trong label.
Caio Tarifa

Xin chào @Phobetron, thực sự đây là một giải pháp tốt. Tôi tìm kiếm một cách để thêm lớp đó ở cấp độ phụ huynh thay vì được mô tả ở đây . Tôi lao vào mã đường ray nhưng tôi đã đánh mất chính mình ngay lập tức khi không thể theo dõi quá trình kết xuất với byebug .. Bạn có nghĩ điều này thực sự có thể?
SanjiBukai

20

Các mã bổ sung đang được thêm vào bởi ActionView::Base.field_error_proc. Nếu bạn không sử dụng field_with_errorsđể tạo kiểu cho biểu mẫu của mình, bạn có thể ghi đè lên application.rb:

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

Ngoài ra, bạn có thể thay đổi nó thành một cái gì đó phù hợp với giao diện người dùng của bạn:

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

Điều này đang làm việc tốt cho tôi; dường như là giải pháp thanh lịch nhất để sử dụng với Twitter Bootstrap
Avishai

5

Tôi đang làm việc với Rails 5 và Materialize-Sass và tôi gặp một số vấn đề với hành vi mặc định từ Rails để xử lý các xác nhận trường không thành công như trong hình ảnh bên dưới và đó là do thêm divvào các trường đầu vào khi xác thực không thành công.

nhập mô tả hình ảnh ở đây

Làm việc với câu trả lời @Phobetron và sửa đổi câu trả lời của Hugo Demiglio. Tôi đã thực hiện một số điều chỉnh cho các khối mã đó và tôi nhận được một cái gì đó hoạt động tốt trong các trường hợp sau:

  • Nếu cả hai inputlabelclassthuộc tính riêng của họ ở bất cứ đâu
    • <input type="my-field" class="control">
    • <label class="active" for="...">My field</label>
  • Nếu các thẻ inputhoặc labelkhông có classthuộc tính
    • <input type="my-field">
    • <label for="...">My field</label>
  • nếu labelthẻ có một thẻ khác bên trong vớiclass attribute
    • <label for="..."><i class="icon-name"></i>My field</label>

Trong tất cả các trường hợp đó, errorlớp sẽ được thêm vào các lớp hiện có trong classthuộc tính nếu tồn tại hoặc nó sẽ được tạo nếu nó không có trong nhãn hoặc thẻ đầu vào .

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

Tôi hy vọng nó có thể hữu ích cho những người có cùng điều kiện như tôi.


4

Ngoài câu trả lời @phobetron, không hoạt động khi bạn có thẻ khác với thuộc tính lớp, như <label for="..."><i class="icon my-icon"></i>My field</label>.

Tôi đã làm một số thay đổi về giải pháp của mình:

# 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

Nhưng điều này sẽ không hoạt động nếu trường của bạn không có thuộc tính lớp
mizurnix

2

Nếu vì lý do nào đó bạn vẫn đang làm việc trên Rails 2 (như tôi), hãy xem bài viết SO ở đây .

Nó cung cấp một kịch bản để đưa vào khởi tạo.


2

Một điều cần lưu ý (như tôi đã phát hiện ra trong ngày hôm nay) là nếu bạn thả nổi nhãn hoặc trường nhập (tôi đang thả nổi tất cả các trường đầu vào bên phải), css sẽ bị hỏng ngay cả khi bạn ghi đè ActionView :: Base.field_error_proc.

Một cách khác là giảm một mức sâu hơn trong định dạng CSS như vậy:

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

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

2

Tôi đã thực hiện một tùy chọn để vô hiệu hóa điều khủng khiếp này cho một số đối tượng

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

Vì vậy, có thể sử dụng nó như thế này:

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

1

Đây là giải pháp xây dựng của tôi dựa trên câu trả lời của @ Phobetron. Đặt mã này vào application.rb, các thẻ của bạn <p><span>được tạo bởi các form.error :pcuộc gọi tương ứng sẽ nhận được fields_with_errorsthẻ css. Phần còn lại sẽ nhận được errorlớp 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
}

Tôi thấy cách này linh hoạt nhất và không gây khó chịu trong tất cả các lần trước để định kiểu phản hồi trên các biểu mẫu của tôi.


1

Nếu nó chỉ dành cho mục đích tạo kiểu (bạn không bận tâm div), bạn chỉ có thể thêm phần này vào css của mình:

div.field_with_errors {
 display: inline;
}

Các divsẽ đóng vai trò như một spanvà nó sẽ không can thiệp vào thiết kế của bạn (vì divlà một yếu tố khối - display: block;- theo mặc định, nó sẽ gây ra một dòng mới sau khi nó đóng cửa; spaninline, vì vậy nó không).


1

Nếu bạn chỉ muốn tắt lỗi cho một số thành phần nhất định, ví dụ: hộp kiểm , bạn có thể làm điều này:

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

Nếu chỉ nói về vấn đề kiểu dáng, chúng ta có thể ghi đè lên "field_with_errors". Nhưng vì điều đó có thể ảnh hưởng đến các biểu mẫu khác trong ứng dụng của chúng tôi, tốt hơn hết là ghi đè lớp "field_with_errors" chỉ trong biểu mẫu đó.

Xem xét 'Parent_group' là một trong các lớp cha cho trường lỗi của biểu mẫu (lớp của biểu mẫu hoặc lớp của bất kỳ phần tử cha nào cho trường lỗi), sau đó

  .parent_class .field_with_errors {
    display: inline;
  }

Nó sẽ khắc phục vấn đề cũng như, nó sẽ không làm phiền bất kỳ hình thức nào khác trong ứng dụng của chúng tôi.

HOẶC LÀ

Nếu chúng ta cần ghi đè lên kiểu "field_with_errors" cho toàn bộ applicaiton, thì như @dontangg đã nói,

.field_with_errors { display: inline; } 

sẽ làm việc sửa chữa. Hy vọng nó giúp :)


0

Nếu bạn không muốn thay đổi field_error_proccho toàn bộ ứng dụng của bạn, jQuery Unwrap có thể cung cấp một giải pháp nhắm mục tiêu nhiều hơn cho lĩnh vực vấn đề cụ thể, ví dụ,

$('FORM .field_with_errors > INPUT[type="checkbox"]').unwrap();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.