在 Rails 3中向导航添加“当前”类的最佳方法

导航菜单中有一些静态页面。我想添加一个类“当前”的项目,目前正在显示。

我这样做的方法是添加大量的 helper 方法(每个方法对应一个条目)来检查控制器和操作。

def current_root_class
'class="current"' if controller_name == "homepage" && action_name == "index"
end


<ul>
<li <%= current_root_class %>><%= link_to "Home", root_path %>

还有什么更好的方法吗! ? 我现在的方法太愚蠢了... ..。

58529 次浏览

Not truly an answer here, because I'm using quite the same way as you are. I've just defined helper methods to test for multiple controller or actions:

In application_helper.rb

  def controller?(*controller)
controller.include?(params[:controller])
end


def action?(*action)
action.include?(params[:action])
end

Then you can use if controller?("homepage") && action?("index", "show") in your views or other helper methods…

I use an awesome gem called Tabs on Rails.

The way I've done it is to add a helper function in the application_helper

def current_class?(test_path)
return 'current' if request.request_uri == test_path
''
end

Then in the nav,

<%= link_to 'Home', root_path, :class => current_class?(root_path) %>

This tests the link path against the current page uri and returns either your current class or an empty string.

I've not tested this thoroughly and I'm very new to RoR (moving over after a decade with PHP) so if this has a major flaw I'd love to hear it.

At least this way you only need 1 helper function and a simple call in each link.

Use the current_page? helper to determine whether or not you should assign the "current" class. For example:

<%= 'active' if current_page?(home_about_path) %>

Note you can also pass a path (not only a hash of options), e.g: current_page?(root_path).

I made a helper called nav_link:

def nav_link(link_text, link_path)
class_name = current_page?(link_path) ? 'current' : ''


content_tag(:li, :class => class_name) do
link_to link_text, link_path
end
end

used like:

nav_link 'Home', root_path

which will produce HTML like

<li class="current"><a href="/">Home</a></li>

The current_page? method isn't flexible enough for me (say you set a controller but not an action, then it'll only return true on the controller's index action), so I've made this based on the other answers:

  def nav_link_to(link_text, link_path, checks=nil)
active = false
if not checks.nil?
active = true
checks.each do |check,v|
if not v.include? params[check]
active = false
break
end
end
end


return content_tag :li, :class => (active ? 'active' : '') do
link_to link_text, link_path
end
end

Example:

nav_link_to "Pages", pages_url, :controller => 'pages'

I use this nav_link(text, link) function in application_helper.rb (Rails 3) to get the job done and it rolls my bootstrap twitter 2.0 nav bar links for me.

def nav_link(text, link)
recognized = Rails.application.routes.recognize_path(link)
if recognized[:controller] == params[:controller] && recognized[:action] == params[:action]
content_tag(:li, :class => "active") do
link_to( text, link)
end
else
content_tag(:li) do
link_to( text, link)
end
end
end

Example:

<%=nav_link("About Us", about_path) %>

all these work with simple nav bars, but what about drop down sub-menu ? when a sub-menu is selected the top menu item should be made 'current' in this case tabs_on_rails me be the solution

Yep! Check out this article: A Better Way to Add a ‘selected’ Class to Links in Rails

Drop nav_link_helper.rb into app/helpers and it can be as easy as:

<%= nav_link 'My_Page', 'http://example.com/page' %>

The nav_link helper works just like the standard Rails link_to helper, but adds a 'selected' class to your link (or its wrapper) if certain criteria are met. By default, if the link's destination url is the same url as the url of the current page, a default class of 'selected' is added to the link.

There's a gist here: https://gist.github.com/3279194

UPDATE: This is now a gem: http://rubygems.org/gems/nav_link_to

I have a more succinct version of nav_link that works exactly like link_to, but is customized to output a wrapping li tag.

Put the following in your application_helper.rb

def nav_link(*args, &block)
if block_given?
options      = args.first || {}
html_options = args.second
nav_link(capture(&block), options, html_options)
else
name         = args[0]
options      = args[1] || {}
html_options = args[2]


html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options)


class_name = current_page?(url) ? 'active' : nil


href = html_options['href']
tag_options = tag_options(html_options)


href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
"<li class=\"#{class_name}\"><a #{href_attr}#{tag_options}>#{ERB::Util.html_escape(name || url)}</a></li>".html_safe
end
end

If you look at the above code and compare it to the link_to code in url_helper.rb, the only difference is that it checks if the url is the current page, and adds the class "active" to a wrapping li tag. This is because I'm using the nav_link helper with Twitter Bootstrap's nav component which prefers links to be wrapped inside li tags and the "active" class applied to the outer li.

The nice thing about the above code is that it allows you to pass in a block into the function, just like you can do with link_to.

For example, a bootstrap nav list with icons would look like:

Slim:

ul.nav.nav-list
=nav_link root_path do
i.icon-home
|  Home
=nav_link "#" do
i.icon-user
|  Users

Output:

<ul class="nav nav-list">
<li class="active">
<a href="/">
<i class="icon-home"/>
Home
</a>
</li>
<li>
<a href="#">
<i class="icon-users"/>
Users
</a>
</li>
</ul>

In addition, just like the link_to helper, you can pass in HTML options into nav_link, which will be applied to the a tag.

An example of passing in a title for the anchor:

Slim:

ul.nav.nav-list
=nav_link root_path, title:"Home" do
i.icon-home
|  Home
=nav_link "#", title:"Users" do
i.icon-user
|  Users

Output:

<ul class="nav nav-list">
<li class="active">
<a href="/" title="Home">
<i class="icon-home"/>
Home
</a>
</li>
<li>
<a href="#" title="Users">
<i class="icon-users"/>
Users
</a>
</li>
</ul>

To build off @Skilldrick 's answer...

If you add this code to application.js it will make sure that any dropdown menus with active children will also be marked as active...

$('.active').closest('li.dropdown').addClass('active');

To recap supportive code > Add a helper called nav_link:

def nav_link_to(link_text, link_path)
class_name = current_page?(link_path) ? 'active' : ''


content_tag(:li, :class => class_name) do
link_to link_text, link_path
end
end

used like:

nav_link_to 'Home', root_path

which will produce HTML like

<li class="active"><a href="/">Home</a></li>

This is how I solved in my current project.

def class_if_current_page(current_page = {}, *my_class)
if current_page?(current_page)
my_class.each do |klass|
"#{klass} "
end
end
end

Then..

li = link_to company_path
class: %w{ class_if_current_page( { status: "pending" }, "active" ), "company" } do
Current Company

I use a simple helper like this for top level links so the /stories/my-story page highlights the /stories link

def nav_link text, url


active = (url == request.fullpath || (url != '/' && request.fullpath[0..(url.size-1)] == url))


"<li#{ active ? " class='selected'" : '' }><a href='#{url}'>#{text}</a></li>".html_safe


end

Let me show my solution:

_header.html.erb:

  <ul class="nav">
<%= nav_tabs(@tabs) %>
</ul>

application_helper.rb:

 def nav_tabs(tabs=[])
html = []
tabs.each do |tab|
html << (content_tag :li, :class => ("current-page" if request.fullpath.split(/[\??]/)[0] == tab[:path]) do
link_to tab[:path] do
content_tag(:i, '', :class => tab[:icon]) +
tag(:br) +
"#{tab[:name]}"
end
end)
end


html.join.html_safe
end

application_controller.rb:

before_filter :set_navigation_tabs


private
def set_navigation_tabs
@tabs =
if current_user && manager?
[
{ :name => "Home", :icon => "icon-home", :path => home_index_path },
{ :name => "Portfolio", :icon => "icon-camera", :path => portfolio_home_index_path },
{ :name => "Contact", :icon => "icon-envelope-alt", :path => contact_home_index_path }
]
elsif current_user && client?
...
end

According to the answer by Skilldrick, I'll change it to the following:

def nav_link(*args, &block)
is_active = current_page?(args[0]) || current_page?(args[1])
class_name = is_active ? 'active' : nil


content_tag(:li, class: class_name) do
link_to *args, &block
end
end

to make it much more useful.

I think the best way is

application_helper.rb:

def is_active(controller, action)
params[:action] == action && params[:controller] == controller ? "active" : nil
end

And in menu:

<li class="<%= is_active('controller', 'action') %>">

I know it is a out dated answer, but you can easily ignore all these current page check by using a link_to helper wrapper, called active_link_to gem, it works exactly what you want, add a active class to current page link

This version is based on @Skilldrick's one but allows you to add html content.

Thus, you can do:

nav_link "A Page", a_page_path

