This plugin is now available at GitHub. Follow all plugin modifications there (the SVN repo will soon be obsolete).
It seems like I’ve been going on and on about navigation in Rails lately. That’s probably because I have been. But it’s one of those things that presents itself in every app I work on. And since I usually go about it close to the same way, it makes a good case for a plugin, don’t you think? I shall call it, navigation helper. Clever, I know.
It’s pretty basic, but I’ll go through a few examples, anyway. Before I continue, two notes about these examples:
- whenever you see
navigation(...), think of<%= navigation(...) -%>as that method is a helper used in a view - assume the current page will always be ‘Home’
Now that we have that settled, let’s see a few examples.
Basic Usage
This is how most people would probably use this plugin:
navigation [:home, :about, :contact_me] # HTML output: # <ul class="nav_bar"> # <li class="current"><a href="/home">Home</a></li> # <li><a href="/about">About</a></li> # <li><a href="/contact_me">Contact Me</a></li> # </ul>
You must use symbols as the links/sections, and we’ll see why in just a bit. Also, notice how :contact_me turns into ‘Contact Me’ as link text. You can use CSS to transform it to all lowercase or all uppercase, but the default is to capitalize each word (which you can’t do with CSS).
Oh, and the above example would be looking for the following named routes: home_path, about_path, and contact_me_path. So make sure you have a named route for each section you pass to the helper.
Authorized Sections
I don’t know about you, but a lot of the time I have one or two tabs that only appear based on some sort of authorization. For example, this very site has ‘Portfolio’, ‘Words’, ‘Archives’, and ‘About’ at the top. But if I’m logged in it also has ‘Admin’. Again, that’s a common need for me, so I added in support for that. Use it like so:
# Example 1: single section navigation [:home, :about, :admin], :authorize => [:admin] # Example 2: multiple sections navigation [:home, :about, :users, :reports], :authorize => [:users, :reports]
The plugin will only add those sections to the list if they are authorized. And how does it “authorize”? Well, by default the plugin will check against a logged_in? method. If that method either doesn’t exist or fails (returns false), those tabs will not be added. “But Ryan, don’t you think it’s kind of wrong of you to assume that we’re using a logged_in? method?” Well, maybe, maybe not. Either way, you can override that by telling the helper which method to use, like so:
navigation [:home, :about, :admin], :authorize => [:admin], :with => :auth_method
Now auth_method will be checked instead of logged_in?.
One more thing. In the case that the entire navigation is to be authorized, there is a way to avoid re-typing all of those sections in :authorize option.
navigation [:home, :about, :admin], :authorize => [:all]
Adding Subtitles
This was a recent need of mine (in the Portfolio). What I mean by subtitles is text that supports each link, but is not part of the link itself. That may have not helped you at all. Maybe this example will:
navigation [:home, 'Start Here', :about, 'Learn More'] # HTML output: # <ul class="nav_bar"> # <li class="current"> # <a href="/home">Home</a> # <span>Start Here</span> # </li> # <li> # <a href="/about">About</a> # <span>Learn More</span> # </li> # </ul>
Then you can do some fancy CSS :hover styling or whatever. Sometimes that adds just enough spice to a boring navigation bar.
But wait, there’s a second option. If you want the subtitles to appear as hover text instead, just let the helper know:
navigation [:home, 'Start Here', :about, 'Learn More'], :hover_text => true # HTML output: # <ul class="nav_bar"> # <li class="current"><a href="/home" title="Start Here">Home</a></li> # <li><a href="/about" title="Learn More">About</a></li> # </ul>
And as I briefly mentioned above, that is why you have to pass symbols as the link/section and strings as the subtitles. The plugin understands symbols and strings differently (the positions matter, too: even for sections, odd for subtitles).
Setting Current Tab
OK, the last thing. By default the plugin uses the controller’s name to determine the “current” tab (or link or section) you’re on. But since that’s not always feasible, you can specify the current tab for any controller by doing:
class PublicController < ApplicationController current_tab :home end
Now something like this will work as intended:
navigation [:home, :about, :contact_me]
The PublicController will be seen as :home to the navigation helper, and will choose the current tab accordingly.
Documentation
All (or most) of the above is in the README. It’s on the agile web development site, too (although it doesn’t provide much info).
Installation
If you think this plugin could help you out, by all means install it. Here’s how:
ruby script/plugin install http://svn.rpheath.com/code/plugins/navigation_helper
...or…
piston import http://svn.rpheath.com/code/plugins/navigation_helper vendor/plugins/navigation_helper
Conclusion
I have a few more things I want to do with this plugin (such as sub-navigation), but I didn’t want to get unnecessarily complex just yet. Feel free to modify the code to your needs, and let me know if there’s something you absolutely think should be added/changed/fixed. Who knows, I just might do it! Enjoy.






