DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Luke has posted 4 posts at DZone. View Full User Profile

Creating Navigation Bars With Current Section Highlight In Rails

05.08.2006
| 22543 views |
  • submit to reddit
        UPDATE: I have since packaged this functionality up as a Rails plugin - more information can be found at http://opensource.agileevolved.com

*THE CODE BELOW HAS BEEN DISCOVERED TO HAVE PROBLEMS RUNNING IN PRODUCTION MODE - THIS HAS BEEN FIXED IN THE PLUGIN VERSION OF THIS SNIPPET. PLEASE SEE THE URL ABOVE*

There are many ways of creating navigation bars using XHTML and CSS but the most common method is to create an unordered list of links which are then formatted in a variety of ways using CSS. There are also many ways of highlighting the tab for the current page, some using server-side logic, some using CSS.

This method enables you to create as many navigation lists as you like in your Rails views, giving each navigation list a unique ID which is then referenced using a simple macro in your controller to set the highlighted link.

First of all, we have the main helper function that lets us create our navigation lists - add the following to your application helper:

def navigation_list(links, options={})
  link_html = ''
  links.each do |link|
    class_name = 'navlink'
    class_name << ' current' if current_section(options[:id], link[:text])
    link_html << content_tag('li', link_to(link[:text], link[:url]),
      :id => "nav_#{link[:text].rubify}", :class => class_name)
  end
  content_tag('ul', link_html, :id => "navbar_#{options[:id]}" || 'navigation')
end

private
  def current_section(navigation_id, link_text)
    link_name = link_text.rubify.to_sym
    navigation_id = navigation_id.rubify.to_sym
    return true if (@controller.current_section?(navigation_id, link_name))
  end

As a small aside, you'll notice the above code relies on a small extension to the Ruby String class - create a file called string.rb to your Rails lib/ folder and add the following monkey patch:

class String 
  def rubify
    self.downcase.strip.gsub(' ', '_')
  end
end

Some of the above functionality will become apparent later, but essentially what it does is take an array of links in a specific format (see below) and turn them into an unordered list of links. The function takes an optional :id parameter (which otherwise defaults to 'navigation') which is used as both the HTML ID on the UL element and as an identifier when using the navigation mappings functionality below. Each LI element is given a class of 'navlink' and also a class of 'current' if it is the current link (see below). This provides plenty of hooks for styling in your CSS.

Here is an example usage of the above function:

<%=	navigation_list [
		{ :text => 'Dashboard', :url => { :controller => 'dashboard' },
		{ :text => 'Admin', :url => { :controller => 'admin', :action => 'login' } }
	], :id => 'main_nav' %>

The :text key is the actual link text, and the :url key is the standard set of Rails url_for options. You can create as many of these navigation lists on a page as you want - just be sure to give each one a unique ID.

The other step is to create a way of storing your "navigation mappings" - a hash of navigation list to current link pairs. Add the following to your ApplicationController:

@@navigation_mappings = {}

def self.navigation_section(mapping)
  @@navigation_mappings.merge!(mapping)
end
  
def current_section?(navigation_id, link_name)
  return (@@navigation_mappings[navigation_id] == link_name)
end

You can use the navigation_section macro to specify which link to set as current for a particular navigation list. Using the example navigation list above, if we wanted to set the Dashboard link as current for all the actions in our DashboardController, we would add the following to our controller code:

class DashboardController < ApplicationController
  navigation_section :main_nav => :dashboard
end

Again, you can add as many navigation_section declarations to your controller if you have many navigation lists on your page that you want to control. The key is the ID of the navigation list and the value is a symbol version of the particular link text - for example, 'This is a link' would become :this_is_a_link.

Adding this mapping will add a 'current' class to the LI element for that particular link - you can now use this in your stylesheet to style the current page link appropriately.    

Comments

Snippets Manager replied on Fri, 2008/07/11 - 2:51am

So, what's the name of the recommended plugin and where can I find it? http://opensource.agileevolved.com/ does not currently resolve.

Snippets Manager replied on Tue, 2006/12/26 - 5:54pm

After execution, I have got this error, please anybody there to resolve it undefined method `rubify' for "Dashboard":String Extracted source (around line #9): 6: <%= navigation_list [ 7: { :text => 'Dashboard', :url => { :controller => 'dashboard' }}, 8: { :text => 'Admin', :url => { :controller => 'admin', :action => 'login' } } 9: ], :id => 'main_nav' %> 10: <%= yield %> 11:

Luke Redpath replied on Mon, 2006/05/08 - 9:24am

jalense, you are right there was a small mistake there. May I suggest you start using the Rails plugin for this on opensource.agileevolved.com as there are some issues with the above code when running in production mode due to the use of class variables and class caching.

Snippets Manager replied on Tue, 2006/05/16 - 9:13am

Greate snippet! Aren't you missing a curly brace in the example usage though? Shouldn't it read: <%= navigation_list [ { :text => 'Dashboard', :url => { :controller => 'dashboard' } }, { :text => 'Admin', :url => { :controller => 'admin', :action => 'login' } } ], :id => 'main_nav' %> or at least, that seemed to get it to work for me.