but also:

nav_link a_page_path do
<strong>A Page</strong>
end

or any other html content (you can add an icon for instance).

Here the helper is:

  def nav_link(name = nil, options = nil, html_options = nil, &block)
html_options, options, name = options, name, block if block_given?
options ||= {}


html_options = convert_options_to_data_attributes(options, html_options)


url = url_for(options)
html_options['href'] ||= url


class_name = current_page?(url) ? 'current' : ''
content_tag(:li, :class => class_name) do
content_tag(:a, name || url, html_options, &block)
end
end

My easy way -

application.html.erb,

<div class="navbar">
<div class="<%= @menu1_current %> first-item"><a href="/menu1"> MENU1 </a></div>
<div class="<%= @menu2_current %>"><a href="/menu2"> MENU2 </a></div>
<div class="<%= @menu3_current %>"><a href="/menu3"> MENU3 </a></div>
<div class="<%= @menu4_current %> last-item"><a href="/menu4"> MENU4 </a></div>
</div>

main_controller.erb,

class MainController < ApplicationController
def menu1
@menu1_current = "current"
end


def menu2
@menu2_current = "current"
end


def menu3
@menu3_current = "current"
end


def menu4
@menu4_current = "current"
end
end

Thanks.

Here is the full example, on how to add an active class on bootstrap menu page in rails view.

    <li class="<%= 'active' if current_page?(root_path) %>"><%= link_to 'Home', controller: "welcome" %></li>
<li class="<%= 'active' if current_page?(about_path) %>"><%= link_to 'About us', about_path %></li>
<li class="<%= 'active' if current_page?(contact_path) %>"><%= link_to 'Contact us', contact_path %></li>

If also you want to support html options hash in the view. For example if you want to call it with other CSS class or id, you can define the helper function like this.

def nav_link_to(text, url, options = {})
options[:class] ||= ""
options[:class] += " active"
options[:class].strip!
link_to text, url, options
end

So in the view, call this helper the same way you'd call link_to helper

<%= nav_link_to "About", about_path, class: "my-css-class" %>

For me personally i used a combination of answers here

<li class="<%= 'active' if current_page?(inventory_index_path) %>"><a href="#">Menu</a></li>

I am using materialize css and my way of making the main categories collapsible is by using the code below

$('.active').closest(".collapsible.collapsible-accordion")
.find(".collapsible-header")
.click();

hope it helps someone

I think I came up with a simple solution that might be helpful for a lot of use cases. This lets me:

  • Support not only plain text but HTML inside link_to (e.g. add an icon inside the link)
  • Add just few lines of code to application_helper.rb
  • Append active to the whole class name of the link element instead of it being the sole class.

So, add this to application_helper.rb:

def active_class?(class_name = nil, path)
class_name ||= ""
class_name += " active" if current_page?(path)
class_name.strip!
return class_name
end

And on your template you can have something like this:

<div class="col-xs-3">
<%= link_to root_path, :class => active_class?("btn btn-outline-primary", root_path) do %>
<i class="fa fa-list-alt fa-fw"></i>
<% end %>
</div>

As bonus you can specify or not a class_name and use it like this: <div class="<%= current_page?(root_path) %>">

Thanks to previous answers 1, 2 and resources.

Create a method in ApplicationHelper as below.

def active controllers, action_names = nil
class_name = controllers.split(",").any? { |c| controller.controller_name == c.strip } ? "active" : ""
if class_name.present? && action_names.present?
return action_names.split(",").any? { |an| controller.action_name == an.strip } ? "active" : ""
end
class_name
end

Now use it in view as below use cases.

1. For all action of any specific controller

<li class="<%= active('controller_name')%>">
....
</li>

2. For all action of many controllers (with comma seperated)

<li class="<%= active('controller_name1,controller_name2')%>">
....
</li>

3. For specific action of any specific controller

<li class="<%= active('controller_name', 'action_name')%>">
....
</li>

4. For specific action of many controllers (with comma seperated)

<li class="<%= active('controller_name1,controller_name2', 'action_name')%>">
....
</li>

5. For some specific actions of any specific controller

<li class="<%= active('controller_name', 'index, show')%>">
....
</li>

6. For some specific actions of many controllers (with comma seperated)

<li class="<%= active('controller_name1,controller_name2', 'index, show')%>">
....
</li>

Hope it helps