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
Related
I have a table Companies with field city_id. I need in sorting companies by cities. I do next:
# companies_controller.rb
def sort_by_city
#cities = City.joins(:company).uniq
#companies = Company.where("city_id = ?", params[:city_id])
render 'index'
end
# routes.rb:
get '/companies/city/:city_id', to: 'companies#sort_by_city'
and in index.html.erb I try make links for this filter:
<% #cities.each do |city| %>
<li><%= link_to city.name, UNKNOWN_path(city.id) %> (<%= city.companies_count %>)
<% end %>
I want links like this: "www.site.com/companies/city/23"
What I must write instead of UNKNOWN_path? Or I do something wrong before (in controller or in routes.rb)?
You can give a name to your route with the as option :
For example :
get '/companies/city/:city_id', to: 'companies#sort_by_city', as: 'companies_city_sort'
And then use
companies_city_sort_path
Also, have a look at resourceful routing, it may be more adapted.
If you type rake routes in your terminal you can find the name of the current path.
Just look for the line where companies#sort_by_city is listed.
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.
I have a simple menu that looks like this:
<ul class="menu">
<li class="active">Home</li>
<li class=""><%= link_to 'Feeds' , feeds_path %></li>
<li class=""><%= link_to 'Accounts' , accounts_path %></li>
</ul>
Class "active" is the style to mark my current page.
I have two questions:
1. How do I "tell" the app what page I am on and ask it to "change" the class to active?
2. Is there a better way to create this menu (maybe driven by the controller or a db table)?
I realize this is a newbie question, but I have been thinking about this for a few days now, have read some tutorials, but none of them really click.
Thanks for your help.
I use the method current_page? to set my active link. It takes a path as its parameter. I create a hash of link texts and paths and iterate over it printing the links. That way I only have to call current_page? one time.
There are gems that can help you, though. Look through these: https://www.ruby-toolbox.com/categories/rails_menu_builders
I have done this recently in ApplicationHelper:
def nav_links
items = [home_link, about_me_link, contact_link]
content_tag :ul, :class => "nav" do
items.collect { |item| concat item}
end
end
def home_link
nav_item_active_if(!#article || #article.type.nil?) do
link_to "Home", root_path
end
end
def about_me_link
nav_item_active_if(#article && #article.type == "About") do
link_to "About Me", article_path(About.first)
end
end
def contact_link
nav_item_active_if(#article && #article.type == "Contact") do
link_to "Contact", article_path(Contact.first)
end
end
def nav_item_active_if(condition, attributes = {}, &block)
if condition
attributes["class"] = "active"
end
content_tag(:li, attributes, &block)
end
In your view you can simply call:
<%= nav_links %>
You can maybe use it as an example.
Not a perfect solution, but you could do the following.
create 3 variables:
#homeActive = ""
#feedsActive = ""
#accountsActive = ""
In the code you provided, set the class to each variable corresponding with name.
<li class=#homeActive>Home</li>
Now in your controller under the home method lets say, set #homeActive = "active", and the other two to "". Repeat for the other methods and this should work.
I used a bunch of 'if's and the current_page? method.
It's ugly but it worked, if someone has a better idea how to do this, I will be happy to learn about it.
if current_page? (root_path)
content_tag(:li , link_to('Home' , root_path), :class => "active")
else
content_tag(:li , link_to('Home' , root_path))
end
I'm trying keep a tab on a page to be selected by checking the controller.
I.e., any page that displays the Products controller should keep the Product tab selected.
I can't seem to find the right approach.
I was considering of making a helper method, but it seems a bit tricky to put the helper into my link_to helper. Here is my guess of how I can make it work:
<%= link_to "Products", products_path, current_controller?('products') ? :class => 'selected' %>
Anyone has a better idea?
And the problem wont just be in one place, you'll have many tabs and each tabs will have rules on which controllers+action combinations it would be active/selected for.
It's a common problems and some people have written "plugins" for the same too.
I suggest you write helpers. Make your own mini DSL. Decide for yourself what is easy and nice to look at:
<%= link_to_tab_for("Products", products_path, :controller => "sss", :action => "", :other_html_options => {})
Next step, implement this method in helpers/application.rb
def link_to_tab_for(name, path, options)
controller = options.delete(:controller)
action = options.delete(:controller)
klass = [].push(options[:class]).compact
if current_controller?(controller) && (action ? current_action?(action) : true)
klass.push("selected")
end
options[:class] = klass.empty ? "" : klass.join(" ")
link_to(name, path, options)
end
Have a gander at attempting the above method to your liking of course.