2002-02-17: Original version posted.
2003-03-03: 1.02 - Fixes IE memory leakage.
2006-05-26: Changed license to Apache Software License 2.0.

Introduction

You might remember that we used to have a pretty good tab strip control here at WebFX a long time ago. This control was removed when we redesigned the site with the argument that we should fix it to work in Mozilla. Now, more than three years later we finally got down and created a tab pane control that we think is a worthy replacement.

This Tab Pane control is fairly similar to the tab system Tim Scarfe created for developer-x.com and the basic idea is the same. That idea is to be able to use a normal XHTML document structure and if the browser supports DOM level 1 then the structure of the elements is slightly adjusted and a the className is changed for the tab pane container so that the css rules specially defined for the tab are applied.

The Tab Pane has support for persistence using cookies so that you can navigate between pages without having to reselect the selected tab for all your tab panes in your document. The look and feel is entirely decided by CSS so it is fairly easy to create the look and feel you need for your web application. As of this writing there are three different styles available; Luna, Windows Classic and the WebFX look and feel that this pane is currently using. See the demo page for the look and feel of the other two.

Usage

Include the Files

To use the tab pane you should include two files. One JavaScript file and on css file.

<script type="text/javascript" src="js/tabpane.js"></script>
<link type="text/css" rel="StyleSheet" href="css/tab.webfx.css" />

The XHTML Structure

The basic structure for the tab pane consists of an element with the class name tab-pane that in turn contains elements with the class names tab-page. The tab page element should contain one element with the class name tab. This last one should preferably be a header of some kind in case the browser does not support DOM1 and therefore will leave the structure intact. The other two elements can be almost anything but a div element is good because it usually does not change the rendering.

Below is the code for a simple tab pane with the tab pages.

<div class="tab-pane" id="tab-pane-1">

   <div class="tab-page">
      <h2 class="tab">General</h2>

      This is text of tab 1. This is text of tab 1.
      This is text of tab 1. This is text of tab 1.

   </div>

   <div class="tab-page">
      <h2 class="tab">Privacy</h2>

      This is text of tab 2. This is text of tab 2.
      This is text of tab 2. This is text of tab 2.

   </div>
</div>

Notice that the id is not needed unless two or more tab panes are present in the same document and you are using the persistence feature.

Initialization

The code above is a complete working tab pane. You do not have to add any more js code but there are a few good reasons why you would want to do this. If no js code is added all the tab panes in the document are initialized when the document is loaded. If you have lots of text and/or images this will take quite some time and the layout of the page will feel jerky. A better way is to call the function setupAllTabs after all your XHTML has been defined. This works much better but if you have a lot of text this is not optional either because the browser might render some of the text before the entire tab structure is available.

      ...
   </div>
</div>
<!-- tab pane closed -->

<script type="text/javascript">
setupAllTabs();
</script>

The best way to go is to create as much as possible as soon as possible. This involves adding calls to js after the tab pane is opened and as soon as every page is opened.

<div class="tab-pane" id="tab-pane-1">

<script type="text/javascript">
var tabPane1 = new WebFXTabPane( document.getElementById( "tab-pane-1" ) );
</script>

   <div class="tab-page" id="tab-page-1">
      <h2 class="tab">General</h2>

      <script type="text/javascript">
      tabPane1.addTabPage( document.getElementById( "tab-page-1" ) );
      </script>

      This is text of tab 1. This is text of tab 1.
      This is text of tab 1. This is text of tab 1.

   </div>

   <div class="tab-page" id="tab-page-2">
      <h2 class="tab">Privacy</h2>

      <script type="text/javascript">
      tabPane1.addTabPage( document.getElementById( "tab-page-2" ) );
      </script>

      This is text of tab 2. This is text of tab 2.
      This is text of tab 2. This is text of tab 2.

   </div>
</div>

The code for this is, as you can see, not half as nice and you should decide from time to time if you really need this. In most web applications (especially intranet apps) this is not needed because the amount of data inside each tab page is limited (or added later).

One thing to note about this last method is that some browser have trouble changing the content model during the page load (noticeably Konqueror).

API

WebFXTabPane

This is the class representing a tab pane.

Syntax

new WebFXTabPane(oElement [, bUseCookie])

Parameters

