Best way to add “current” class to nav in Rails 3

2019-01-07 01:51发布

问题:

I have some static pages in a navigation menu. I want to add a class like "current" to the item which is currently displaying.

The way I am doing so is to add tons of helper methods (each for one item) to check the controller and action.

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

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

Is there any better way to do so!? My current way is so stupid......

回答1:

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…



回答2:

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>


回答3:

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).



回答4:

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) %>


回答5:

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.



回答6:

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>


回答7:

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') %>">


回答8:

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



回答9:

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>


回答10:

I use an awesome gem called Tabs on Rails.



回答11:

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



回答12:

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'


回答13:

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>


回答14:

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



回答15:

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


回答16:

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


回答17:

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.



回答18:

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


回答19:

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.



回答20:

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



回答21:

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


回答22:

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.



回答23:

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" %>


回答24:

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