I'm trying to create a helper method that can have optional arguments for link_to method. My intention is to create a helper method for different cases:
# Typical case #1 (can have any number of extra arguments)
<%= toolbar_item('google.com', 'Google', 'globe', 'btn-primary', target: '_blank') %>
# Typical case #2 (extra arguments are optional)
<%= toolbar_item(root_path, 'Start', 'flag', 'btn-primary') %>
Follows the code:
def toolbar_item(url,text,icon,custom_class, optional_extra_settings = {})
link_to raw("<i class='fa fa-#{icon}'></i> #{text}"), url, class: custom_class, optional_extra_settings
end
It's no good. link_to method does not recognize the extra_settings and raises and error.
Any ideas?
Thanks!
The link_to method accepts only 3 arguments. The last argument needs to be a hash. Therefore you have to merge your class setting with the optional extra settings hash.
Change your example to:
def toolbar_item(url, text, icon, custom_class, optional_extra_settings = {})
html_options = { class: custom_class }.merge(optional_extra_settings)
link_to raw("<i class='fa fa-#{h icon}'></i> #{h text}"), url, html_options
end
Furthermore, you will notice that I used h to escape your icon and text. Just to be safe, because you disabled the auto-escaping that is usually done by Rails by using raw.
Don't reinvent the wheel. The CSS class is an option you can pass to the link_to helper via the options hash. Let's move it to the options hash and remove one needless argument.
# notice class: 'btn-primary' vs 'btn-primary'
<%= toolbar_item(..., class: 'btn-primary', target: '_blank') %>
Now, link_to also accepts a block. Use it to simplify your code pass the icon ('globe' or 'flag', etc...) as a block.
def toolbar_item(url, text, options = {}, &block)
if block_given?
link_to url, options do
yield
end
else
link_to text, url, options
end
end
Now, each time you use the helper with an icon, you can specify the icon you want:
<%= toolbar_item 'google.com', class: 'btn-primary' do %>
<%= content_tag :i, class: 'globe' %> Google
<% end %>
Which begs the question. Do you really need a helper after all? All we did was create a wrapper. You can just do:
<%= link_to 'Google', 'google.com', class: 'btn-primary' %>
<%= link_to 'Google', class: 'btn-primary' do %>
<%= content_tag :i, class: 'globe' %> Google
<% end %>
Related
I have a method like this:
gamer.rb
def approve_gamer(type)
where(type: type).last.update(status: 'approved')
end
and I want to make a button for each type to call approve_gamer('type').
Should it be button_to [:approve_gamer, gamer] or link_to ... class: "btn btn-default"? How do I pass the type parameter there?
I'd use the button_to helper since I wouldn't call data-changing methods through GET.
button_to creates a form around the button and sends the data through POST.
More to read here: http://apidock.com/rails/ActionView/Helpers/UrlHelper/button_to
You can also set the form yourself:
<%= form_for gamer, :url => { :action => 'approve' } do |f| %>
<%= f.hidden_field('type', :value => type_goes_here) %>
<%= button_tag 'Approve', class: 'btn btn-default' %>
<% end %>
That method you call should be in your gamers_controller though.
Also you could simply call the edit method for gamer and set the parameters.
And if you call the method approveyou have to set the route, too.
The documentation for Rails select form helper states (see documentation):
select(object, method, choices = nil, options = {}, html_options = {}, &block)
Which allows adding a class simple, like so:
<%= f.select :some_attr, MYOPTIONS, {}, {class: 'my-class'} %>
My question is, how do I add a class to it when using it as a block? Rails documentation states:
select(report, "campaign_ids") do
available_campaigns.each do |c|
content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
end
end
It doesn't work when I use it like so:
<%= f.select :some_attr, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
Nor does it work if I use:
f.select :some_attr, class: 'my-class' do
The class is not applied to the select tag in the HTML.
I solved my own problem, although I don't fully understand the answer, so if someone else understands this better, I'd love to hear your answer.
To get it to work, I simply added an additional empty hash to the beginning, like so:
<%= f.select :some_attr, {}, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
The second hash is still options and the last is still html_options, so as an example, you can also add include_blank like so:
f.select :some_attr, {}, {include_blank: true}, {class: 'my-class'}
However, I don't know what the first hash is, nor what values can be passed there. I've looked at the Rails source, but I still have no clue. If you have insight into this, I'd love to hear it.
A couple oddities to be aware of:
In your example, you're using f.select, which you can find a reference for here:
https://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/select
Only the first parameters is required, the rest have defaults. However, to assign that HMTL class, you had to have a value for the fourth parameter, which necessitated having something for the second and third parameters as well.
What you ended up with is a valid solution:
<%= f.select :some_attr, {}, {}, {class: 'my-class'} do %>
<% MYOPTIONS.each do |MYOPTION| do %>
<%= content_tag :option, MYOPTION.label, value: MYOPTION.value %>
<% end %>
<% end %>
The block, when provided, takes precedence over the literal value (an empty hash in this case).
Surprisingly, if you were rendering this tag using select_tag instead of f.select, passing a block wouldn't be an option:
https://apidock.com/rails/ActionView/Helpers/FormTagHelper/select_tag
I want to conditionally add the class "hidden" to a Rails link tag, depending on if "accepted == true".
If I weren't using a rails link_to I could do <a href="#" class="foo bar <%= "hidden" if accepted == true %>" >. How can I accomplish this inside a link_to?
<%= link_to "Accept Friend Request",
"#",
class: "btn btn-success btn-sm btn-block requestSent ???hidden???",
disabled: true %>
If you use interpolation with #{}, anything you put between it is run as plain old Ruby code.
In this example you could add a conditional class in the string like this:
<%= link_to "Accept Friend Request",
"#",
class: "btn btn-success btn-sm btn-block requestSent #{'hidden' if accepted}",
disabled: true %>
Just note that you should use single quotes around the class name 'hidden'.
Also note that when a variable represents a boolean value (true or false), you don't need to explicitly say if accepted == true. You can simply say if accepted.
You can do it outside the link_to:
<% css_class = accepted ? "hidden" : "" %>
<%= link_to "Accept Friend Request",
"#",
class: "btn btn-success btn-sm btn-block requestSent #{css_class}",
disabled: true %>
You can use a helper to build up the link as well:
def accept_friend_request_link
classes = [:btn, :and_friends]
if accepted
classes << :hidden
end
link_to 'Accept Friend Request', '#', class: classes, disabled: true
end
I posted a similar answer to this question.
A cleaner solution
The standard approach requires putting logic into the views and using string interpolation or moving things into a separate helper.
Here's an updated approach that avoids any of that:
<%= link_to "Accept Friend Request",
"#",
class: class_string("btn btn-success btn-sm ban-block requestSent" => true, hidden: accepted),
disabled: true %>
class_string method
The class_string helper takes a hash with key/value pairs consisting of CSS class name strings and boolean values. The result of the method is a string of classes where the boolean value evaluated to true.
Sample Usage
class_names("foo bar" => true, baz: false, buzz: some_truthy_variable)
# => "foo bar baz"
Inspired by React
This technique is inspired by an add-on called classNames (formerly known as classSet) from Facebookâs React front-end framework.
Using in your Rails projects
As of now, the class_names function does not exist in Rails, but this article shows you how to add or implement it into your projects.
I've override the link_to to expect a class_if parameter, check it out:
def link_to(options = {}, html_options = {})
if html_options.is_a?(Hash) && html_options.key?(:class_if)
html_options[:class] <<
(" #{html_options[:class_if][1].strip}") if html_options[:class_if][0]
html_options.delete :class_if
end
super(options, html_options)
end
And usage:
<%= link_to(my_path, class: "class1", class_if: [true_or_false?, 'class2']) %>
I've just override one method, but a good refactor is to override all of described signatures here: http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
UPDATE
Another solution is:
module ApplicationHelper
def active_link_to(name = nil, options = nil, html_options = nil, &block)
html_options ||= { class: '' }
html_options[:class].concat('is-active') if options.match(controller_name)
link_to(name, options, html_options, &block)
end
end
And usage:
<%= active_link_to(my_path, 'My path') %>
This way, I got an "active status" even when is a custom route like: "my_path/help", "my_path/two".
I feel like I'm abusing form_for (and simple_form_for) to update a single hidden attribute of a record. Here is an example:
<%= simple_form_for :present, url: present_path(list_item), method: 'put' do |f| %>
<%= f.hidden_field :ordered, value: "1" %>
<%= f.button :submit, "ordered", class: "btn btn-mini" %>
<% end %>
Essentially this presents a single button that a user can press to mark a present as 'ordered'. On the back end, it updates the attribute :ordered to the value 1 for the current list_item.
I feel like this is a cheat because its not a proper form per se. I'd much rather have a single link_to which would when clicked updated the attribute.
I imagine the link_to would need to be method: aware to update the attribute.
Is it possible to replace my simple_form_for with a single link_to?
it is, use
link_to 'Ordered', present_path(list_item, ordered: 1), method: :put
Try using:
link_to 'Ordered', present_path(list_item, present: {ordered: 1}), method: :put
I need implements a helper that creates <button>...</button> tag, I need to do some similar to this:
<%= form_for(some_var) do |f| %>
<%= f.submit '+' %>
<% end %>
The helper should work like this:
<%= f.button '+' %>
# Returns
<button type="submit">+</button>
I saw https://github.com/rails/rails/blob/master/actionpack/lib/action_view/helpers/form_tag_helper.rb#L458 but this isn't implemented in Rails 3.0.7.
What I need to do to implements this helper in my application?
You can create a custom form helper that inherits from FormBuilder to use when creating forms. I created this button method to use with Twitter's Bootstrap.
Replace 'Bootstrap' with whatever fits. (Perhaps CuteAsAButtonBuilder?)
app/helpers/bootstrap_form_builder.rb
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
def button(label, options={})
# You can also set default options, like a class
default_class = options[:class] || 'btn'
#template.button_tag(label.to_s.humanize, :class => default_class)
end
end
Now you have two ways to use the builder.
1. DRY for ducks
Every time you build a form that uses the button, you need to specify the builder...
<%= form_for #duck, :builder => BootstrapFormBuilder do |form|%>
2. DRY for devs
Add the following
app/helpers/application_helper.rb
module ApplicationHelper
def bootstrap_form_for(name, *args, &block)
options = args.extract_options!
form_for(name, *(args << options.merge(:builder => BootstrapFormBuilder)), &block)
end
end
Just call the magic builder...
<%= bootstrap_form_for #person do |form| %>
<%= form.button 'Click Me' %>
<% end %>
I had implemented a similar helper method in one of my applications earlier. I needed the button tag with an image on the button and a class of its own. You can pass either a string which is the text that is displayed on the button or the object itself. It looks like this:
def submit_button(object)
image = "#{image_tag('/images/icons/tick.png', :alt => '')}"
if object.is_a?(String)
value = "#{image}#{object}"
else
name = object.class.to_s.titlecase
value = object.new_record? ? "#{image} Save #{name} Information" : "#{image} Update #{name} Information"
end
content_tag :button, :type => :submit, :class => 'button positive' do
content_tag(:image, '/images/icons/tick.png', :alt => '')
value
end
end
Then you call this in the form <%= submit_button #admission %>
It looks like this: