Tuesday, July 07, 2009

Implementing breadcrumb in rails projects – a clean approach

breadcrumb

In most web applications, breadcrumbs offer a nice way to trace back to previously visited pages. However, generating the breadcrumbs and showing them on dynamic pages sometimes need special care for the following reasons-

  1. The links of the breadcrumb may contain dynamic links.
  2. It is used in all the pages. So, changing the breadcrumb may trigger a series of changes.

In ScrumPad, we are using ruby on rails. There are a few plugins to help rails projects in managing breadcrumbs, but none seemed to satisfy our need. In fact, most projects come up with some distinctive ways to generate the breadcrumbs. However, we kept the breadcrumb clean in the following way in ScrumPad.

Step#1:

Encapsulated the breadcrumb generation logic into a single class and used it from all places.

class Breadcrumb
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TagHelper
include ApplicationHelper

attr_accessor :parts, :separator, :prefix

def initialize(separator = nil, parts = [])
self.separator = separator || "<img src="\"/images/stub_arrow_right.gif\"" />"
self.parts = parts
end

def add(title, url = nil)
self.parts << {:title => title, :url => url}
end

def set_prefix(title, url)
self.prefix = {:title => title, :url => url}
end

def to_s
if(!self.parts.nil? and !self.parts.empty?)
if(self.prefix.nil?)
parts_with_prefix = self.parts
else
parts_with_prefix = [self.prefix] + self.parts
end

breadcrumb_html = []
parts_with_prefix.each do |part|
if(part[:url].nil? or part[:url].empty?)
breadcrumb_html << "#{part[:title]}"
else
breadcrumb_html << link_to(part[:title], part[:url])
end
end
return breadcrumb_html.join(" #{self.separator} ")
end
return ''
end
end

Step#2:

A before_filter in the ActionController initializes the breadcrumb in the following way:-

def initialize_breadcrumb()
@breadcrumb = Breadcrumb.new
if(project_selected?)
@breadcrumb.prefix = {:title => "Dashboard", :url => dashboard_project_path(selected_project())}
end
end

Step#3:

Now, @breadcrumb is available to all actions. However, if the RESTful resource definition is right, the rest of the breadcrumb generation becomes really simple. We have methods like the following in our controllers-

def generate_breadcrumb(sprint, story = nil)
@breadcrumb.add(sprint.name, project_sprint_path(project.id, sprint.id))
if(!story.nil?)
@breadcrumb.add(story.display_title, story_path(story))
elsif(self.action_name != 'index')
@breadcrumb.add('Stories', stories_path())
end

current_page = self.action_name == 'index' ? 'Stories' : self.action_name.humanize
@breadcrumb.add(current_page)
end

Step#4:

Finally, in my view layout, I just use this instance variable as shown here:-

<%= @breadcrumb %>

So, the whole breadcrumb generation is encapsulated and the implementation is clean. Let me know if you liked it. I am in the process of creating a plugin so that anyone can just drop it in any rails app and start using straight away!