Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@greypants
Forked from reagent/nav_link.rb
Last active October 17, 2023 05:49
Show Gist options
  • Star 59 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save greypants/3279194 to your computer and use it in GitHub Desktop.
Save greypants/3279194 to your computer and use it in GitHub Desktop.
RAILS 3: nav_link helper for adding 'selected' class to navigation elements

UPDATE: It's a gem!

This helper has finally been moved into a gem called nav_lynx!

https://github.com/vigetlabs/nav_lynx

http://rubygems.org/gems/nav_lynx

Thanks to @brianjlandau and @reagent for getting that set up and tested!

Behold, the nav_link:

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.

<%= nav_link 'My Page', my_path %>

When my_path is the same as the current page url, this outputs:

<a class="selected" href="http://example.com/page">My Page</a>

For more options and full usage details, see: http://viget.com/extend/rails-selected-nav-link-helper

Drop nav_link_helper.rb into app/helpers in your Rails 3.x app and enjoy.

UPDATE: Now with block support!

Same usage as link_to:

<%= nav_link 'http://example.com/page' do %>
  <strong>My Page</strong>
<% end %>
@jem
Copy link

jem commented Aug 8, 2012

This obviously goes beyond it, but might be good to mention the simple link_to_unless_current method available from ActionView as well.

@greypants
Copy link
Author

Yeah, that was actually where I started when first looking into this issue, and is definitely good to note. The main problem with that is just doesn't output the <a> tag by default, but just the text inside it if the page is 'selected'. That tends to visually break things when css styling relies on that element's presence. You can specify specific markup to output instead, but it gets a little messy.

@murdoch
Copy link

murdoch commented Aug 10, 2012

I learned a lot from reading through the code here, thanks for sharing, I imagine I will use it often.

@column111
Copy link

nice code ...
Will it be possible to place an additional and/or Tag inside the Tag?

e.g.:

  • Home
  • or something like
  • More
  • @column111
    Copy link

    nice code ...
    Will it be possible to place an additional span-tag and/or b-tag inside the Link-Tag?

    e.g.:
    li --> a --> span --> b

    span und b-Tag können auch classes haben

    @cayblood
    Copy link

    I'm getting errors if I use the controller_segment option and the current url does not have the segment being looked for.

    @rubenrails
    Copy link

    It would be cool if this can accept a block as the last argument, just as link_to

    @proosakos
    Copy link

    Has anyone successfully used this with dropdowns? Twitter bootstrap styled dropdowns are lists nested inside the wrapper

  • tag, which is what needs the "active" class assigned to it.

  • @greypants
    Copy link
    Author

    Just updated to accept blocks!

    @awakia
    Copy link

    awakia commented Feb 4, 2013

    Really nice helpers and I used this a lot. Though it cause ActionController::RoutingError if the request method is other than GET. Just updating two methods fix this issue, so please update this gist. Thanks.

          def current_controller
    -      controller_for(@request.path)
    +      controller_for(@request.path, @request.request_method)
         end
    
    -    def controller_for(path)
    -      Rails.application.routes.recognize_path(path)[:controller]
    +    def controller_for(path, method = "GET")
    +      Rails.application.routes.recognize_path(path, method: method)[:controller]
         end
    

    @leisti
    Copy link

    leisti commented Feb 6, 2013

    This is useful. I have a couple of suggestions, though:

    First, please copy the usage instructions from
    http://viget.com/extend/rails-selected-nav-link-helper to README.markdown. You
    never know when and if the article will become unavailable for some reason, and
    it's a good idea to keep everything to do with one gist in one place.

    Second, it would be nice if one could also give an option for defining a class
    name to be given for unselected links, in case the user wants to define
    properties for such links in a .css file.

    Third, I didn't really see the point of methods that are called from only one
    place in the code -- I think they can confuse rather than clarifying the code,
    so I merged the code of methods link_classes, html_options and link to method
    LinkGenerator::to_html.

    I also made a couple of changes to parameter and variable names, for better
    self-documentation and more clarity. These are the resulting changes to the
    code (tested to work):

      def to_html
    -   html = link
    -   if @options[:wrapper]
    -      html = content_tag(@options[:wrapper], html, :class => wrapper_classes)
    -   end
    -   html.html_safe
    +   linked_classes = nil
    +   if @html_options[:class]
    +     linked_classes = @html_options[:class] + " #{class_name_to_be_used}"
    +   elsif !@options[:wrapper_class]
    +     linked_classes = class_name_to_be_used
    +   end
    +   merged_html_options = @html_options.merge(class: linked_classes)
    +   the_link = link_to(@title, @path, merged_html_options)
    +   if @options[:wrapper_class]
    +     the_link = content_tag(@options[:wrapper_class], the_link, :class => all_wrapper_classes)
    +   end
    +   the_link.html_safe
      end
    
    - def link_classes
    -   if @html_options[:class]
    -     @html_options[:class] + " #{selected_class}"
    -   elsif !@options[:wrapper]
    -     selected_class
    -   end
    - end
    
    - def html_options
    -   selected? ? @html_options.merge(:class => link_classes) : @html_options
    - end
    
    - def link
    -   link_to(@title, @path, html_options)
    - end
    
    + def unselected_class
    +   @options[:unselected_class] || ''
    + end
    
    + def class_name_to_be_used
    +   name = selected? ? selected_class : unselected_class
    + end
    
    - def wrapper_classes
    -   if selected?
    -     "#{selected_class} #{@options[:wrapper_class]}"
    -   else
    -     @options[:wrapper_class]
    -   end
    
    + def all_wrapper_classes
    +   "#{class_name_to_be_used} #{@options[:other_wrapper_classes]}"
    + end

    And this is a usage example, from a .html.erb file:

    <%= nav_link "Good stuff", good_stuff_path, {}, {selected_class: 'active', unselected_class: 'inactive', wrapper_class: 'li'} %>

    @greypants
    Copy link
    Author

    Thanks for the feedback guys! All good stuff. I'll be moving this into a full repo soon for better collaboration.

    @firedev
    Copy link

    firedev commented Mar 17, 2013

    I think it should be nav_link_to and please make it a gem.

    @meal
    Copy link

    meal commented Mar 24, 2013

    I tried to used it with engine mounted, unfortunately it doesn't work in such case :(

    @rubenrails
    Copy link

    +1 @greypants for accepting blocks!

    @firedev
    Copy link

    firedev commented Apr 4, 2013

    @C-E-Rios
    Copy link

    How can I make this work to ensure the when a user lands on the home-page the home tab is 'selected'?

    Currently no tabs are selected when you land on the website.

    Thanks.

    @greypants
    Copy link
    Author

    @diegocouto
    Copy link

    It seems to be running smoothly also on Rails 4, so maybe the Gem dependency could be updated.

    And by the way, thanks for this! :-)

    @greendezine
    Copy link

    Just what I needed, and working great on Rails 4! In a similar fashion to how url_segment works, Is it possible to add the selected class to all pages under a sub-directory structure ?

    For example: admin/products/item, admin/products/list etc.. Automatically adding the "selected" class to all urls within "/products" (within the same controller)

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment