A Custom Rails FormBuilder for Twitter Bootstrap 3
Include the following module in your helpers directory
# app/helpers/forms_helper.rb
module FormsHelper
# A custom FormBuilder class for Rails forms with Twitter Bootstrap 3
class CustomFormBuilder < ActionView::Helpers::FormBuilder
# ActionView::Helpers::FormHelper and ActionView::Helpers::FormBuilder
# methods each have different args. This hash stores the names of the args
# and the methods that have those args in order to DRY up the method aliases
# defined below.
METHOD_NAMES_FOR_ARG_SETS = {
'method,options={}' => %w{
date_field datetime_field datetime_local_field email_field file_field
hidden_field number_field password_field phone_field range_field
search_field telephone_field text_area text_field time_field url_field
},
# Removing checkboxes for now - the error field causes alignment issues
# "method,options={},checked_val='1',unchecked_val='0'" => %w{ check_box },
"method,collection,value_method,text_method,options={},html_options={},&block" => %w{
collection_check_boxes collection_radio_buttons
},
"method,collection,value_method,text_method,options={},html_options={}" => %w{
collection_select
},
"method,options={},html_options={}" => %w{date_select datetime_select time_select},
"method,tag_value,options={}" => %w{ radio_button },
"method,choices,options={},html_options={}" => %w{ select }
}
for args_set, method_names in METHOD_NAMES_FOR_ARG_SETS
html_class_in = args_set.index("html_options") ? 'html_options' : 'options'
args_without_defaults = args_set.gsub(/\=[^,]+/, '')
for method_name in method_names
method_str = <<-RUBY
def #{method_name}_with_error_field(#{args_set})
update_options_with_form_control_class! #{html_class_in}
content = #{method_name}_without_error_field(#{args_without_defaults})
append_error_field_to_content!(content, method, options)
end
RUBY
# Subtracting 2 from the __LINE__ var for accurate error messages
class_eval(method_str, __FILE__, __LINE__-2)
alias_method_chain method_name, :error_field
end
end
# Creates a div wrapper with class 'form-group'
def group(options ={}, &block)
@template.form_group(options, &block)
end
# Shows the error messages for :base if any
def error_messages
@template.error_messages_for(object)
end
# Creates a div wrapepr with class 'input-group'
def input_group(options={}, &block)
@template.input_group(options, &block)
end
# Creates a fieldset tag
def fieldset(&block)
@template.form_fieldset(&block)
end
# Creates a legend tag
def legend(content, options ={})
@template.form_legend(content, options)
end
# Creates a div with class 'text-error' for displaying error messages
# on specific fields
def error_field(attribute, options = {})
return unless object.errors[attribute]
update_options_with_class!(options, 'text-error')
content = object.errors[attribute].first
@template.content_tag(:div, content, options)
end
# Creates a small tag with class 'help-block' for displaying helpful
# text under a form input
def help_text(method, content, options = {})
return if object.errors[method].any?
update_options_with_class!(options, 'help-block')
@template.content_tag(:small, content, options)
end
# Creates a div wrapper with class 'checkbox'
def checkbox(&block)
@template.form_checkbox(&block)
end
# Creates a submit button with class 'btn btn-primary'
def submit(name, options ={})
update_options_with_class!(options, 'btn btn-primary')
super(name, options) << " " # add a space to the end
end
# Creates a cancel button to go back to a previous page.
def cancel(path, edit_path = nil, options ={})
options[:class] ||= ''
options[:class] << "btn btn-default"
options[:data] = { confirm: "Cancel without saving?"}
if edit_path && object.persisted?
path = edit_path
end
@template.link_to('cancel', path, options)
end
private
# Add the error_field div to each field by default
def append_error_field_to_content!(content, method, options)
unless object.errors[method].any? || options[:error_field] == false
content << error_field(method)
end
content
end
# Update an options hash with class 'form-control'
def update_options_with_form_control_class!(options)
update_options_with_class!(options, 'form-control')
end
# Update an options hash with a :class
def update_options_with_class!(options, klass)
@template.update_options_with_class!(options, klass)
end
end
# Use this instead of form_for
def custom_form(record, options = {}, &block)
options.update(builder: CustomFormBuilder)
form_for(record, options, &block)
end
# Creates a div wrapper with class 'input-group'
def input_group(options={}, &block)
content = capture(&block)
update_options_with_class!(options, 'input-group')
content_tag(:div, content, options)
end
# Creates a div wrapper with class 'form-group'
def form_group(options ={}, &block)
content = capture(&block)
update_options_with_class!(options, 'form-group')
content_tag(:div, content, options)
end
# Creates a fieldset with class from current_namespace (used internally in our project)
def form_fieldset(&block)
klass = "#{current_namespace}-fieldset"
content_tag(:fieldset, capture(&block), class: klass)
end
# Creates a legend tag
def form_legend(content, options ={})
content_tag(:legend, content, options)
end
# Creates a div wrapper with class 'input-group'
def form_input_group(&block)
content_tag(:div, capture(&block), class: "input-group")
end
# Creates a div wrapper with class 'checkbox'
def form_checkbox(&block)
content_tag(:div, capture(&block), class: "checkbox")
end
# Show the error messages on :base for a record
def error_messages_for(object)
if object.errors[:base].any?
message = object.errors[:base].first
%{<div class="text-danger">#{message}</div>}.html_safe
end
end
# Update an options hash with a given :class value
def update_options_with_class!(options, klass)
options[:class] ||= ''
options[:class] << " #{klass}"
options
end
end
This will give you much cleaner, DRYer forms that come pre-equiped with twitter bootstrap
3 classes.
Here's an example:
<%= custom_form(@user) do |f| %>
<%= f.error_messages %>
<%= f.fieldset do %>
<%= f.legend("Your contact info") %>
<%= f.group do %>
<%= f.label :email %>
<%= f.email_field :email %>
<% end %>
<%= f.group do %>
<%= f.label :phone_number %>
<%= f.phone_field :phone_number %>
<% end %>
<%= f.checkbox do %>
<%= f.check_box :may_contact %>
<%= f.label :may_contact, 'You may contact me' %>
<% end %>
<% end %>
<%= f.submit("Save") %>
<!-- Goes to root if it's a new record, otherwise goes to /my_profile -->
<%= f.cancel root_path, my_profile_path %>
<% end %>
Written by Gavin Morrice
Related protips
3 Responses
Hi really helpful, I was thinking the same thing when I found you're code. I've just done a very similar version for Rails 3 and with fields_for.
I've made a gist so we can work on it as we move forward, here's the link :
https://gist.github.com/giacomomacri/7486617
Bye
over 1 year ago
·
Where is the definition for @template.update_options_with_class!
? Google search comes up empty and searching the rails repository on github finds no matches.
Update: Nevermind. I found it at the bottom of this example. Got stuck thinking it was a Rails method.
over 1 year ago
·
@boxofrox :)
over 1 year ago
·
Have a fresh tip? Share with Coderwall community!
Post
Post a tip
Best
#Rails
Authors
Sponsored by #native_company# — Learn More
#native_title#
#native_desc#