Ajax, tabs and Rails: Part 1 26 September 2008
Developing a horizontal tabbed menu with Ajax functionality with Ruby on Rails, provides us with a fine example of why unobtrusive JavaScript is important, and worth bothering with.
I'll quickly set up a skeleton of a rails app with an 'about us' section showing several related pages with information about something or another.
$ rails tabs
$ script/generate controller about page_1 page_2 page_3 page_4
class AboutController < ApplicationController
#specify layout
layout 'application'
#I don't need to define any methods, Rails will simply go looking for the called page in the 'about' folder under 'views'
end
My layout 'application' is minimal so as to keep things simple.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
# be sure to include javascript defaults, we'll be using Prototype
<%= javascript_include_tag :all, :cache => true %>
# ...and a style sheet
<%= stylesheet_link_tag 'screen' %>
<title>Tabs</title>
</head>
<body>
<div id="content">
# Tabbed navigation is appropriately represented in HTML as an unordered list.
<ul id="tabnav">
# To help keep my template clean, I'll call a helper method (yet to be written), for generating a link.
<%= link('page_1', 'Page One') %>
<%= link('page_2', 'Page Two') %>
<%= link('page_3', 'Page Three') %>
<%= link('page_4', 'Page Four') %>
</ul>
<div id="tabContent">
<%= yield %>
</div>
</body>
</html>
Here is my helper method in about_helper.rb:
# helpers/about_helper.rb
# Method takes a page id and label and generates a link inside a list element, attaching a class name of 'active' on list element for active link.
def link(id, label)
"<li#{ ' class="active"' if controller.action_name == id}>#{link_to(label, send('about_' + id + "_path"))}</li>"
end
I'll need to write some routes before we have something that works:
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
def map.controller_actions(controller, actions)
actions.each do |action|
self.send("#{controller}_#{action}", "#{controller}/#{action}", :controller => controller, :action => action)
end
end
map.controller_actions 'about', %w[page_1 page_2 page_3 page_4]
end
And now for a lick of paint:
/* screen.css */
body {
font: 100% verdana, arial, sans-serif;
background-color: #fff;
margin: 50px;
}
ul#tabnav {
text-align: left;
margin: 1em 0 1em 0;
font: bold 11px verdana, arial, sans-serif;
border-bottom: 1px solid #6c6;
list-style-type: none;
padding: 3px 10px 3px 10px;
}
ul#tabnav li {
display: inline;
}
ul#tabnav li.active {
border-bottom: 1px solid #fff;
background-color: #fff;
}
ul#tabnav li.active a {
background-color: #fff;
color: #000;
position: relative;
top: 1px;
padding-top: 4px;
}
ul#tabnav li a {
padding: 3px 4px;
border: 1px solid #6c6;
background-color: #cfc;
color: #666;
margin-right: 0px;
text-decoration: none;
border-bottom: none;
}
ul#tabnav a:hover {
background: #fff;
}
Within the 'about' folder under views, you'll find several pages created earlier that need some dummy content. After adding some lorem text I kick-start mongrel and visit http://0.0.0.0:3000/about/page_1:

Clicking through each tab works like a charm. I now have a simple, but functionally sound tab menu device. I'll consider adding a behavioral layer on top of what I've created, in order to provide Ajax functionality, and will write about it very soon in part two.
AHOY THERE!
We're an Agile web development duo operating from a tiny dinghy currently moored off France. We build accessible, standards driven web solutions using the power of the Ruby on Rails web development framework and efficiency of agile processes... more»