Comments
This is absolutely great. I find myself doing the same thing over and over. Not sure why it didn’t occur to me that this would be a good plugin.
I haven’t checked out the code yet or anything, but the idea itself seems great. I love that you even thought to include the authorized tabs and active tab overrides.
Thinking about this (again, without looking at code and only thinking about future expansion), it might make sense to have the primary navigation array also be hashes. i.e.
I love your concise syntax, but having an option of something like this could really open up the flexibility. The best thing about this would be the potential to do nested drop down menus (which I tend to need more often than not) with something like:
Again, this is not at all a criticism. I’m just so excited by the idea that I can’t stop making it more and more complicated. It’s a problem I have. :)
You know, I was back and forth on the the hash or array thing. My original plan was (and this was concerning subtitles, but use your imagination) to do something like this with the subtitles:
The section/subtitle association is a lot more obvious that way. But there’s a problem with that: a
Hashis an unordered container. So then I thought, no biggy, if aHashis passed in, I’ll just instantiate a new dictionary object, which will preserve the order.Well, I would have had to do something like this:
But the fact that I had to still pass section, subtitle, section, subtitle to the dictionary class (same as the array) sort of turned me off to that idea. In the end, I decided to just use a plain array instead of an array/hash combo.
Personally, I don’t think visible subtitles AND hover text is applicable. It seems like at that point I’d be adding options just for the sake of adding options. But yes, this plugin could definitely be extended a bit more to cover all sorts of other navigation needs.
I get your point about needing both as being redundant, I was just trying to make it as ridiculous as possible.
WRT to hashes, in Ruby 1.9 all hashes are ordered and in Edge Rails there is an OrderdHash class included in active support.
http://www.culann.com/2008/01/rails-goodies-activesupportorderedhash
Great plugin!
I was glad to see that it uses class rather than id. I needed more than one so I did something like this:
I didn’t see any support for link_to_unless_current did I miss it? I did notice class=”current” for styling.
Thanks for the plugin.
Well, I’m of the opinion that there’s no need to replace the link with text on the “current” tab. Also, since this plugin matches the current tab at the controller level, several actions could result in that controller being the “current” one. I like to be able to always click the tab again to get to the default page, rather than providing a series of breadcrumbs to backtrack.
But it wouldn’t be all that hard to modify the plugin itself, and feel free to do so. You could also use javascript to do this:
//// // prototype document.observe("dom:loaded", function() { $$('ul.nav_bar li.current').each(function(e) { e.update(e.down().innerHTML); }); }); //// // jQuery $(document).ready(function() { $('ul.nav_bar li.current').each(function(i) { this.html(this.children('a:first').text()); }); });That’s untested, of course. Admittedly, though, I’d modify the plugin over a javascript solution, I just thought I’d mention it.
Oh, and I’m glad you found the plugin useful :-)
Hello there,
thanks for the useful plugin! Quick question: is it possible to add sub-elements of a navigation element like e.g. in this example:
http://qrayg.com/experiment/cssmenus/
So you can specify sub-navigation elements of each of the navigation [:home, :about, :admin].. elements… or no subelements and the behaviour is just like the current one.
Hope that makes sense :)
-J
Well even better / compliant: http://www.htmldog.com/articles/suckerfish/dropdowns/
Sorry, no sub-elements for the navigation helper. It’s a different type of navigation from a menu-driven approach. It provides more of a tabbed interface. But maybe that’s an idea for a navigation_menu plugin? I could actually use that on a project I’m working on… so who knows… maybe I’ll write it (I’ll post about it here if I do).
Ryan,
alright – thanks for letting me know. I need it kinda right away, too … and will implement it one way or another over the next couple days. In case you wanna do it together, let me know :)
-J
Hi Ryan,
I keep on having “undefined method `home_path’ for #” error when I add you navigation bar to my home page.
Am I doing something wrong here ?
I have added the following lines in my routes.rb file :
Any idea ?
Yeah, minor problem with your routes. You need:
From that, Rails will build helpers for you based on the map.[whatever] part. So for
map.homeyou’ll gethome_path,home_url, etc. In your case, though, you’re gettinghome_path_pathbecause you’re including “_path” in the route definition. Try omitting the “_path” part in routes.rb and see if that helps.Let me know.
Thanks Ryan, it solved my problem. I did know that Rails was building _url helpers but not _path ones.
That works great, thanks for the support and the plugin !
Ryan, what about situations where you need to pass a parameter to the named route. For example:
How would it work here?
Kris -
For my needs, I usually have the tab mapped to a controller’s index action (since it’s the default), which can act as sort of a “dashboard-ish” type of representation (for users it may be the most recently registered users, or whatever). So in your example, I’d have the tab be
:users(which would map to the defaultindexaction of theusers_controller), and usecurrent_user(either via restful_authentication or my own implementation) to represent anything I needed to do that was user-centric.But I understand that you may have just been using that as an example, and the user you’re referencing above may not be yourself (ie,
current_user). In either case, I think that may be getting a little too specific for the plugin, but feel free to hack away if you want to add support for that type of thing.Hi,
I would like to specify the text of the link instead of the default of the section name. Will appreciate any help on how to tweak this plugin to achieve this.
thanks.
Never mind,
I managed to do what I wanted. Basically in the construct method where the subtitles are added to the link I am doing
therefore getting the span tag as part of the link which is what I needed.
Cheers!.