*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.
Aren't you missing a curly brace in the example usage though? Shouldn't it read:
or at least, that seemed to get it to work for me.