Skip to content

Instantly share code, notes, and snippets.

@mattslay
Forked from allard/bootstrap.rb
Created July 30, 2012 13:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattslay/3206827 to your computer and use it in GitHub Desktop.
Save mattslay/3206827 to your computer and use it in GitHub Desktop.
Form builder for Twitter Bootstrap v2 form elements
# This file goes in config/initializers
require 'bootstrap_form_builder'
# Make this the default Form Builder. You can delete this if you don't want form_for to use
# the bootstrap form builder by default
ActionView::Base.default_form_builder = BootstrapFormBuilder::FormBuilder
# Add in our FormHelper methods, so you can use bootstrap_form_for.
ActionView::Base.send :include, BootstrapFormBuilder::FormHelper
### Only use one of these error handling methods ###
# Get rid of the rails error handling completely.
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance_tag|
"#{html_tag}".html_safe
end
# Only remove the default rails error handling on input and label
# Relies on the Nokogiri gem.
# Credit to https://github.com/ripuk
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
html = %(<div class="field_with_errors">#{html_tag}</div>).html_safe
elements = Nokogiri::HTML::DocumentFragment.parse(html_tag).css "label, input"
elements.each do |e|
if e.node_name.eql? 'label'
html = %(#{e}).html_safe
elsif e.node_name.eql? 'input'
html = %(#{e}).html_safe
end
end
html
end
# This file goes in lib/
#
# Original gist by Jamie Penny: https://gist.github.com/1829710
# Fork by allard: https://gist.github.com/2670024
# Fork of allard's version by Matt Slay: https://gist.github.com/3204634
#
# Changes:
#----------------------------------------------------------------------------
# 2012-06-05 allard: Added inline help messages, set default check_box label if none is set
#
# 2012-07-29 MattSlay: Added 'collection_select' to the basic_helpers list.
# For default label text on checkboxes, underscore chars are converted to spaces.
# Refactored repetitive code from basic_helpers and multipart_helpers
# into control_opening_html() and control_closing_html().
#----------------------------------------------------------------------------
# Usage (ERB):
# <%= bootstrap_form_for @calendar_entry do |f| %>
# <%= content_tag :legend, (locals[:title] || 'Edit Calendar Entry') %>
# <%= f.text_field :name, :class => 'span3' %>
# <%= f.text_area :description, :class => 'span3' %>
# <%= f.jquery_datetime_select :start_time, :class => 'span3' %>
# <%= f.jquery_datetime_select :end_time, :class => 'span3' %>
# <%= f.check_box :all_day, { :label => 'Agree?, :description => 'left hand label'' } %>
# <%= f.text_field :tag_string, :label_options => { :text => 'Tags' }, :class => 'span3' do %>
# <p class="help-block">Use commas to separate tags.</p>
# <% end %>
# <div class="form-actions">
# <%= f.submit 'Save', :class => 'btn btn-primary' %>
# <%= link_to 'Cancel', calendar_entries_path, :class => 'btn' %>
# </div>
# <% end %>
#
# Inline help:
# <%= f.text_field :name, :help => "inline help message"
#
# collection_select example:
# <%= f.collection_select :church_office_id, ChurchOffice.all(), :id, :name %>
#
#
#----------------------------------------------------------------------------
#Usage (HAML):
#
#= bootstrap_form_for @calendar_entry do |f|
# %fieldset
# %legend= locals[:title] || 'Edit Calendar Entry'
# = f.text_field :name, :class => 'span3'
# = f.text_area :description, :class => 'span3'
# = f.jquery_datetime_select :start_time, :class => 'span3'
# = f.jquery_datetime_select :end_time, :class => 'span3'
# = f.check_box :all_day
# = f.text_field :tag_string, :label => 'Tags', :class => 'span3' do
# %p.help-block
# Use commas to separate tags.</p>
#
# .form-actions
# = f.submit 'Save', :class => 'btn btn-primary'
# = link_to 'Cancel', calendar_entries_path, :class => 'btn'
#
# collection_select example:
# = f.collection_select :church_office_id, ChurchOffice.all(), :id, :name
#------------------------------------------------------------------------------
module BootstrapFormBuilder
module FormHelper
[:form_for, :fields_for].each do |method|
module_eval do
define_method "bootstrap_#{method}" do |record, *args, &block|
# add the TwitterBootstrap builder to the options
options = args.extract_options!
options[:builder] = BootstrapFormBuilder::FormBuilder
if method == :form_for
options[:html] ||= {}
options[:html][:class] ||= 'form-horizontal'
end
# call the original method with our overridden options
send method, record, *(args << options), &block
end
end
end
end
class FormBuilder < ActionView::Helpers::FormBuilder
include FormHelper
include ActionView::Helpers::TagHelper
def get_error_text(object, field, options)
if object.nil? || options[:hide_errors]
""
else
errors = object.errors[field.to_sym]
if errors.empty? then "" else errors.first end
end
end
def get_object_id(field, options)
object = @template.instance_variable_get("@#{@object_name}")
return options[:id] || object.class.name.underscore + '_' + field.to_s
end
def get_label(field, options)
labelOptions = {:class => 'control-label'}.merge(options[:label_options] || {})
text = options[:label] || labelOptions[:text] || nil
options.delete(:label)
options.delete(:label_options)
labelTag = label(field, text, labelOptions)
end
def inline_help(options)
return "" unless options[:help]
text = options[:help]
options.delete(:help)
content_tag :span, text, :class => 'help-inline'
end
def submit(value, options = {}, *args)
super(value, {:class => "btn btn-primary"}.merge(options), *args)
end
def jquery_date_select(field, options = {})
id = get_object_id(field, options)
date =
if options['start_date']
options['start_date']
elsif object.nil?
Date.now
else
object.send(field.to_sym)
end
date_picker_script = "<script type='text/javascript'>" +
"$( function() { " +
"$('##{id}')" +
".datepicker( $.datepicker.regional[ 'en-NZ' ] )" +
".datepicker( 'setDate', new Date('#{date}') ); } );" +
"</script>"
return basic_date_select(field, options.merge(javascript: date_picker_script))
end
def basic_date_select(field, options = {})
placeholder_text = options[:placeholder_text] || ''
id = get_object_id(field, options)
errorText = get_error_text(object, field, options)
wrapperClass = 'control-group' + (errorText.empty? ? '' : ' error')
errorSpan = if errorText.empty? then "" else "<span class='help-inline'>#{errorText}</span>" end
labelTag = get_label(field, options)
date =
if options[:start_date]
options[:start_date]
elsif object.nil?
Date.now.utc
else
object.send(field.to_sym)
end
javascript = options[:javascript] ||
"
<script>
$(function() {
var el = $('##{id}');
var currentValue = el.val();
if(currentValue.trim() == '') return;
el.val(new Date(currentValue).toString('dd MMM, yyyy'));
});
</script>"
("<div class='#{wrapperClass}'>" +
labelTag +
"<div class='controls'>" +
super_text_field(field, {
:id => id, :placeholder => placeholder_text, :value => date.to_s,
:class => options[:class]
}.merge(options[:text_field] || {})) +
errorSpan +
javascript +
"</div>" +
"</div>").html_safe
end
def jquery_datetime_select(field, options = {})
id = get_object_id(field, options)
date_time =
if options['start_time']
options['start_time']
elsif object.nil?
DateTime.now.utc
else
object.send(field.to_sym)
end
datetime_picker_script = "<script type='text/javascript'>" +
"$( function() { " +
"$('##{id}')" +
".datetimepicker( $.datepicker.regional[ 'en-NZ' ] )" +
".datetimepicker( 'setDate', new Date('#{date_time}') ); } );" +
"</script>"
return basic_datetime_select(field, options.merge(javascript: datetime_picker_script))
end
def basic_datetime_select(field, options = {})
placeholder_text = options[:placeholder_text] || ''
id = get_object_id(field, options)
errorText = get_error_text(object, field, options)
wrapperClass = 'control-group' + (errorText.empty? ? '' : ' error')
errorSpan = if errorText.empty? then "" else "<span class='help-inline'>#{errorText}</span>" end
labelTag = get_label(field, options)
date_time =
if options[:start_time]
options[:start_time]
elsif object.nil?
DateTime.now.utc
else
object.send(field.to_sym)
end
javascript = options[:javascript] ||
"
<script>
$(function() {
var el = $('##{id}');
var currentValue = el.val();
if(currentValue.trim() == '') return;
el.val(new Date(currentValue).toString('dd MMM, yyyy HH:mm'));
});
</script>"
("<div class='#{wrapperClass}'>" +
labelTag +
"<div class='controls'>" +
super_text_field(field, {
:id => id, :placeholder => placeholder_text, :value => date_time.to_s,
:class => options[:class]
}.merge(options[:text_field] || {})) +
errorSpan +
javascript +
"</div>" +
"</div>").html_safe
end
def control_opening_html
labelTag = get_label(@field, @options)
errorText = get_error_text(@object, @field, @options)
wrapperClass = 'control-group' + (errorText.empty? ? '' : ' error')
#-- Build up the opening html to wrap everything in Twitter control Bootstap markup
("<div class='#{wrapperClass}'>" +
labelTag +
"<div class='controls'>")
end
def control_closing_html(help_block)
errorText = get_error_text(@object, @field, @options)
errorSpan = if errorText.empty? then "" else "<span class='help-inline'>#{errorText}</span>" end
#-- Build up the return string
( inline_help(@options) +
errorSpan +
(help_block ? @template.capture(&help_block) : "") +
"</div>" +
"</div>")
end
basic_helpers = %w{text_field text_area select email_field password_field number_field collection_select}
multipart_helpers = %w{date_select datetime_select}
trailing_label_helpers = %w{check_box}
basic_helpers.each do |name|
define_method(name) do |field, *args, &help_block|
@field = field
@options = args.last.is_a?(Hash) ? args.last : {}
@object = @template.instance_variable_get("@#{@object_name}")
( control_opening_html() +
super(field, *args) +
control_closing_html(help_block)
).html_safe
end
end
multipart_helpers.each do |name|
define_method(name) do |field, *args, &help_block|
@field = field
@options = args.last.is_a?(Hash) ? args.last : {}
@object = @template.instance_variable_get("@#{@object_name}")
@options[:class] = 'inline ' + options[:class] if options[:class]
( control_opening_html() +
super(field, *args) +
control_closing_html()
).html_safe
end
end
trailing_label_helpers.each do |name|
# First alias old method
class_eval("alias super_#{name.to_s} #{name}")
define_method(name) do |field, *args, &help_block|
options = args.first.is_a?(Hash) ? args.first : {}
object = @template.instance_variable_get("@#{@object_name}")
labelOptions = {:class => 'checkbox'}.merge(options[:label_options] || {})
labelOptions["for"] = "#{@object_name}_#{field}"
label_text = options[:label] || labelOptions[:text] || field.to_s.capitalize.gsub('_', ' ')
options.delete(:label)
options.delete(:label_options)
errorText = get_error_text(object, field, options)
wrapperClass = 'control-group' + (errorText.empty? ? '' : ' error')
errorSpan = if errorText.empty? then "" else "<span class='help-inline'>#{errorText}</span>" end
description = if options[:description] then %{<label class="control-label">#{options[:description]}</label>} else "" end
options.delete(:description)
("<div class='#{wrapperClass}'>" +
description +
"<div class='controls'>" +
tag(:label, labelOptions, true) +
super(field, *args) +
inline_help(options) +
errorSpan +
(help_block ? @template.capture(&help_block) : "") +
label_text +
"</label>" +
"</div>" +
"</div>"
).html_safe
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment