Semantic navigation; a semantic way to build beautifully simple navigation structures in Rails.
Most of the navigation styles I’ve done over the years pretty much boil down to this idea: Use simple markup, and do the rest with css (and javascript if you need it). So, yeah, the default markup is beautifully simple (UL and LI tags).
Ok, so navigation is easy right? Until you start managing active/highlighted, disabled states, and more if you may need them. This can quickly become a mess, and all too often it just stays in the views with whatever logic tossed on top of it as people go. I’ve seen it too many times, and I wanted to do something about it.
I went in with these requirements:
- Should be simple
- Should be easily customizable
- Should handle active/highlighted states
- Should handle disabled states
- Should be pleasant to use
- Should use less code to create than it generates
And then I wrote a DSL that met those requirements:
<% semantic_navigation :main do |n| %> <%= n.group 'Media' do %> <%= n.item 'Image Gallery', :link => '/media/images', :highlights_on => '/media/videos' %> <%= n.item 'Videos', :link => '/media/videos', :disabled_if => proc { true } %> <%= n.item 'Contact Us' # auto links to the contact_us_path if it exists %> <% end %> <% end %>
Since I clearly needed something that allowed for customization I ended up emulating the way Rails uses form builders. With some ideas taken from formtastic, I setup navigasmic to use builders. There are two builders that are provided (HTML for ERB, HAML, etc. and XML for ERB, Builder, etc), and these builders can be extended or replaced if you need more custom markup.
Currently the HTML builder generates UL and LI tags, which helps with SEO, and the XML builder generates tags that can be used for XML site maps.
The gem is hosted on gemcutter, so if you haven’t already, add it as a gem source:
sudo gem sources -a http://gemcutter.org/
Then install the navigasmic gem:
sudo gem install navigasmic
Simple Usage
- semantic_navigation :main do |n| = n.group 'Media', :html => {:class => 'media'} do = n.item 'Image Gallery', :link => {:controller => 'media/images'} = n.item 'Videos', :link => {:controller => 'media/videos'} = n.group 'Info' do = n.item 'About Us', :link => '/about_us', :html => {:title => 'we are the coolest'} = n.item 'Nothing Here'…produces…
<ul id="main" class="semantic-navigation"> <li class="media with-group"> <span>Media<span> <ul> <li id="image_gallery"><a href="/media/images"><span>Image Gallery</span></a></li> <li id="videos"><a href="/media/videos"><span>Videos</span></a></li> </ul> </li> <li class="with-group"> <span>Info<span> <ul> <li id="about_us" title="we are the coolest"><a href="/about_us/"><span>About Us</span></a></li> <li id="videos"><span>Nothing Here</span></li> </ul> </li> </ul>
Nesting of items and additional markup
- semantic_navigation :main, :html => { :class => 'primary' } do |n| = n.group 'Media' do = n.item 'Image Gallery', :link => {:controller => 'media/images', :action => 'index'} = n.item 'Videos', :link => {:controller => 'media/videos'} .secondary = n.group 'Info' do = n.item 'About Us', :link => '/about_us' do %ul = n.item 'Nothing Here'…produces…
<ul id="main" class="primary semantic-navigation"> <li id="media" class="with-group"> <span>Media</span> <ul> <li id="image_gallery"><a href="/media/images"><span>Image Gallery</span></a></li> <li id="videos"><a href="/media/videos"><span>Videos</span></a></li> </ul> </li> <div class="secondary"> <li id="info" class="with-group"> <span>Info</span> <ul> <li id="about_us"> <a href="/about_us"><span>About Us</span></a> <ul> <li id="nothing_here"><span>Nothing Here</span></li> </ul> </li> </ul> </li> </div> </ul>
Highlighting — highlights on the media controller (or any that inherit from it), on the /images path, and on mondays
- semantic_navigation :main do = n.item 'Image Gallery', :link => 'media/images' :highlights_on => [{:controller => 'media'}, '/images', proc { Time.now.wday == 1 }]…produces
<ul id="main" class="semantic-navigation"> <li id="image_gallery" class="highlighted"><a href="/media/images"><span>Image Gallery</span></a></li> </ul>
Disabling — disabled on tuesdays, and when not logged in
- semantic_navigation :main do = n.item 'Image Gallery', :link => 'media/images' :disabled_if => proc { Time.now.wday == 2 || !logged_in? }…produces
<ul id="main" class="semantic-navigation"> <li id="image_gallery" class="disabled"><span>Image Gallery</span></li> </ul>
Nesting of items and additional markup
<% semantic_navigation :main, :html => { :class => 'primary' } do |n| %> <%= n.group 'Media' do %> <%= n.item 'Image Gallery', :link => {:controller => 'media/images', :action => 'index'} %> <%= n.item 'Videos', :link => {:controller => 'media/videos'} %> <% end %> <div class="secondary"> <%= n.group 'Info' do %> <%= n.item 'About Us', :link => '/about_us' do %> <ul><%= n.item 'Nothing Here' %></ul> <% end %> <% end %> </div> <% end %>…produces…
<ul id="main" class="primary semantic-navigation"> <li id="media" class="with-group"> <span>Media</span> <ul> <li id="image_gallery"><a href="/media/images"><span>Image Gallery</span></a></li> <li id="videos"><a href="/media/videos"><span>Videos</span></a></li> </ul> </li> <div class="secondary"> <li id="info" class="with-group"> <span>Info</span> <ul> <li id="about_us"> <a href="/about_us"><span>About Us</span></a> <ul> <li id="nothing_here"><span>Nothing Here</span></li> </ul> </li> </ul> </li> </div> </ul>
The XML Builder isn’t hashed out completely, but it more or less generates google sitemap style xml. I don’t know if the format is entirely correct in terms of nesting (it would make sense if it is), and I’d invite anyone who would like to hash it out more to do so.
Simple Usage Using Builder
xml.instruct! :xml, :version => '1.0', :encoding => 'UTF-8' semantic_navigation :primary, :builder => Navigasmic::XmlNavigationBuilder do |n| n.group 'Media' do n.item 'Image Gallery', :link => {:controller => 'media/images', :action => 'index'} n.item 'Videos', :link => {:controller => 'media/videos'}, :changefreq => 'monthly' end end…produces…
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> <urlset> <url> <loc>http://host:port/media/images</loc> <changefreq>yearly</changefreq> </url> <url> <loc>http://host:port/media/videos</loc> <changefreq>monthly</changefreq> </url> </urlset> </urlset>
Simple Usage Using HAML
- semantic_navigation :primary, :builder => Navigasmic::XmlNavigationBuilder, :changefreq => 'daily' do |n| = n.item 'Image Gallery', :link => {:controller => 'media/images', :action => 'index'} = n.item 'Videos', :link => {:controller => 'media/videos'}, :changefreq => 'monthly'…produces…
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> <url> <loc>http://host:port/media/images</loc> <changefreq>daily</changefreq> </url> <url> <loc>http://host:port/media/videos</loc> <changefreq>monthly</changefreq> </url> </urlset>
Tip: You can check the template_format and use the XML Builder when it’s :xml, and the HTML Builder if not.
:builder => template_format == :xml ? Navigasmic::XmlNavigationBuilder : nil
You can always create your own builder by extending one of the existing ones, or creating one from scratch.
Then just specify your builder, or do it as a configuration.
semantic_navigation :main, :builder => MyCustomBuilder do
Navigasmic::SemanticNavigationHelper.builder = MyCustomBuilder
RDoc documentation should be automatically generated after each commit and made available on the rdoc.info website.
I’m only testing with the latest Rails 3.x stable release, and it should work under Rails 2.3.x as well. Feel free to add backwards compatibility if you need it.
Navigasmic is hosted on Github: http://github.com/jejacks0n/navigasmic, and the gem is available on Gemcutter: http://gemcutter.org/gems/navigasmic
Copyright © Jeremy Jackson, released under the MIT license.