1

I have the following in an initializer in a rails app that uses Twitter bootstrap so that it removes the div.field_with_errors that rails applies when validation fails on a field but also the initializer adds the help/validation text after the erroneous input field:

require 'nokogiri'
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  html = %(<div class="field_with_errors">#{html_tag}</div>).html_safe

  form_fields = [
    'textarea',
    'input',
    'select'
  ]

  elements = Nokogiri::HTML::DocumentFragment.parse(html_tag).css("label, " + form_fields.join(', '))

  elements.each do |e|
    if e.node_name.eql? 'label'
      html = %(#{e}).html_safe
    elsif form_fields.include? e.node_name
      if instance.error_message.kind_of?(Array)
        html = %(#{e}<span class="help-inline">&nbsp;#{instance.error_message.join(',')}</span>).html_safe
      else
        html = %(#{e}<span class="help-inline">&nbsp;#{instance.error_message}</span>).html_safe
      end
    end
  end
  html
end

This works fine but I also need to apply the .error class to the surrounding div.control-group for each error.

My initializer currently gives the following output:

<div class="control-group">
    <label class="control-label" for="post_message">Message</label>
    <div class="controls">
        <input id="post_message" name="post[message]" required="required" size="30" type="text" value="" /><span class="help-inline">&nbsp;can't be blank</span>
    </div>
</div>

but I need something adding to my initializer so that it adds the .error class to the div.control-group like so:

<div class="control-group error">
    <label class="control-label" for="post_message">Message</label>
    <div class="controls">
        <input id="post_message" name="post[message]" required="required" size="30" type="text" value="" /><span class="help-inline">&nbsp;can't be blank</span>
    </div>
</div>

The solution will probably need to allow for the fact that each validation error could have more than one label and input that are all within the same div.control-group (eg radio buttons / checkboxes / 2 text fields side by side).

I assume it needs some sort of e.at_xpath() to find the div.control-group parent and add the .error class to it but I'm not sure how to do this.

Can anyone help?

PS This may all be possible using the formtastic or simple_form gems but I'd rather just use my own html if possible.


EDIT

If I put e['class'] = 'foo' in the if e.node_name.eql? 'label' section then it applies the class to the label so I think I just need to find the parent tag of e and then apply an .error class to it but I can't figure out what the xpath would be to get from label to its div.control-group parent; no combination of dots, slashes or whatever seems to work but xpath isn't my strong point.

user1116573
  • 2,817
  • 4
  • 17
  • 27

3 Answers3

0

Not best the solution but a valid workaround as i'm more html/js guy than rails

-> use jQuery selectors & addClass http://api.jquery.com/contains-selector/ http://api.jquery.com/addClass/

$("div.control-group:contains('can\\'t be blank')").addClass("error");
maxmax
  • 778
  • 4
  • 10
  • Thanks but I'm trying to update the html being output for when js in use by user or when user submits form and it misses something in my js validation but is caught by my server-side validation. – user1116573 Jun 01 '12 at 22:46
0

I've managed it now.

According to this SO answer it isn't possible: Rails - ActionView::Base.field_error_proc moving up the DOM tree?

So, here's what I did:

  1. Remove the default rails error handling from label and inputs
  2. Wrap each erroneous label and form field with span.field_with_errors
  3. .field_with_errors then extends the .control-group.error css using SASS

config/initializers/bootstrap.rb

require 'nokogiri'

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
  html = %(<div class="field_with_errors">#{html_tag}</div>).html_safe

  form_fields = [
    'textarea',
    'input',
    'select'
  ]

  elements = Nokogiri::HTML::DocumentFragment.parse(html_tag).css("label, " + form_fields.join(', '))

  elements.each do |e|
    if e.node_name.eql? 'label'
      # wrap erroneous label
      html = %(<span class='field_with_errors'>#{e}</span>).html_safe
    elsif form_fields.include? e.node_name
      # wrap erroneous field
      if instance.error_message.kind_of?(Array)
        html = %(<span class='field_with_errors'>#{e}<span class="help-inline">&nbsp;#{instance.error_message.join(',')}</span></span>).html_safe
      else
        html = %(<span class='field_with_errors'>#{e}<span class="help-inline">&nbsp;#{instance.error_message}</span></span>).html_safe
      end
    end
  end
  html
end

assets/stylesheets/application.css.scss

.field_with_errors {
  @extend .control-group.error;
}

Hope that helps someone else.

Community
  • 1
  • 1
user1116573
  • 2,817
  • 4
  • 17
  • 27
0

If you don't need to display the error messages next to the erroneous field then you can simply add the following to assets/stylesheets/application.css.scss:

.field_with_errors {
  @extend .control-group.error;
  display: inline;
}
user1116573
  • 2,817
  • 4
  • 17
  • 27