How to extend the Rails FormBuilder field

I am using Bootstrap 3 with Rails 4, and I wanted to create a custom FormBuilder to handle some Bootstrap syntax syntax. In particular, I need a custom assistant that would wrap the form-group div around the form field, since Bootstrap applies the error state to this shell, and not to the field itself ...

 <div class="form-group has-error"> <label class="col-md-3 control-label" for="user_email">Email</label> <div class='col-md-9'> <input class="form-control required-input" id="user_email" name="user[email]" placeholder=" peter@example.com " type="email" value=" someone@example.com " /> </div> </div> 

Note the extra has-error class in the outer div ...

Anyway, I wrote this helper and it works great!

 def form_group(method, options={}) class_def = 'form-group' class_def << ' has-error' unless @object.errors[method].blank? class_def << " #{options[:class]}" if options[:class].present? options[:class] = class_def @template.content_tag(:div, options) { yield } end # Here a HAML sample... = f.form_group :email do = f.label :email, nil, class: 'col-md-3 control-label' .col-md-9 = f.email_field :email, class: 'form-control required-input', placeholder: t('sample.email') 

Now I want to use Bootstrap form help text to display error messages. This requires me to extend the Rails native helpers (for example, text_field in the above example), and then call them in the f.form_group block.

The solution seemed simple enough: call the parent and add my span block to the end ...

 def text_field(method, options={}) @template.text_field(method, options) if !@object.errors [method].blank? @template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') end end 

Only it does not output HTML, the div will just display empty. I tried a bunch of diff syntax approaches:

  • super vs text_field vs text_field_tag
  • concat - result - @template.concat(@template.content_tag( [...] ))
  • dynamic vars, for example. def text_field(method, *args) and then options = args.extract_options!.symbolize_keys!

I ever get weird syntax errors or empty div . In some cases, the input field will appear, but the span help text will not, or vice versa.

I'm sure I'm pretending to be something simple, I just don't see it.

+6
source share
1 answer

Took a few days, but I ended up stumbling on the correct syntax. I hope this saves someone else's sanity!

Ruby return automagic, combined with the complex Rails scope at different times, made me disconnect. In particular, @template.text_field draws content, but it must be returned by a helper method in order to appear inside the calling block. However, we must return the results of two calls ...

 def text_field(method, options={}) field_errors = object.errors[method].join(', ') if !@object.errors [method].blank? content = super content << (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors) return content end 

We must return the results of both the parent method (via super ) and our custom @template.content_tag(:span, injection. We can shorten this a bit using the Ruby plus + operator, which combines the return results.

 def text_field(method, options={}) field_errors = object.errors[method].join(', ') if !@object.errors [method].blank? super + (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors) end 

Note. the form was initiated by an ActiveModel, so we have access to @object . Implementing form_for without linking it to the model will require you instead of text_field_tag .

Here is my finished custom FormBuilder

 class BootstrapFormBuilder < ActionView::Helpers::FormBuilder def form_group(method, options={}) class_def = 'form-group' class_def << ' has-error' unless @object.errors[method].blank? class_def << " #{options[:class]}" if options[:class].present? options[:class] = class_def @template.content_tag(:div, options) { yield } end def text_field(method, options={}) field_errors = object.errors[method].join(', ') if !@object.errors [method].blank? super + (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors) end end 

Remember to say form_for !

 form_for(:user, :builder => BootstrapFormBuilder [, ...]) 

Edit: Here are some useful links that have helped me on my path to enlightenment. Link juice for authors!

+9
source

All Articles