Name Type Descripton
oElement HTMLElement The html element that represents the tab pane
bUseCookie Boolean Optional. If this is set to true then the selected tab is persisted. The default value is true.

Static Methods

Name Description
setCookie

Syntax

object.setCookie(sName, sValue [, nDays])

Arguments

Name Type Descripton
sName String The name of the cookie
sValue String The value of the cookie
nDays Number Optional. The number of days to store the cookie

Return Type

void

Sets a cookie
getCookie

Syntax

object.getCookie(sName)

Arguments

Name Type Descripton
sName String The name of the cookie

Return Type

String

Retrieves a cookie by name
removeCookie

Syntax

object.removeCookie(sName)

Arguments

Name Type Descripton
sName String The name of the cookie to remove

Return Type

void

Removes a cookie by name

Static Fields

Name Type Descripton
None.

Methods

Name Description
addTabPage

Syntax

object.addTabPage(oElement)

Arguments

Name Type Descripton
oElement HTMLElement The html element that represents the tab page

Return Type

WebFXTabPage

Adds a tab page by passing an html element
getSelectedIndex

Syntax

object.getSelectedIndex()

Arguments

No Arguments.

Return Type

Number

The index of the selected tab page
setSelectedIndex

Syntax

object.setSelectedIndex(n)

Arguments

Name Type Descripton
n Number The index of the tab page to select

Return Type

void

Sets the selected tab page by index

Fields

Name Type Descripton
classNameTag String This string is added to the class name to tag the tab pane as beeing created
element HTMLElement Read only.The html element being that represents the tab pane
pages WebFXTabPages[] Read only.An array containing the tab pages
selectedIndex Number Read only.The index of the selected tab page
tabRow HTMLElement Read only.The html element that encloses all tabs
useCookie Boolean Is used to decide if the selected tab page index should be persisted using a cookie.

Remarks

None.

WebFXTabPage

This is the class representing a tab page.

Syntax

new WebFXTabPage(oElement, oTabPane, nIndex)

Parameters

Name Type Descripton
oElement HTMLElement The html element that represents the tab page
oTabPane WebFXTabPane The tab pane to add the page to
nIndex Number The index of the tab page

Static Methods

Name Description
None.

Static Fields

Name Type Descripton
None.

Methods

Name Description
hide

Syntax

object.hide()

Arguments

No Arguments.

Return Type

void

Hides the tab page
select

Syntax

object.select()

Arguments

No Arguments.

Return Type

void

Selects the tab page
show

Syntax

object.show()

Arguments

No Arguments.

Return Type

void

Makes the tab page visible

Fields

Name Type Descripton
element HTMLElement Read only.The html element being used as the page
index Number Read only. The index of the tab page in the tab pane pages array.
tab HTMLElement Read only.The html element being used as the tab.

Remarks

Do not use this constructor manually. Use addTabPage of the WebFXTabPane class instead.

Globals

Functions

Name Description
hasSupport

Syntax

hasSupport()

Arguments

No Arguments.

Return Type

Boolean

Returns whether the browser is supported or not
setupAllTabs

Syntax

setupAllTabs()

Arguments

No Arguments.

Return Type

void

Initializes all tab panes and tab pages that have not been initialized already.

Objects

Name Type Descripton
None.

Implementation

Check for support

The way to check the browser whether it support a certain feature in the DOM is to use the method document.implementation.hasFeature. However since IE5.5 supports all the features that this script needs but it does not support this way of checking for support we have to add a separate check for IE55.

function hasSupport() {

   if (typeof hasSupport.support != "undefined")
      return hasSupport.support;

   var ie55 = /msie 5\.[56789]/i.test( navigator.userAgent );

   hasSupport.support = ( typeof document.implementation != "undefined" &&
         document.implementation.hasFeature( "html", "1.0" ) || ie55 )

   // IE55 has a serious DOM1 bug... Patch it!
   if ( ie55 ) {
      document._getElementsByTagName = document.getElementsByTagName;
      document.getElementsByTagName = function ( sTagName ) {
         if ( sTagName == "*" )
            return document.all;
         else
            return document._getElementsByTagName( sTagName );
      };
   }

   return hasSupport.support;
}

As you can see in the code above IE55 has a bug an therefore we also patch that. Too many people are still using IE55 to just ignore it.

WebFXTabPane

