Helper wrapping link_to with an html attribute - ruby-on-rails

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

Related

sending multiple parameters to another function in ruby

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

Rails: Refactoring HTML producing helper method to use a partial - how to use blocks there?

After my earlier question Developing helper functions that generate HTML: should I rather use nested content_tag()s or partials? I am convinced now to rewrite some of my more complex HTML generating helper functions to use templates instead of nested content_tag() calls.
So here's my original helper:
def bootstrap_navlist(&block)
classes = ['nav', 'nav-list']
content_tag(:ul, class: classes.join(' ')) do
capture(self, &block)
end
end
And that's what I came up with using a partial now:
def bootstrap_navlist(&block)
render partial: 'bootstrap/navlist'
end
# views/bootstrap/_navlist.html.erb
<ul class="<%= ['nav', 'nav-list'].join(' ') %>">
How do I output the block here??
</ul>
The block looks something like this, but it can be any HTML you like:
= bootstrap_navlist do |navlist|
= navlist.item 'Home', '#'
= navlist.sublist 'Meine Favoriten', '/favorites' do |sublist|
As you can guess, I'm not sure how to output the block in the view. Should I simply capture it in the helper and pass it as a :local variable? Or is there a more common way?
Thanks a lot.
This is a case where the content tags were not deeply nested an would be reasonable as a helper.
Your helper, and partial would look like this:
def bootstrap_navlist(&block)
render template: 'bootstrap/navlist', :locals => { :block => block }
end
<ul class="nav nav-list">
<%= capture(self, &block) %>
</ul>

Rails passing a block to a helper method

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.

Loop & output content_tags within content_tag in helper

I'm trying a helper method that will output a list of items, to be called like so:
foo_list( ['item_one', link_to( 'item_two', '#' ) ... ] )
I have written the helper like so after reading Using helpers in rails 3 to output html:
def foo_list items
content_tag :ul do
items.collect {|item| content_tag(:li, item)}
end
end
However I just get an empty UL in that case, if I do this as a test:
def foo_list items
content_tag :ul do
content_tag(:li, 'foo')
end
end
I get the UL & LI as expected.
I've tried swapping it around a bit doing:
def foo_list items
contents = items.map {|item| content_tag(:li, item)}
content_tag( :ul, contents )
end
In that case I get the whole list but the LI tags are html escaped (even though the strings are HTML safe). Doing content_tag(:ul, contents.join("\n").html_safe ) works but it feels wrong to me and I feel content_tag should work in block mode with a collection somehow.
Try this:
def foo_list items
content_tag :ul do
items.collect {|item| concat(content_tag(:li, item))}
end
end
I couldn't get that work any better.
If you were using HAML already, you could write your helper like this:
def foo_list(items)
haml_tag :ul do
items.each do |item|
haml_tag :li, item
end
end
end
Usage from view:
- foo_list(["item_one", link_to("item_two", "#"), ... ])
Output would be correctly intended.
You could use content_tag_for, which works with collections:
def foo_list(items)
content_tag(:ul) { content_tag_for :li, items }
end
Update: In Rails 5 content_tag_for (and div_for) were moved into a separate gem. You have to install the record_tag_helper gem in order to use them.
Along with answers above, this worked for me well:
(1..14).to_a.each do |age|
concat content_tag :li, "#{link_to age, '#'}".html_safe
end
The big issue is that content_tag isn't doing anything smart when it receives arrays, you need to send it already processed content. I've found that a good way to do this is to fold/reduce your array to concat it all together.
For example, your first and third example can use the following instead of your items.map/collect line:
items.reduce(''.html_safe) { |x, item| x << content_tag(:li, item) }
For reference, here is the definition of concat that you're running into when you execute this code (from actionpack/lib/action_view/helpers/tag_helper.rb).
def concat(value)
if dirty? || value.html_safe?
super(value)
else
super(ERB::Util.h(value))
end
end
alias << concat

Rail3 - Smart way to Display Menu & add an Active Style Class?

Given a Rails 3 App with a menu like:
<ul>
<li>Home</li>
<li>Books</li>
<li>Pages</li>
</ul>
What is a smart way in Rails to have the app know the breadcrumb,,, or when to make one of the LIs show as:
<li class="active">Books</li>
thx
I'm not sure if I will provide you with a smart way but better something than nothing...
If your menu has some links - it is not in your example but I suppose that real menu should have links, not just the items. For example something like this in HAML: (I'm using HAML as writing ERB in text area is pure hell)
%ul
%li= link_to "Home", :controller => "home"
%li= link_to "Books", :controller => "books"
%li= link_to "Pages", :controller => "pages"
Then this helper (pasted from my project) should come handy:
#
# Shows link with style "current" in case when the target controller is same as
# current
# beware: this helper has some limitation - it only accepts hash as URL parameter
#
def menu_link_to(title, url, html_options = {})
unless url.is_a?(Hash) and url[:controller]
raise "URL parameter has to be Hash and :controller has to be specified"
end
if url[:controller] == controller.controller_path
html_options[:class] = "current"
end
link_to(title, url, html_options)
end
With this helper you can replace your "link_to" in the code above with "menu_link_to" and that's it!
An modified version of Radek Paviensky's helper is a tad simpler and more similar to link_to.
# Shows link with style "current" in case when the target controller is same as
# current.
def menu_link_to(title, options = {}, html_options = {})
if current_page?(options)
html_options[:class] ||= []
html_options[:class] << "active" # #TODO catch cases where the class is passed as string instead of array.
end
link_to(title, options, html_options)
end

Resources