I recently had to include a breadcrumb style navigation system on a site I’m working on.
You know the score… Like on a forum:
Home > Random Crap > My new pet narwhal
Anyway, I found a few gems and plugins out there which help out with this, but nothing really nailed it for what I needed.
My requirements were:
- Simple to implement
- Smartness. I don’t want to write config files for this.
- ..but not too smart. I don’t want it to guess, guess wrong and for me to have no control over fixing this.
- Flexibility. Different breadcrumb trails within the same controller is a must.
I found this solution which I liked, but it didn’t handle dynamic linking so well within my slightly complex setup. That’s to say, I didn’t want the page we’re currently on to be an active link, because this is a bit of a usability faux pas, according to Jacob Nielsen. I don’t remember the exact reasoning, but something a little more elaborate was required.
I based my implementation on the above solution, so it has a few similarities.
Without further ado:
lib/breadcrumb_helper.rb
module BreadcrumbHelper
def self.included receiver
receiver.extend ClassMethods
end
module ClassMethods
def breadcrumb name, options = {}
before_filter options do |controller|
controller.send(:breadcrumb, name, options)
end
end
end
def breadcrumb name, options
@breadcrumbs ||= []
unless options[:url]
url = "#{name}_path"
else
url = options[:url]
end
name = name.to_s.capitalize if name.class == Symbol
if options[:link] != false
url = eval(url) if url =~ /_path|_url|@/ #ouch, ugly hack.
params_pos = url =~ /\?/
if params_pos
useful_url = url[0..params_pos-1]
else
useful_url = url
end
path_parts = ActionController::Routing::Routes.recognize_path(useful_url, :method => :get)
if path_parts[:controller] == params[:controller] and path_parts[:action] == params[:action]
@breadcrumbs << { :name => name }
else
@breadcrumbs << { :name => name, :url => url }
end
else
@breadcrumbs << { :name => name }
end
end
def breadcrumbs delimeter = " / "
links = []
@breadcrumbs.each do |breadcrumb|
links << @template.link_to_if(breadcrumb[:url], breadcrumb[:name], breadcrumb[:url])
end
links.join("<span class='delimeter'>#{delimeter}</span>")
end
def breadcrumbs_title
@template.html_escape(@breadcrumbs.last[:name])
end
end
in app/controllers/application_controller.rb :
include BreadcrumbHelper
helper_method :breadcrumbs
helper_method :breadcrumbs_title
breadcrumb :home
This allows you to use and in your views, as well as setting up an initial breadcrumb of “Home”, which maps to home_path. If you don’t have a home_path in your routes, you might want to change it to something else.
Usage
breadcrumb :items
Items [links to items_path, /items]
breadcrumb "My pet narwhal", :url => item_path(narwhal)
My pet narwhal [links to item_path(narwhal), /item/42 for example] You could only do this in a place where the object narwhal exists in scope, so you might have to put it into a method in your controller.
breadcrumb "People", :url => "users_path"
People [links to users_path, /users]. If you quote the path, it will be eval’d within the controller, this helps avoid putting your breadcrumb defs within methods.
breadcrumb "Something Nice", :link => false
Self explanatory, I hope. In your view
<%= breadcrumbs %>
becomes Home / Items / My pet narwhal
<%= breadcrumbs ":" %>
becomes Home : Items : My pet narwhal
You can also generate the page title using the last breadcrumb, using
<%= breadcrumbs_title %>
I’m sure this can be simplified and optimised further, but.. Enjoy?