Just another WordPress weblog
18 Jan
One modern, attractive way of placing a lot of content into a little space is by using a tab system. This tutorial will show you how to create a sexy, animated tab system complete with CSS sprites, cookies, and animated tab swapping.

There are a few assumptions and notes that we’re going into this system with:
The tutorial also assumes a basic knowledge of javascript. A tiny bit of MooTools or JavaScript framework experience will help.
So how is this awesome system going to work? Here’s the basic outline:
The system itself is pretty bulletproof. If the user doesn’t allow cookies, the starting tab for each list will always be 0.
If JavaScript support isn’t present, the tabs wont be seen on screen as we’ll display:none; them initially.
The HTML to accomplish the tab system and corresponding content items is incredibly simple in structure.
<div class="tab-container"> <ul id="tabs1" class="tabs"> <li>Tab 1</li> <li>Tab 2</li> <li>Tab 3</li> <li>Tab 4</li> </ul> <div class="clear"></div> <ul id="contents1" class="tabs-content"> <li>This is the content for tab 1.</li> <li>This is the content for tab 2.</li> <li>This is the content for tab 3.</li> <li>This is the content for tab 4.</li> </ul> </div>
We will be modifying the above HTML with PHP later in this tutorial to create a more robust system.
As with any CSS and HTML combo, you may style the tabs and their content items however you’d like.
I’ve chosen to use Facebook-style CSS sprites for my example tabs.
Realize that you’ll want to style the following items in a specific fashion so that the system works:
/* tabs structure */
.tab-container { width:320px; background:#eee; padding:5px 10px; }
ul.tabs { list-style-type:none; margin:0; padding:0; }
ul.tabs li { float:left; margin:10px 5px 0 0; }
ul.tabs li a { padding:5px 10px; border:1px solid #ddd; font-weight:bold; background:url(tab-sprite.jpg) 0 0 repeat-x; color:#000; text-decoration:none; }
ul.tabs li a.active { border-color:#028433; background-position:0 -96px; color:#fff; } /* sprite! background position swap */
ul.tabs li a.active:hover { text-decoration:none; cursor:default; }
ul.tabs li:hover { text-decoration:underline; }
ul.tabs-content { margin:10px 0 0 0; padding:0; }
ul.tabs-content li { height:0; overflow:hidden; margin:0; padding:0; }
/* clears floats */
div.clear { clear:both; }
/* ie fixes */
* html ul.tabs-content li { float:left; } /* ie6 */
*+ html ul.tabs-content li { width:99%; float:left; } /* ie7 */
Note that we need to implement a few Internet Explorer-specific fixes; ugly, but necessary.

One of the great advantages of MooTools is the powerful Class system.
MooTools classes allow for flexible, organized, and extendable functionalities.
Our MooTools class will be called “TabSet.” Since the TabSet class performs many actions,
lets break down each part of the class.
The first line is always giving the class a name:
/* give the class a name */
var TabSet = new Class({
Next we need to create an object that will hold our class’ options:
options: { //default tab options
activeClass: 'active', //css class
cookieName: '', //no name means no cookie
cookieOptions: { //options for the cookie, if cookie's wanted
duration: 30, //30 days
path: '/'
},
startIndex: 0 //start with this item if no cookie or active
},
Our options allow us to define:
With only three options in the class, TabSet would be considered a relatively simple class.
Next we implement two Options and Events:
Implements: [Options,Events],
Implementing Options and Events will allow us to correctly handle given options and
fire custom Load and Change events on our lists anywhere within the class.
Next we define the “initialize” method which runs upon creation of every instance of the class:
initialize: function(tabs,contents,options) {
//handle arguments
this.setOptions(options); //mix the given options with the default options
this.tabs = $$(tabs); //save the given tabs within the class
this.contents = $$(contents); //save the given "contents" within the class
//determine the "active" tab
var active = (Cookie.read(this.options.cookieName) || this.options.startIndex); //decide the index that should be active initially
this.activeTab = this.tabs[active].addClass(this.options.activeClass); //now identify the "active" tab
this.activeContent = this.contents[active].setStyle('height','auto'); //identify the "active" content
//run each tab/content combo through the "processItem" method which we'll see below
this.tabs.each(function(tab,i) { this.processItem(tab,this.contents[i],i); },this);
//tabs are ready -- fire the load event!
this.fireEvent('load');
},
Next comes the workhorse method of our TabSet class: processItem:
processItem:function(tab,content,i) {
var contentHeight = content.getScrollSize().y;
//add a click event to the tab
tab.addEvent('click',function() {
//if it's not the active tab
if(tab != this.activeTab) {
//stopper
if(e) e.stop();
//remove the active class from the active tab
this.activeTab.removeClass(this.options.activeClass);
//make the clicked tab the active tab
(this.activeTab = tab).addClass(this.options.activeClass);
//tween the old tab content up
//tween the new content down
this.activeContent.set('tween',{
onComplete:function() {
this.activeContent = content.fade('in').set('tween',{ onComplete: $empty }).tween('height',contentHeight);
//fire the tab change event
this.fireEvent('change',[tab,content]);
}.bind(this)
}).setStyles({
height: contentHeight,
overflow: 'hidden'
}).fade('out').tween('height',0);
//save the index to cookie
if(this.options.cookieName) Cookie.write(this.options.cookieName,i);
}
}.bind(this));
}
});
Here’s the basic outline of what the processItem method does:
And now a sample usage of our class:
window.addEvent('domready',function() {
var tabset = new TabSet($$('#tabs1 li a'),$$('#contents1 li'),{
cookieName: 'demo-list'
});
});
We provide our instance the tab LI A’s and the content LI’s. We also provide the optional options argument. That’s how easy it is to use this class! Here’s the complete class with usage:
/* class */
var TabSet = new Class({
options: {
activeClass: 'active', //css class
cookieName: '',
cookieOptions: {
duration: 30, //30 days
path: '/'
},
startIndex: 0 //start with this item if no cookie or active
},
Implements: [Options,Events],
initialize: function(tabs,contents,options) {
//handle arguments
this.setOptions(options);
this.tabs = $$(tabs);
this.contents = $$(contents);
//determine the "active" tab
var active = (Cookie.read(this.options.cookieName) || this.options.startIndex);
this.activeTab = this.tabs[active].addClass(this.options.activeClass);
this.activeContent = this.contents[active].setStyle('height','auto');
//process each tab and content
this.tabs.each(function(tab,i) {
this.processItem(tab,this.contents[i],i);
},this);
//tabs are ready -- load it!
this.fireEvent('load');
},
processItem:function(tab,content,i) {
var contentHeight = content.getScrollSize().y;
//add a click event to the tab
tab.addEvent('click',function(e) {
//stop!
if(e) e.stop();
//if it's not the active tab
if(tab != this.activeTab) {
//remove the active class from the active tab
this.activeTab.removeClass(this.options.activeClass);
//make the clicked tab the active tab
(this.activeTab = tab).addClass(this.options.activeClass);
//tween the old tab content up
//tween the new content down
this.activeContent.set('tween',{
onComplete:function() {
this.activeContent = content.fade('in').set('tween',{ onComplete: $empty }).tween('height',contentHeight);
//fire the tab change event
this.fireEvent('change',[tab,content]);
}.bind(this)
}).setStyles({
height: contentHeight,
overflow: 'hidden'
}).fade('out').tween('height',0);
//save the index to cookie
if(this.options.cookieName) Cookie.write(this.options.cookieName,i,this.options.cookieOptions);
}
}.bind(this));
}
});
/* usage */
window.addEvent('load',function() {
var tabset = new TabSet($$('#tabs1 li a'),$$('#contents1 li'),{
cookieName: 'demo-list'
});
});

Remember how I said we’d be modifying our original HTML with PHP? Now’s the time. Since we may
have a cookie set for our TabSet, we should attempt to detect that when we output the tab HTML.
Why? Because we want the tabs to load in smoothly. We also want to accommodate for users that don’t have JavaScript or cookies enabled.
Without this PHP, you may notice a slight “jump” in the active content area.
<?php
/*
Removes a desired variable from the querystring
Credit: http://www.addedbytes.com/code/querystring-functions/
*/
function remove_querystring_var($url, $key) {
$url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
$url = substr($url, 0, -1);
return ($url);
}
/* generate the urls */
$demo_tabs_url = remove_querystring_var($_SERVER['REQUEST_URI'],'demo-list');
$demo_tabs_url.= (is_numeric(strpos($demo_tabs_url,'demo-list')) ? '&' : '?').'demo-list=';
/* current tab */
$current_tab = isset($_COOKIE['demo-list']) ? (int) $_COOKIE['demo-list'] : (isset($_GET['demo-list']) ? (int) $_GET['demo-list'] : 0);
?>
<div class="tab-container">
<ul id="tabs1" class="tabs">
<li><a href="<?php echo $demo_tabs_url.'0'; ?>" <?php echo $current_tab == '0' ? ' class="active"' : ''; ?>>Tab 1</a></li>
<li><a href="<?php echo $demo_tabs_url.'1'; ?>" <?php echo $current_tab == '1' ? 'class="active"' : ''; ?>>Tab 2</a></li>
<li><a href="<?php echo $demo_tabs_url.'2'; ?>" <?php echo $current_tab == '2' ? 'class="active"' : ''; ?>>Tab 3</a></li>
<li><a href="<?php echo $demo_tabs_url.'3'; ?>" <?php echo $current_tab == '3' ? 'class="active"' : ''; ?>>Tab 4</a></li>
</ul>
<div class="clear"></div>
<ul id="contents1" class="tabs-content">
<li <?php echo $current_tab == '0' ? ' style="height:auto;"' : ''; ?>>This is the content for tab 1. This is the content for tab 1. This is the content for tab 1. This is the content for tab 1. This is the content for tab 1. This is the content for tab 1. This is the content for tab 1. This is the content for tab 1.</li>
<li <?php echo $current_tab == '1' ? ' style="height:auto;"' : ''; ?>>This is the content for tab 2. This is the content for tab 2. This is the content for tab 2. This is the content for tab 2. This is the content for tab 2. This is the content for tab 2. This is the content for tab 2. This is the content for tab 2.</li>
<li <?php echo $current_tab == '2' ? ' style="height:auto;"' : ''; ?>>This is the content for tab 3. This is the content for tab 3. This is the content for tab 3. This is the content for tab 3. This is the content for tab 3. This is the content for tab 3. This is the content for tab 3. This is the content for tab 3.</li>
<li <?php echo $current_tab == '3' ? ' style="height:auto;"' : ''; ?>>This is the content for tab 4. This is the content for tab 4. This is the content for tab 4. This is the content for tab 4. This is the content for tab 4. This is the content for tab 4. This is the content for tab 4. This is the content for tab 4.</li>
</ul>
</div>
Some users don’t enable JavaScript or cookies for security purposes. We still want our system to work for them though. If you recall from the previous block of code,
we’re using links with a querystring key of “demo-list” to denote a change in tab. The following block of PHP at the top of the page (before ANY output) will
help us change the cookie value to the requested tab.
<?php
/* handle the cookies */
if($_GET['demo-list']) {
/* set the new cookie */
setcookie('demo-list',(int) $_GET['demo-list'],time()+60*60*24*30,'/'); //30 days
if($_COOKIE['demo-list']) {
header('Location: '.remove_querystring_var($_SERVER['REQUEST_URI'],'demo-list'));
exit();
}
}
?>
Note that we only refresh the page if we can verify that the cookie has been set. If the cookie hasn’t been set, the user has their cookies disabled.

Here’s a quick summary of the benefits of the MooTools TabSet class:
I’ve always advocated coding a desired MooTools functionality “inline” before turning it into a class. Here’s the inline MooTools JavaScript code:
$$('ul.tabs').each(function(tabList) {
//get the content list
var tabContentList = tabList.getNext('ul.tabs-content'),
//get the name of the cookie, which is the "title" attribute of the tab list
cookie = 'demo-list',
//the start tab index
startIndex = Cookie.read(cookie) || 0,
//get the actual tab LI items
tabs = tabList.set('title','').getElements('li'),
//get the content LI items
lis = tabContentList.getElements('li'),
//the tab (LI) that is currently active
activeTab = tabs[startIndex].addClass('active'),
//the content LI that is currently active
activeContent = lis[startIndex].setStyle('height','auto');
//for every tab within this tab/content relationship...
tabs.each(function(tab,i) {
//stopper
if(e) e.stop();
//calculate the respective content item's height
var content = lis[i], contentHeight = content.getScrollSize().y;
//add the click event to the tab which...
tab.addEvent('click',function() {
//if it's not the currently activated tab...
if(tab != activeTab) {
//add and remove the active class from old vs. new tab
activeTab.removeClass('active');
(activeTab = tab).addClass('active');
//start the wipe up, wipe down effect
activeContent.set('tween',{
onComplete:function() {
activeContent = content.fade('in').set('tween',{ onComplete: $empty }).tween('height',contentHeight);
}
}).setStyles({
height: contentHeight,
overflow: 'hidden'
}).fade('out').tween('height','0');
//write to cookie
Cookie.write(cookie,i);
//fin!
}
});
});
//fire click event
activeTab.fireEvent('click');
});
Notice that all of the “var” statements at the top either become arguments or options for the class. The transition from inline MooTools JavaScript to a class is extremely simple!

Have more ideas for this class? Be sure to share them in the comments below!
Did you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We’re looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you’re of the ability, please contact Jeffrey at nettuts@tutsplus.com.
Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.
