Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
510 views
in Technique[技术] by (71.8m points)

ruby - Rails - ActionView::Base.field_error_proc moving up the DOM tree?

Is there anyway to go up the DOM tree from the html_tag element passed in?

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  # implementation
end

Is there anyway I can implement this method to move up the DOM tree and place a class on the parent div?

For example:

<div class="email">
  <label for="user_email">Email Address</label>
  <input id="user_email" name="user[email]" size="30" type="text" value="">
</div>

I would like to place a class on the div.email rather than place something directly on the input/label.

Can this be done with the field_error_proc method or is there a clean alternative?

I want to avoid doing this explicitly in my views on every form field. (like the following)

.email{:class => object.errors[:email].present? 'foo' : nil}
  =form.label :email
  =form.text_field :email

FYI: Short answer to my question is that there is no way to gain access to additional portions of the DOM in the field_error_proc method. This is due to the fact that these methods are not actually building a DOM but instead just concatting a bunch of strings together. For info on some possible work arounds, read the solutions below.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You have two options that I can think of off the top of my head:

Rewrite ActionView::Base.field_error_proc

In one situation I rewrote ActionView::Base.field_error_proc (there is a rails cast on it). Using a little nokogiri, I changed the proc to add the error messages to the input/textarea element's data-error attribute instead of wrapping them in an error div. Then a wrote a little javascript using jquery to wrap all inputs and their labels on document ready. If there was error information associated with the input/textarea, it is transferred to the wrapper div.

I know this solution relies on javascript, and may or may not have a graceful fall back, but it works fine in my situation since it is for a web app rather than a publicly accessible site. I feel okay requiring javascript in that scenario.

# place me inside your base controller class
ActionView::Base.field_error_proc = Proc.new do |html_tag, object|
  html = Nokogiri::HTML::DocumentFragment.parse(html_tag)
  html = html.at_css("input") || html.at_css("textarea")
  unless html.nil?
    css_class = html['class'] || "" 
    html['class'] = css_class.split.push("error").join(' ')
    html['data-error'] = object.error_message.join(". ")
    html_tag = html.to_s.html_safe
  end
  html_tag
end

Write a your own ActionView::Helpers::FormBuilder

You can also get more or less the same effect by overriding the text_field method so that it always returns a wrapped input. Then, using the object variable, access the errors hash and add any needed error information to the wrapper. This one does not require javascript and works nicely, but in the end I prefer the first approach because I find it more flexible. If however, I was working on a publicly accessible site, I would use this approach instead.

Also, FYI, I found it handy to override the check_box and radio_button methods so they always return the input with its associated label. There are lots of fun things you can do with a custom FormBuilder.

# place me inside your projects lib folder
class PrettyFormBuilder < ActionView::Helpers::FormBuilder  
  def check_box(field, label_text, options = {})
    checkbox = super(field, options)
    checkbox += label(field, label_text)
    @template.content_tag(:div, checkbox, :class => "wrapper")
  end
end

The above example shows how to wrap a check_box, but it is more or less the same with the text_field. Like I said, use the object.errors to access the errors hash if needed. This is just the tip of the ice berg... there is a ton you can do with custom FormBuilders.

If you go the custom form builder route, you may find it helpful to modify the above ActionView::Base.field_error_proc as follows so you don't get double wrapped fields when there are errors.

ActionView::Base.field_error_proc = Proc.new do |html_tag, object|
  html_tag # return the html tag unmodified and unwrapped
end

To use the form builder either specify it in the form_for method call or place the following in your application helper:

# application helper
module ApplicationHelper
  ActionView::Base.default_form_builder = PrettyFormBuilder
end

Often my solutions end up using some combination of each to achieve the desired result.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...