The constructor for the tab pane creates the tabRow div that is used to place all the actual tabs in. It also checks the cookie state so that the selected tab can be persisted. Besides from this it sets up some properties needed to keep track of the states. Last but not least it checks the childNodes of the element and adds the found tab pages.

function WebFXTabPane( el, bUseCookie ) {
   if ( !hasSupport() || el == null ) return;

   this.element = el;
   this.element.tabPane = this;
   this.pages = [];
   this.selectedIndex = null;
   this.useCookie = bUseCookie != null ? bUseCookie : true;

   // add class name tag to class name
   this.element.className = this.classNameTag + " " + this.element.className;

   // add tab row
   this.tabRow = document.createElement( "div" );
   this.tabRow.className = "tab-row";
   el.insertBefore( this.tabRow, el.firstChild );

   var tabIndex = 0;
   if ( this.useCookie ) {
      tabIndex = Number( WebFXTabPane.getCookie( "webfxtab_" + this.element.id ) );
      if ( isNaN( tabIndex ) )
         tabIndex = 0;
   }
   this.selectedIndex = tabIndex;

   // loop through child nodes and add them
   var cs = el.childNodes;
   var n;
   for (var i = 0; i < cs.length; i++) {
      if (cs[i].nodeType == 1 && cs[i].className == "tab-page") {
         this.addTabPage( cs[i] );
      }
   }
}

There are a few methods added to the WebFXTabPane class and one of the more important ones is the method addTabPage. This method takes the element that represents the tab page and uses that to create a WebFXTabPage object that is added to the pages array. Once the tab page has been added it also checks if this page is the selected one and if it is it shows it.

WebFXTabPane.prototype = {

   ...

   addTabPage:   function ( oElement ) {
      if ( !hasSupport() ) return;

      if ( oElement.tabPage == this )   // already added
         return oElement.tabPage;

      var n = this.pages.length;
      var tp = this.pages[n] = new WebFXTabPage( oElement, this, n );
      tp.tabPane = this;

      // move the tab out of the box
      this.tabRow.appendChild( tp.tab );

      if ( n == this.selectedIndex )
         tp.show();
      else
         tp.hide();

      return tp;
   }
};

WebFXTabPage

This class is used to keep track of the actual tab page. Once created it moves the tab element to the tabRow of the tab pane. It also adds an anchor around the text so that the user can use the keyboard to activate the tabs.

function WebFXTabPage( el, tabPane, nIndex ) {
   if ( !hasSupport() || el == null ) return;

   this.element = el;
   this.element.tabPage = this;
   this.index = nIndex;

   var cs = el.childNodes;
   for (var i = 0; i < cs.length; i++) {
      if (cs[i].nodeType == 1 && cs[i].className == "tab") {
         this.tab = cs[i];
         break;
      }
   }

   // insert a tag around content to support keyboard navigation
   var a = document.createElement( "A" );
   a.href = "javascript:void 0;";
   while ( this.tab.hasChildNodes() )
      a.appendChild( this.tab.firstChild );
   this.tab.appendChild( a );

   // hook up events, using DOM0
   var oThis = this;
   this.tab.onclick = function () { oThis.select(); };
   this.tab.onmouseover = function () { WebFXTabPage.tabOver( oThis ); };
   this.tab.onmouseout = function () { WebFXTabPage.tabOut( oThis ); };
}

Initialization

The initialization uses the global function setupAllTabs that goes through all elements and checks their class names and if the class names match the classes used by the tab pane controls it checks whether this element belongs to an uninitialized control and in that case it initializes it now.

function setupAllTabs() {
   if ( !hasSupport() ) return;

   var all = document.getElementsByTagName( "*" );
   var l = all.length;
   var tabPaneRe = /tab\-pane/;
   var tabPageRe = /tab\-page/;
   var cn, el;
   var parentTabPane;

   for ( var i = 0; i < l; i++ ) {
      el = all[i]
      cn = el.className;

      // no className
      if ( cn == "" ) continue;

      // uninitiated tab pane
      if ( tabPaneRe.test( cn ) && !el.tabPane )
         new WebFXTabPane( el );

      // unitiated tab page wit a valid tab pane parent
      else if ( tabPageRe.test( cn ) && !el.tabPage &&
               tabPaneRe.test( el.parentNode.className ) ) {
         el.parentNode.tabPane.addTabPage( el );
      }
   }
}

