I'm trying to create a function that adds some functionality to the link_to function in rails. What I'd like it to do is simply to add a class to it. What I have so far:
#application_helper.rb
def button_link(*args)
link_to(*args.push(class: 'btn'))
end
Problem is that if I now add another class to the button_link function it doesn't work.
Example:
<td class='button'>
<%= button_link "Show", category_path(item), class: "btn-primary" %>
</td>
I get the following error: wrong number of arguments (4 for 3). How can I do this correctly?
link_to has 4 method signatures.. This is the one used most often.
Below we check to see if a class was already sent in -- and because of how HTML classes work, we want to have multiple classes, which are space-separated values.
def button_link(body, url, html_options={})
html_options[:class] ||= ""
html_options[:class] << " btn"
link_to body, url, html_options
end
The other method signatures can be viewed http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Try changing your helper method to this, trying to maintain the link_to form:
def button_link(name, url_options, html_options = {})
if html_options.has_key?(:class)
css_options = html_options.fetch(:class)
css_options << ' current'
html_options.merge!( { :class => css_options } )
else
html_options.merge!( { :class => ' btn' } )
end
link_to(name, url_options, html_options)
end
Related
So I have a helper method that I am trying to apply css to without putting it in a div or any other element. How would I go about applying the css class to this helper in rails?
I tried:
<%= first_letter_content(e.content), :class => "first-letter" %>
and
<%= (first_letter_content(e.content), :class => "first-letter") %>
both resulting in syntax error, unexpected ',', expecting ')'
Helper code:
def first_letter_content(content)
first_letter = content[0]
return first_letter
end
Any suggestions? I have been trying to find the proper syntax, but no luck.
Your helper does not support options (extra args) but you are trying to give a HTML class to the element.
You should wrap the content of first_letter_content inside a div/span (depending on what you want, block or inline) and apply the class on this HTML element:
<div class='first-letter'>
<%= first_letter_content(e.content) %>
</div>
Or you can directly wrap the content[0] inside a div in the helper method:
def first_letter_content(content, options = {})
content_tag(:div, content[0], options)
end
And use it like this:
first_letter_content(content, class: 'first-letter')
first_letter_content(content, class: 'first-letter', id: 'something')
first_letter_content(content)
Also, you can refactor your helper method to this:
def first_letter_content(content)
content[0]
end
It is a minor improvement but in Ruby the "last thing" used in a method will be returned by this method.
Examples:
def something
a = 2
b = 3
a
end
# => returns `2`
def something_else
a = 2
b = 3
end
# => returns `3`
def whatever
a = 12
nil
end
# => returns `nil`
I am trying to apply css to without putting it in a div or any other element
Css classes are for DOM elements, so you should wrap this content into some element/node.
For example:
def first_letter_content(content, css_class)
content_tag(:div, content[0], class: css_class)
end
Call:
<%= first_letter_content(e.content, "first-letter") %>
I find myself repeating this type of code over and over and over again.
<% if !#model.property.blank? %>
<label>Property</label>
<div><%= #model.property %></div>
<% end %>
The goal being to only output a label and a property value if and only if the value is present. I find that repeating this code many times makes it hard to scan the source code. Can this be reduced and made more concise? What pattern can be applied to this to make it easier to code?
You can create a helper for you, that will deal with theses tests automatically:
# application helper
def display_if_exists(instance, attribute)
return nil if instance.blank? || attribute.blank?
label_tag = content_tag :label do
instance.class.human_attribute_name attribute.to_sym
end
div_tag = content_tag :div do
instance.try(attribute.to_sym)
end
return (label_tag + div_tag).html_safe
end
And use it this way:
# view
display_if_exists(#user, :username)
A little improvement, with options:
def display_if_exists(instance, attribute, options = {})
return nil if instance.blank? || attribute.blank?
label_options = options.delete(:label)
div_options = options.delete(:div)
label_tag = content_tag :label, label_options do
instance.class.human_attribute_name attribute.to_sym
end
div_tag = content_tag :div, div_options do
instance.try(attribute.to_sym)
end
return (label_tag + div_tag).html_safe
end
And use the options like this:
display_if_exists(#user, :username, { label: { class: 'html-class' }, div: { style: 'margin-top: 2px;' } })
An other option is the Rails Presenter Pattern. It is very interesting, but might be too deep for what you are trying to achieve:
http://eewang.github.io/blog/2013/09/26/presenting-the-rails-presenter-pattern/
http://fr.slideshare.net/mdesjardins/presenters-in-rails
http://fr.slideshare.net/thaichor/presenter-and-decorator-in-rails
Ruby on Rails patterns - decorator vs presenter
May be you would like to extract this into a helper method where you can put the existing logic and call that helper.
def print_property_if_present(model)
"<label>Property</label><div>#{model.property}</div>" if model.property.present?
end
Don't forget to call html_safe to render the output in an HTML printable format.
Hope this helps!
Is there a way to add an icon to the following link_to_unless_current? I need it to be added in both current and non-current states.
= link_to_unless_current("Stats", stats_practice_path(current_user.practice)) do content_tag(:div, content_tag(:p, "Stats"), :class => "nav-active") end
I think you use a helper method for this.
Ex:
module ApplicationHelper
def icon(image = "youricon.png")
raw(image_tag(image))
end
end
and in your view:
= link_to_unless_current(icon, stats_practice_path(current_user.practice)) do content_tag(:div, content_tag(:p, "Stats"), :class => "nav-active") end
(Note: I didn't test this myself, I think it should work :))
I'd like to have a helper that works just like link_to except that it merges in a data attribute (in this case for ease of creating tabs using bootstrap: http://twitter.github.com/bootstrap/javascript.html#tabs)
So I can call it like this:
link_to_tab("Name", #obj)
and get
<a href='/path' data-toggle='tab'>Name</a>
I've come up with this which seems to work:
def link_to_tab(*args, &block)
toggle_hash = {'data-toggle' => 'tab'}
last_arg = args.pop # if link_to was given a hash of html_options, merge with it
if last_arg.is_a? Hash
link_to(*args, last_arg.merge(toggle_hash), &block)
else
link_to(*args, last_arg, toggle_hash, &block)
end
end
Is there a cleaner, more idiomatic way to support all of the styles of calling link_to?
Not really. You could try this...
def link_to_tab(*args, &block)
toggle_hash = {'data-toggle' => 'tab'}
if args.last.is_a? Hash
args.last.merge!(toggle_hash)
else
args << toggle_hash
end
link_to(*args, &block)
end
Not that different though...
I'd like to have a helper that works just like link_to except that it merges in a data attribute
I might be missing something, but why not just pass a custom data argument to the link_to helper?
= link_to "foo tab", {}, "data-toggle" => "tab"
Outputs:
<a data-toggle="tab" href="/">foo tab</a>
Edit
If you're planning on using it a lot you can do:
def link_to_tab(*args, &block)
if args.last.is_a? Hash
link_to *(args.take args.size - 1), args.last.merge("data-tab" => "tab"), &block
else
link_to *args, "data-tab" => "tab", &block
end
end
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.