A Better Way to Add a ‘selected’ Class to Links in Rails


This code is now maintained as the nav_lynx gem.

Adding a "selected" CSS class to navigation items (usually when they match the current page or section of a site) is a required step in almost every build-out. For some reason, it always seems to be a pain. There are plenty of ways to go about this, but most of them tend to be awkward, unsightly, or hard to maintain. As a result, I've been driven to create a Rails 3.x helper that (I hope) is 100% the opposite of that.

Behold, the nav_link:

 <%= nav_link &#39;My Page&#39;, &#39;http://example.com/page&#39; %>&#10;<!-- Outputs: -->&#10;<a href="http://example.com/page" class="selected">My Page</a>

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


 :selected_class => &#39;custom-selected-name&#39;&#10;

Overrides the default class of ‘selected’ as the class to be added to your selected nav.

 :ignore_params => true/false&#10;

Set this to true if you want the helper to ignore query strings in the url when comparing. The urls http://example.com/ and http://example.com/?param=something will be treated as equal.

 :controller_segment => number&#10;

Instead of comparing urls, you can compare the controllers of the current page and of the page you’re linking to. Assign an index identifying the controller segment you wish to match. For example, if your controller is ‘members/pages’, and you specify :controller_segment =>2, the helper will look to match */pages

 :url_segment => number&#10;

Instead of comparing full urls, you can just check segments of the urls. In the path /news/article, 'news' is segment 1, article is segment 2. This is especially useful for category navigation. Assign an index identifying the url segment you wish to match. For example, if a page’s or link’s url is ‘example.com/news/story’, and you specify :url_segment => 1, the helper will look to match /news/*

 :wrapper => &#39;div/li/span/etc...&#39;&#10;

Often times you don’t want your ‘selected’ class directly on the anchor tag. You can wrap your anchor tag in another element with :wrapper => ‘li’ (or any other html element). The ‘selected’ class will be added to this wrapper instead of the anchor. Any html_options will still be added directly to the anchor tag.

 :wrapper_class => &#39;additional wrapper class-names&#39;&#10;

If you want to specify additional classes for your wrapper (whether it is selected or not), you can add them with :wrapper_class => ‘class-name class-name-2’


With a Wrapper:

 <%= nav_link &#39;Page&#39;, some_page_path, {:class => &#39;link&#39;}, {:wrapper => &#39;li&#39;, :wrapper_class => &#39;test-wrapper&#39;, :selected_class => &#39;active&#39;} %>&#10;<!-- Outputs: -->&#10;<li class="active test-wrapper"><a href="example.com/page" class="link">Page</a></li>&#10;

Ignoring Query Strings:

Same as above, but ignoring query strings (example.com/page == example.com/page?query=true&anotherVar=false)

 <%= nav_link &#39;Page&#39;, some_page_path, {}, {:ignore_params => &#39;true&#39;} %>&#10;

Url Segment Matching:

If a specified url segment matches the current url segment (/path/segment_2/page_1 == /path/segment_2/page_99)

 <%= nav_link &#39;Page&#39;, some_page_path, {}, {:url_segment => &#39;2&#39;} %>&#10;

Controller Segment Matching:

If a specified controller name segment matches the current controller name segment (/controller_1/controller_2/controller_3 == /controller_1/controller_2/controller_98)

 <%= nav_link &#39;Page&#39;, some_page_path, {}, {:controller_segment => &#39;2&#39;} %>

The Code

You can grab the codez here: https://gist.github.com/3279194

Drop nav_link_helper.rb into app/helpers in your Rails 3.x app and give it a try.

As a primarily front-end dev, and a Rails n00b, my first attempt was functional, but needed some love from a REAL Ruby dev. Three cheers for Patrick Reagan (reagent) cleaning things up/doing a complete refactor of my mess :)

Gimme Some Feedback!

How have you've handled these situations in the past? Is this solution headed in the right direction? Could it be better? I'd love to hear your thoughts.

Dan Tello

Posted in Article Category: #Code