This function can be called manually at any time but the script makes hooks to the load event for the window. This is done using DOM level 2 events if available. If not we test if it supports the IE5 way of attaching events and last we fall back on classic way of setting events.

// DOM2
if ( typeof window.addEventListener != "undefined" )
   window.addEventListener( "load", setupAllTabs, false );

// IE
else if ( typeof window.attachEvent != "undefined" )
   window.attachEvent( "onload", setupAllTabs );

else {
   if ( window.onload != null ) {
      var oldOnload = window.onload;
      window.onload = function ( e ) {
         oldOnload( e );
         setupAllTabs();
      };
   }
   else
      window.onload = setupAllTabs;
}

Look & Feel

The structure

To be able to change the look and feel one needs to understand the structure of the tab pane. When the original XHTML source tree is transformed into the tab pane the class name of the element representing the tab pane is tagged with the property classNameTag. The default tag is dynamic-tab-pane-control and therefore all your css rules should take this into account. If you want different look on different tab panes in the same document this tag can be changed to make the css rules easier to set up.

<div class="dynamic-tab-pane-control tab-pane" id="tab-pane-1">
   <div class="tab-row">
      <h2 class="tab selected"><a ... >General</a></h2>
      <h2 class="tab hover"><a ... >Privacy</a></h2>
   </div>
   <div class="tab-page">

      This is text of tab 1. This is text of tab 1.
      This is text of tab 1. This is text of tab 1.

   </div>

   <div class="tab-page">

      This is text of tab 2. This is text of tab 2.
      This is text of tab 2. This is text of tab 2.

   </div>
</div>

The selected tab will have the class name tab selected and the tab that the mouse hovers over will have the class name tab hover. If the selected tab is hovered it will have the class name tab selected hover. These rules allow you to differentiate the look of tabs between the different states.

The CSS Rules

Here we will walk through the Windows Classic css file. First we set the width and position of the tab pane to prevent a few rendering bugs in IE6.

.dynamic-tab-pane-control.tab-pane {
   position:        relative;
   width:           100%;
}

.dynamic-tab-pane-control .tab-row {
   z-index:         1;
   white-space:     nowrap;
}

Then we setup the css for the tab. Notice how the position is set to relative to allow the top position to be slightly changed and to allow the z-index property to be changed to position the tabs below the tab pages.

.dynamic-tab-pane-control .tab-row .tab {
   font:            Menu;
   cursor:          Default;
   display:         inline;
   margin:          1px -2px 1px 2px;
   float:           left;
   padding:         2px 5px 3px 5px;
   background:      ThreeDFace;
   border:          1px solid;
   border-color:    ThreeDHighlight ThreeDDarkShadow
                    ThreeDDarkShadow ThreeDHighlight;
   border-bottom:   0;
   z-index:         1;
   position:        relative;
   top:             0;
}

For the selected tab we set the z-index to 3 to put it above the tab pages. We also move it a little and change some other properties to make it look more like the classic window tab control.

.dynamic-tab-pane-control .tab-row .tab.selected {
   border-bottom:   0;
   z-index:         3;
   padding:         2px 6px 5px 7px;
   margin:          1px -3px -2px 0px;
   top:             -2px;
}

Then we override the text properties on the tabs as well as for the .hover rule.

.dynamic-tab-pane-control .tab-row .tab a {
   font:            Menu;
   color:           WindowText;
   text-decoration: none;
   cursor:          default;
}

.dynamic-tab-pane-control .tab-row .hover a {
   color:           blue;
}

Then we set the z-index for the tab pages to 2 so that it will be shown above tabs but below the selected tab. We also set the borders and and a few other properties.

.dynamic-tab-pane-control .tab-page {
   clear:           both;
   border:          1px solid;
   border-color:    ThreeDHighlight ThreeDDarkShadow
                    ThreeDDarkShadow ThreeDHighlight;
   background:      ThreeDFace;
   z-index:         2;
   position:        relative;
   top:             -2px;
   color:           WindowText;
   font:            MessageBox;
   font:            Message-Box;
   padding:         10px;
}

Tab Pane
Usage
API
Implementation
Look & Feel
Demo
Download

Author: Erik Arvidsson