I am using Bootstrap 3 with Rails 4, and I wanted to create a custom FormBuilder to handle some of Bootstrap's unique HTML syntax. Specifically, I needed a custom helper that would create the form-group
div wrapper around a form field, since Bootstrap applies error state to this wrapper, and not 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="[email protected]" type="email" value="[email protected]" />
</div>
</div>
Note the extra class has-error
in the outer div...
Anyway, I wrote that 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's 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 utilize Bootstrap's form help text in order to display error messages. This requires me to extend Rails native helpers (such as text_field
in the example above) and then call them within the the block of f.form_group
.
The solution seemed simple enough: call the parent, and append my span block onto the end...
def text_field(method, options={})
@template.text_field(method, options)
if [email protected][method].blank?
@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block')
end
end
Only it wouldn't output any HTML, the div would simply show up empty. I've tried a bunch of diff syntax approaches:
super
vs text_field
vs text_field_tag
concat
-ing the results -- @template.concat(@template.content_tag( [...] ))
def text_field(method, *args)
and then options = args.extract_options!.symbolize_keys!
I only ever get weird syntax errors, or an empty div
. In some instances, the input
field would appear, but the help text span
wouldn't, or vice verse.
I'm sure I'm screwing up something simple, I just don't see it.
Took a few days, but I ultimately stumbled onto the proper syntax. Hopefully it saves someone else's sanity!
Ruby's return
automagic, combined with Rails at-times complex scoping, had me off kilter. Specifically, @template.text_field
draws the content, but it must be returned by the helper method in order to appear inside the calling block. However we have to return the results of two calls...
def text_field(method, options={})
field_errors = object.errors[method].join(', ') if [email protected][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
) plus our custom @template.content_tag(:span,
injection. We can shorten this up a bit using Ruby's plus +
operator, which concatenates return results.
def text_field(method, options={})
field_errors = object.errors[method].join(', ') if [email protected][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 with an ActiveModel object, which is why we have access to @object
. Implementing form_for
without associating it with a model would require you to extend text_field_tag
instead.
Here's my completed 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 [email protected][method].blank?
super + (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors)
end
end
Don't forget to tell form_for
!
form_for(:user, :builder => BootstrapFormBuilder [, ...])
Edit: Here's a number of useful links that helped me along the road to enlightenment. Link-juice kudos to the authors!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With