-
-
Save allard/2670024 to your computer and use it in GitHub Desktop.
Form builder for Twitter Bootstrap v2 form elements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Changes: | |
# 2012-06-05 allard: Added inline help messages, set default check_box label if none is set | |
# | |
# This file goes in lib/ | |
# Usage: | |
# <%= 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" | |
# | |
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 | |
basic_helpers = %w{text_field text_area select email_field password_field number_field} | |
multipart_helpers = %w{date_select datetime_select} | |
trailing_label_helpers = %w{check_box} | |
basic_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.last.is_a?(Hash) ? args.last : {} | |
object = @template.instance_variable_get("@#{@object_name}") | |
labelTag = get_label(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 | |
("<div class='#{wrapperClass}'>" + | |
labelTag + | |
"<div class='controls'>" + | |
super(field, *args) + | |
inline_help(options) + | |
errorSpan + | |
(help_block ? @template.capture(&help_block) : "") + | |
"</div>" + | |
"</div>" | |
).html_safe | |
end | |
end | |
multipart_helpers.each do |name| | |
define_method(name) do |field, *args, &help_block| | |
options = args.last.is_a?(Hash) ? args.last : {} | |
object = @template.instance_variable_get("@#{@object_name}") | |
labelTag = get_label(field, options) | |
options[:class] = 'inline ' + options[:class] if options[:class] | |
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 | |
("<div class='#{wrapperClass}'>" + | |
labelTag + | |
"<div class='controls'>" + | |
super(field, *args) + | |
inline_help(options) + | |
errorSpan + | |
(help_block ? @template.capture(&help_block) : "") + | |
"</div>" + | |
"</div>" | |
).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 | |
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 |
mattslay
commented
Jul 28, 2012
via email
Fork it? Really?
It seems like this form helper tool has been forked several time already. To me, when I see a tool with so many forks, I never know which forked version to choose, or which one is from a dev who is gonna keep the thing evolving. It seems to add too many choices floating around.
Am I looking at this in the wrong way?
…On Jul 28, 2012, at 4:56 PM, allard ***@***.*** wrote:
A combination of .gsub and .titleize will likely do the trick for you. Looking forward to seeing your forked gist with this update.
On 29/07/2012, at 5:44 , Matt Slay ***@***.*** wrote:
> I like your change to "set default check_box label if none is set", However, I wish you would make it convert any underscore characters in the field name to a space.
>
> I have fields named like "receive_letter", and the label is rendered as "Receive_letter" and I'd rather see "Receive letter" (note that underscore is converted to a space. It looks better.
> ---
>
> Reply to this email directly or view it on GitHub:
> https://gist.github.com/2670024
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/2670024
Everyone has their own pet peeves. Mine was the check box label and why I added that to the existing gist. Everyone wins when everyone shares what they've added. At the moment the gist does what I want it to do for me and I'm not planning on spending more time on it until I have another need. The way for you to share your additions is to fork it. That's just the way it goes until someone takes ownership, creates a proper plugin and can verify pull requests when they come and so on. I'm happy to contribute my changes but I won't take ownership, create a plugin or spend time validating others changes.
…On 29/07/2012, at 8:41 , Matt Slay ***@***.*** wrote:
Fork it? Really?
It seems like this form helper tool has been forked several time already. To me, when I see a tool with so many forks, I never know which forked version to choose, or which one is from a dev who is gonna keep the thing evolving. It seems to add too many choices floating around.
Am I looking at this in the wrong way?
On Jul 28, 2012, at 4:56 PM, allard ***@***.*** wrote:
> A combination of .gsub and .titleize will likely do the trick for you. Looking forward to seeing your forked gist with this update.
>
> On 29/07/2012, at 5:44 , Matt Slay ***@***.*** wrote:
>
> > I like your change to "set default check_box label if none is set", However, I wish you would make it convert any underscore characters in the field name to a space.
> >
> > I have fields named like "receive_letter", and the label is rendered as "Receive_letter" and I'd rather see "Receive letter" (note that underscore is converted to a space. It looks better.
> > ---
> >
> > Reply to this email directly or view it on GitHub:
> > https://gist.github.com/2670024
> ---
>
> Reply to this email directly or view it on GitHub:
> https://gist.github.com/2670024
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/2670024
OK. I get what you are saying. I'm still kind of new to the Ruby/Rails world, and the use and sharing of code through github. So I've still got some paradigm shifts to make.
So, I'll fork it, and (this next step is important, and you did a fine job of it): DOCUMENT what I added.
Thanks.
I'm also working on the code right now to handle the collection_select helper. I think I see what is needed to make it. This enhancement will indeed merit creating fork, for sure.
…On Jul 28, 2012, at 5:57 PM, allard ***@***.*** wrote:
Everyone has their own pet peeves. Mine was the check box label and why I added that to the existing gist. Everyone wins when everyone shares what they've added. At the moment the gist does what I want it to do for me and I'm not planning on spending more time on it until I have another need. The way for you to share your additions is to fork it. That's just the way it goes until someone takes ownership, creates a proper plugin and can verify pull requests when they come and so on. I'm happy to contribute my changes but I won't take ownership, create a plugin or spend time validating others changes.
On 29/07/2012, at 8:41 , Matt Slay ***@***.*** wrote:
> Fork it? Really?
>
> It seems like this form helper tool has been forked several time already. To me, when I see a tool with so many forks, I never know which forked version to choose, or which one is from a dev who is gonna keep the thing evolving. It seems to add too many choices floating around.
>
> Am I looking at this in the wrong way?
>
> On Jul 28, 2012, at 4:56 PM, allard ***@***.*** wrote:
>
> > A combination of .gsub and .titleize will likely do the trick for you. Looking forward to seeing your forked gist with this update.
> >
> > On 29/07/2012, at 5:44 , Matt Slay ***@***.*** wrote:
> >
> > > I like your change to "set default check_box label if none is set", However, I wish you would make it convert any underscore characters in the field name to a space.
> > >
> > > I have fields named like "receive_letter", and the label is rendered as "Receive_letter" and I'd rather see "Receive letter" (note that underscore is converted to a space. It looks better.
> > > ---
> > >
> > > Reply to this email directly or view it on GitHub:
> > > https://gist.github.com/2670024
> > ---
> >
> > Reply to this email directly or view it on GitHub:
> > https://gist.github.com/2670024
> ---
>
> Reply to this email directly or view it on GitHub:
> https://gist.github.com/2670024
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/2670024
allard - I made a fork, and documented my changes at the of the file.
The main thing I accomplished is that it now works with f.collection_select(). I also refactored the bootstrap html wrapping out to some new handler methods, because it was beginning to have some redundant code now that more controls are being supported.
See my fork here: https://gist.github.com/3206827
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment