when I use the Form Builder in form_for to generate a button (with f.button) it generates a button with the title " ". What I want to do is add a prefix to this title: "MyPrefix ".
So basically all I need to do is overwrite the button method (https://github.com/rails/rails/blob/488699166c3558963fa82d4689a35f8c3fd93f47/actionpack/lib/action_view/helpers/form_helper.rb#L1402). But how do I do that?
Thanks!
You can do this with a Custom Form Builder (search for "customized form builders" on that page).
The code would be something roughly like the following (you'll have to decide where exactly the prefix should go):
class PrefixedFormBuilder < ActionView::Helpers::FormBuilder
def button(value=nil)
super(value, :title => 'MyPrefix')
end
end
with a custom helper in ApplicationHelper:
def prefixed_form_for(record, *args, &block)
options = args.extract_options!
options.merge!(:builder => PrefixedFormBuilder)
form_for(record, *(args + [options]), &block)
end
and then using:
prefixed_form_for(#model) do |f|
in your views.
Related
I'm converting my site to Twitter Bootstrap 3, and have run into what seems like silly problem, but I haven't been able to find an easy solution via google.
How do I get class="form-control" to be populated by default in the Rails Form Helper? I can only do it by typing it explicitly, this seems like a waste of time. (below)
It is required for bootstrap to style the input.
<%= f.label :email %>
<%= f.text_field :email, class: "form-control" %>
Am I naive to think that Rails should add this feature just because bootstrap implemented it?
Yup, this can be done without changing the way you use the Rails form helpers. You can extend the form helpers to include the class name if it is not already included in the options.
Note: You will have to override each method in FormTagHelper that you want to augment. This only augments text_field_tag.
Add something like this to your ApplicationHelper:
module ApplicationHelper
module BootstrapExtension
FORM_CONTROL_CLASS = "form-control"
# Override the 'text_field_tag' method defined in FormTagHelper[1]
#
# [1] https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/form_tag_helper.rb
def text_field_tag(name, value = nil, options = {})
class_name = options[:class]
if class_name.nil?
# Add 'form-control' as the only class if no class was provided
options[:class] = FORM_CONTROL_CLASS
else
# Add ' form-control' to the class if it doesn't already exist
options[:class] << " #{FORM_CONTROL_CLASS}" if
" #{class_name} ".index(" #{FORM_CONTROL_CLASS} ").nil?
end
# Call the original 'text_field_tag' method to do the real work
super
end
end
# Add the modified method to ApplicationHelper
include BootstrapExtension
end
To get the class added to all form elements, even if those form elements are generated by gems like simple_form, the modification has to be done on a higher-level class than the ApplicationController. The following snippet can be placed in an initializer to do just that:
require 'action_view/helpers/tags/base'
# Most input types need the form-control class on them. This is the easiest way to get that into every form input
module BootstrapTag
FORM_CONTROL_CLASS = 'form-control'
def tag(name, options, *)
options = add_bootstrap_class_to_options options, true if name.to_s == 'input'
super
end
private
def content_tag_string(name, content, options, *)
options = add_bootstrap_class_to_options options if name.to_s.in? %w(select textarea)
super
end
def add_bootstrap_class_to_options(options, check_type = false)
options = {} if options.nil?
options.stringify_keys!
if !check_type || options['type'].to_s.in?(%w(text password number email))
options['class'] = [] unless options.has_key? 'class'
options['class'] << FORM_CONTROL_CLASS if options['class'].is_a?(Array) && !options['class'].include?(FORM_CONTROL_CLASS)
options['class'] << " #{FORM_CONTROL_CLASS}" if options['class'].is_a?(String) && options['class'] !~ /\b#{FORM_CONTROL_CLASS}\b/
end
options
end
end
ActionView::Helpers::Tags::Base.send :include, BootstrapTag
ActionView::Base.send :include, BootstrapTag
Yes, it's a waste of time.
Use simple_form gem which integrate nicely with Bootstrap. You no longer need to write these.
After bundle, just run
rails generate simple_form:install --bootstrap
Then a simple_form initailizer will be added. You can further customize it in initializers/simple_form_bootstrap, though default is good enough.
All these helper classed will be generated automatically, as well as many other good stuff.
You can use one of the Bootstrap related Gems such as this one:
https://github.com/stouset/twitter_bootstrap_form_for
or this one:
https://github.com/sethvargo/bootstrap_forms
The previous two answers will indeed work very well (simple form using your own initializer or use the bootstrap form gems). As with anything in code, there are many ways to skin a cat. Another (more manual) way is to add a form helper of your own. The steps are basically:
Create a helper file such as app/helpers/custom_form_helper.rb
Inherit from the form builder class: CustomFormBuilder < ActionView::Helpers::FormBuilder
Create the look you want.
def text_field(label, *args)
options = args.extract_options!
new_class = options[:class] || "form-control"
super("dd", label, *(args << options.merge(:class => new_class)))
end
Call your helper method in application so you don not have to include the helper each time you call a form, like:
def custom_form_for(name, *args, &block)
options = args.extract_options!
content_tag("div",
content_tag("dl", form_for(name, *(args << options.merge(:builder => CustomFormBuilder)), &block)), :class => "standard_form")
end
Use the custom form in your forms as custom_form_for
Viget Labs posted an article and gist detailing a rails helper method for adding a particular class (like .selected or .active) to a navigation link if it's url matches the current path.
You can use it in your layout like so:
= nav_link "News", articles_path, {class: "btn btn-small"}
<!-- which creates the following html -->
News
Nice. I'm using bootstrap, and want to have an icon in my button, so I need to generate the following html:
<i class="icon-home"> </i> News
I forked the gist and figured out a simple way of doing it. My fork lets the developer pass :inner_html and :inner_class to the helper like so:
= nav_link "News", articles_path, {class: "btn btn-small"}, {inner_html: 'i', inner_class: 'icon-home'}
It works fine, but I don't like my underlying implementation:
def link
if #options[:inner_html]
link_to(#path, html_options) do
content_tag(#options[:inner_html], '', :class => #options[:inner_class]) + " #{#title}"
end
else
link_to(#title, #path, html_options)
end
end
As you can see, I'm passing the new options to content_tag inside the block of a link_to method. I was hoping I would be able to refactor it in a few ways.
First of all, I'd prefer to be able to do this in my view:
= nav_link "News", articles_path, {class: "btn btn-small"} do
%i.icon-home
I want to give the inner html as a block, and not as attributes of the option hash. Can anyone give me any pointers on how to achieve this?
I thought it would a simple case of telling the nav_link method to accept a block:
def nav_link(title, path, html_options = {}, options = {}, &block)
LinkGenerator.new(request, title, path, html_options, options, &block).to_html
end
class LinkGenerator
include ActionView::Helpers::UrlHelper
include ActionView::Context
def initialize(request, title, path, html_options = {}, options = {}, &block)
#request = request
#title = title
#path = path
#html_options = html_options
#options = options
#block = block
end
def link
if #block.present?
link_to #path, html_options do
#block.call
#title
end
end
end
But this fails to output the icon, and instead inserts a number (4). I don't get it clearly. Anyone got any advice. Where can I go to read more about this sort of thing, as I really want to be able to figure stuff like this out without having to ask on stackoverflow.
I tried your problem and the following worked for me perfectly, in the helper:
def my_link(title, path, &block)
if block_given?
link_to path do
block.call
concat(title)
end
else
link_to title, path
end
end
Usage:
my_link "No title", User.first do
%i.icon-home
The solution in the end was as follows:
# capture the output of the block, early on if block_given?
def nav_link(title, path, html_options = {}, options = {}, &block)
LinkGenerator.new(request, title, path, html_options, options, (capture(&block) if block_given?)).to_html
end
I also had to modify my link method:
def link
if #block.present?
link_to(#path, html_options) do
#block.concat(#title)
end
else
link_to(#title, #path, html_options)
end
end
I've updated my gist. You could probably hack it up to accept more complex blocks.
Is there a way that I can create a custom form helper so that instead of:
special_field_tag :object, :method
I can achieve something like:
form.special_field :method
Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:
class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper
# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = #object.nil? ? 0 : #object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end
def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return #object_name + output + "[#{label}]"
end
def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return #object_name + output + "_#{label}"
end
end
Which you can use like this:
<% form_for #quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>
There are some instance variables created by Rails that you can access in these helper methods:
#object - the model object specified by the form
#object_name - the class name of the object
#template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
#options - options passed to the FormBuilder when its created by the form_for call
I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.
The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.
This file should be added in the app/helpers folder
#Tilendor, thanks so much for the pointers. Here is an example of an enum_select form tag helper that uses Rails 4.1 enums to automatically populate the options of a select tag:
# helpers/application_helper.rb
module ApplicationHelper
class ActionView::Helpers::FormBuilder
# http://stackoverflow.com/a/2625727/1935918
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
def enum_select(name, options = {})
# select_tag "company[time_zone]", options_for_select(Company.time_zones
# .map { |value| [value[0].titleize, value[0]] }, selected: company.time_zone)
select_tag #object_name + "[#{name}]", options_for_select(#object.class.send(name.to_s.pluralize)
.map { |value| [value[0].titleize, value[0]] }, selected: #object.send(name))
end
end
end
The trickiest construct is #object.class.send(name.to_s.pluralize) which produces a hash of available values (e.g., Company.time_zones). Putting it in helpers/application_helper.rb makes it automatically available. It is used like:
<%= f.label :billing_status %>:
<%= f.enum_select :billing_status %><br />
Our app was displaying phone numbers in text fields, and we wanted to omit the country code for domestic numbers. I was using form helpers. After reading this and rails source a bit, i came to this solution:
class ActionView::Helpers::FormBuilder
def phone_text_field name, options = {}
value = object.public_send(name).to_s
if value.start_with? "9" # national number
value = value[1..-1]
options["value"] = value
end
text_field name, options
end
end
I put this on app/helpers/application_helper.rb and use it like i use text_field() helper. Hope this helps someone.
I'd like to be able to generate the following markup:
<label for="field">Something <span class="hint">Field hint</span></label>
from the following code:
form_for ... do |f|
f.label :field, :hint => "Field hint"
end
So far I've created an initializer to store the custom functionality which re-opens ActionView::Helpers::FormBuilder and changes the label method, however I'm not sure what the best way to actually get the span into the text for the label. If I try to put the text in directly then rails, rightly so, escapes the content.
I'd quite like to use the existing label infrastructure as it has all the validation error support. This rules out using content_tag and generating it all myself (which would work, but doesn't seem... right).
Instead of changing the default builder, you should create a custom builder and pass it to the form with the :builder parameter.
class HintFormBuilder < ActionView::Helpers::FormBuilder
end
form_for #resource, :builder => HintFormBuilder do |f|
# ...
end
The Hint builder inherits all FormBuilder features, including validation, error messages and so on. Now, you should change what you need to change in order to customize the behavior.
This is a really raw draft.
class HintFormBuilder < ActionView::Helpers::FormBuilder
(%w(label)).each do |selector|
src = <<-end_src
def #{selector}(method, options = {})
hint = options.delete(:hint)
returning(super) do |element|
# replace here the value of element with hint
# if hint != nil
# remember to use gsub! and not gsub
end
end
end_src
class_eval src, __FILE__, __LINE__
end
end
EDIT based on the first comment:
It's always a good idea to not hack the Rails internals because you might need to use, now or in the future, plugins or features that rely on the original behavior. If you don't want to manually append the builder in your forms, you can create an helper.
def search_form_for(record_or_name_or_array, *args, &proc)
options = { :builder => HintFormBuilder }
form_for(record_or_name_or_array,
*(args << options),
&proc)
end
If you want to reopen the original class instead, I would suggest to create a new method. This solution also applies to the custom helper and has the benefit you can customize it without the need to gsub! the response. Yes, gsub! is the common way to do so because when extending the original methods you only have access to the method/options and the result, no the value (that is injected by the #object variable).
class ActionView::Helpers::FormBuilder
def label_with_hint(method, text = nil, options = {})
hint = options.delete(:hint)
# do your own customizations...
#template.label(#object_name, method, text, objectify_options(options))
end
end
EDIT: I was mistaken, you can pass a custom text as a parameter so you don't need to gsub! the returned string. I got confused by the text_field tag.
At this point, you can use either the first (subclassing with/without custom method), second (hacking internals) or third option (hacking internals with custom method) and intercept the text value before it is sent to #template.label.
Also note that text can be nil. If nil, the value is automatically generated from method. You should be aware of this.
Here's what I would have done.
# config/initializers/[anything].rb
ActionView::Base.default_form_builder = CustomFormBuilder
# lib/custom_form_builder.rb
class CustomFormBuilder < ActionView::Helpers::FormBuilder
def label(field, text, options = {})
if options[:hint]
hint = #template.content_tag(:span, options[:hint], :class => "hint")
super(field, "#{field.to_s.humanize} #{hint}", options)
else
super
end
end
end
Is there an easy way to have the form helpers set the tabindex parameter automatically when using form helpers in Rails?
Basically, I don't want to have to manually set the tab index on every form element when building forms (I keep forgetting to update them when I change things). The majority of the forms I write are basically a list of fields. The tab index should be in the order they are defined. Ideally, I would set the initial index in the form_for call and everything else would be handled for me.
Does anyone know how to do this?
I usually add a method like this to ApplicationHelper
def autotab
#current_tab ||= 0
#current_tab += 1
end
Then in my views I make calls to the helper with a :tabindex => autotab like so:
<%= text_field "post", "login",:tabindex => autotab, :value => #login %>
You can also modify all the text_field, check_box, methods one at a time to add the tabindex automatically, by adding something like this to your application helper: (untested but you get the point)
def text_field_with_tabindex(*args)
options = args.last
options[:tabindex] = autotab if options.is_a?(Hash) && options[:tabindex].nil?
text_field_without_tabindex(*args)
end
def self.included(base)
base.class_eval do
alias_method_chain :text_field, :tabindex
end
end
That might be more trouble than it's worth