Best way to add "current" class to nav in Rails 3
Asked Answered
S

24

112

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

Sarmentum answered 14/9, 2010 at 3:22 Comment(0)
U
55

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…

Unsocial answered 14/9, 2010 at 12:12 Comment(5)
Your way suit best when there are some pages handled by different controller / actions but in the same menu item, right? Have you encountered more advantages~?Sarmentum
No more advantage except the conciseness of the syntax. I'm curious to see if someone else has a better solution.Unsocial
I did this method with great success. Added this code to the view: <%= link_to "Users", users_path, class: (controller?("users") ? 'selected' : nil) %> Really neat that it works for both /users and /users/new.Swetiana
an improvement could be controller_name and action_name instead of params probablyMiffy
Well done. I did not think of keeping this simple but this scales very nicely. +1Schreck
S
291

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>
Squirm answered 13/10, 2011 at 15:13 Comment(7)
How would you extend this to many other classes/pages in a DRY way? Looking for an elegant answer to my question: #7760550Dentelle
This works great with twitter ootstrap if you are using the twitter bootstrap navbar twitter.github.com/bootstrap/examples/hero.htmlFreese
Change to class_name = current_page?(link_path) ? 'current' : nil if you don't want the class tag to show when not on current pageHaddad
How would you modify this for nested lists used in dropdowns?Macdonald
Anyone having trouble with this approach when using nested resources?Otolith
@Macdonald - no modification needed on my site: seems to work fine for nested lists in dropdowns.Basinger
I have added a few extra arguments extra_classes=nil, id=nil then inside the content_tag content_tag(:li, class: [class_name, extra_classes], id: id) so that you can add your own classes and id's to the elements as well :)Hypnotism
T
74

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

Tuberculous answered 2/8, 2011 at 22:45 Comment(2)
Is there way to get this to ignore query params?Mcafee
@Mohamad, you can do this: current_page?(controller: 'users', action: 'index')Meredi
U
55

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…

Unsocial answered 14/9, 2010 at 12:12 Comment(5)
Your way suit best when there are some pages handled by different controller / actions but in the same menu item, right? Have you encountered more advantages~?Sarmentum
No more advantage except the conciseness of the syntax. I'm curious to see if someone else has a better solution.Unsocial
I did this method with great success. Added this code to the view: <%= link_to "Users", users_path, class: (controller?("users") ? 'selected' : nil) %> Really neat that it works for both /users and /users/new.Swetiana
an improvement could be controller_name and action_name instead of params probablyMiffy
Well done. I did not think of keeping this simple but this scales very nicely. +1Schreck
B
26

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) %>
Bohman answered 2/3, 2012 at 16:18 Comment(1)
This could be simplified by using the current_page? method, as in other answers.Torture
L
10

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.

Levileviable answered 11/11, 2010 at 22:51 Comment(1)
Just a problem. I used request.path instead of request_uri (request_uri wasnt working, maybe rails version problem?). Your answer is clean and elegant in my opinion.Playgoer
R
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>
Reube answered 9/12, 2012 at 19:41 Comment(0)
H
4

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') %>">
Howard answered 23/5, 2014 at 13:55 Comment(3)
is it okay to leave a blank class"" like that?Unreeve
Yes. It may look strange if you view source seeing a bunch of empty class attributes, but it is valid HTML.Fortney
You can use <%= content_tag(:li, "Click me", class: is_active('controller', 'action')) %>, it won't print an class attribute for nil. apidock.com/rails/ActionView/Helpers/TagHelper/content_tagMalocclusion
S
4

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

Sensibility answered 17/7, 2014 at 13:29 Comment(0)
I
4

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>
Incendiarism answered 22/10, 2015 at 9:15 Comment(0)
H
3

I use an awesome gem called Tabs on Rails.

Hoof answered 22/9, 2010 at 23:18 Comment(1)
Thanks for the suggestion. Since my nav is so simple and there's only one with little items, the gem would probably over-powered.Sarmentum
P
3

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>
Pepin answered 22/9, 2012 at 19:38 Comment(1)
Awesome. For me this was the most viable option exactly because it allows blocks. Thanks a ton!Petrify
P
3

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

Piotrowski answered 7/7, 2016 at 12:57 Comment(1)
forgot about this. Thanks for posting this as a reminder.Schreck
D
2

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'
Dyedinthewool answered 30/12, 2011 at 3:20 Comment(2)
Thank you for your answer. I think yours is similar to the accepted answer actually :)Sarmentum
I came across this answer through Google since I was having this problem, so I thought I'd help everyone else who comes across this as well :)Dyedinthewool
J
2

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

Judkins answered 8/8, 2012 at 17:21 Comment(1)
This is nice. I took into use, and added a modification to support giving a class name to the unselected elements, also, as a comment in the gist.Torture
B
2

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
Backdrop answered 14/8, 2013 at 0:32 Comment(0)
I
2

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
Inshore answered 7/11, 2013 at 20:37 Comment(0)
M
1

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.

Marrilee answered 4/12, 2013 at 7:1 Comment(0)
B
1

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
Bugger answered 11/11, 2014 at 1:32 Comment(0)
H
1

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.

Hyohyoid answered 8/1, 2017 at 14:44 Comment(0)
G
1

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

Gristly answered 4/7, 2017 at 9:57 Comment(0)
N
0

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

Nephritis answered 25/3, 2012 at 16:45 Comment(0)
P
0

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
Prevision answered 2/2, 2013 at 14:34 Comment(0)
B
0

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.

Borst answered 21/7, 2015 at 5:31 Comment(0)
L
0

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" %>
Levison answered 29/11, 2015 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.