Javascript And CSS Tutorial - Dynamic Tabbed Panels

Skill

Javascript And CSS Tutorial - Dynamic Tabbed Panels

Posted in:

A classic user interface design element is the tabbed panel - in fact the browser you are using right now probably uses tabs. There are many styles and designs for tabbed panels on web pages as well, but tend to be static. Here, we are going to create a tabbed panel using javascript that you can add tabs to on the fly.

 

Tab Name:
Tab Contents:  

The above is a working example using the code in this tutorial. You can add tabs and panel content by typing them in the corresponding textboxes. Each time the 'Add Tab' button is clicked, a new tab will be added to the control with your content. I placed a limit of 6 tabs on this example, but in reality, you could have an unlimited number of tabs. The names are limited to 6 characters so they do not overflow the width of the tab. In practice, you'd probably want to implement auto ellipsing so long tab names are automatically truncated.

The rest of this tutorial will guide you through the process of creating tab controls similar to the one shown in the above example.

The HTML

We'll start by creating a div that just holds our tabs and panels. My style sheet sets the width of this div to 500 pixels, but that value can be changed to fit your needs.

<div class="tabControl"></div>

Now we need a table that will hold the tabs. The tabs will be stored in a row inside this table called "mainTabArea". The one empty cell already placed in this row is used to fill the rest of the row not currently occupied by tabs. The height of "mainTabArea" controls how tall your tabs will be. For this example, I set them to 20 pixels. The "return false" in the onselectstart event prevents the tab's text from being selected. This is especially important since most browsers will select an element's text when a user double clicks. The tabHolder CSS class also contains a Mozilla specific style for preventing text selection: "-moz-user-select:none".

<div class="tabControl">
  <table class="tabHolder" cellspacing="0" cellpadding="0"
    onselectstart="return false;">
    <tr id="mainTabArea">
      <td style="font-size:1px;">&nbsp;</td>
    </tr>
  </table>
</div>

The next thing we need to do is create a div named "mainPanelArea" to hold the contents of each tab panel. This div is located directly underneath the table holding our tabs and controls how tall your panels will be. For this example, I set the height to 50 pixels.

<div class="tabControl">
  <table class="tabHolder" cellspacing="0" cellpadding="0"
    onselectstart="return false;">
    <tr id="mainTabArea">
      <td style="font-size:1px;">&nbsp;</td>
    </tr>
  </table>
  <div id="mainPanelArea" class="tabPanel">
  </div>
</div>

That's all the HTML needed to create the above tabbed panel example. Of course, this is not the only possible set up for the tabs and the panels. The important thing is to have a table row in which we can insert new tabs, and a div into which we can insert the panels.

The CSS

Below is the style sheet used to create the above example. You can modify any of these values to create your desired tab panel.

.tabControl
{
   width:500px; /* tab control width */
}

.lowTab, .highTab
{
   background-repeat:no-repeat;
   cursor:pointer;
   width:75px; /* tab width */
}

.lowTab
{
   background-image:url('tab_inactive_75x20.gif');
}

.highTab
{
   background-image:url('tab_active_75x20.gif');
}

.tabHolder
{
   width:100%;
   height:20px; /* tab height */
   border-collapse:collapse;
   border-spacing:0px;
   -moz-user-select:none; /* removes text selection */
}

.tabPanel
{
   top:20px; /* should equal tab height */
   left:0px;
   width:100%;
   height:50px; /* tab panel height */
   background-color:#cad7e3;
   border:1px solid #3c597b;
}

The Javascript

Now for the javascript code. We are going to define a javascript class, so that you can have multiple tab panels on a single page (you would just need to instantiate the object multiple times - one for each tabbed panel). First off, the declaration:

function TabbedPanel(controllerName, tabElement, panelElement)

You might wonder 'why is he declaring a function?'. Well, in javascript, the word function is also used to declare classes. You will see more of how this works as we see more of the code. Here, the object takes a unique sting as a name ("controllerName"), the html element for holding the tabs ("mainTabArea"), and the html element for holding the panels ("mainPanelArea").

Now for some fields:

function TabbedPanel(controllerName, tabElement, panelElement)
{
   //Tabbed Panel Name
   this.Name = controllerName;
   //ID of next tab
   this.tabNumber = 0;
   //Current visible panel
   this.currentHighPanel = null;
   //Current raised tab
   this.currentHighTab = null;
   //The panel holder element
   this.panelContainer = panelElement;
   //The tab holder element
   this.tabContainer = tabElement;
   //CSS style for non-selected tabs
   this.lowTabStyle = 'lowTab';
   //CSS style for selected tabs
   this.highTabStyle = 'highTab';
}

Here we are setting a couple of fields to the values passed in, as well as initializing some others. The comments should do a pretty good job explaining what the fields are. All of these fields are public, so they can be modified after the object has been instantiated - such as the css style names.

Now for a long one - let's add a function to the TabbedPanel class called CreateTab. This function takes a string that will be the displayed name of the new tab.

this.CreateTab = function(tabName)
{
  var tabID = this.Name + 'Tab' + this.tabNumber;
  var panelID = this.Name + 'Panel' + this.tabNumber;
       
  var panel = document.createElement('div');
  panel.style.left = '0px';
  panel.style.top = '0px';
  panel.style.width = '100%';
  panel.style.height = '100%';
  panel.style.display = 'none';
  panel.tabNum = this.tabNumber;
  panel.id = panelID;

  if(this.panelContainer.insertAdjacentElement == null)
    this.panelContainer.appendChild(panel)
  else
    this.panelContainer.insertAdjacentElement("beforeEnd", panel); //Internet Explorer

  var cell = this.tabContainer.insertCell(this.tabContainer.cells.length - 1);
  cell.id = tabID;
  cell.className = this.lowTabStyle;
  cell.tabNum = this.tabNumber;
  cell.onclick = this.OnTabClicked;
  cell.innerHTML = tabName;
  cell.panelObj = this;
  this.TabClickEl(cell);  
       
  this.tabNumber++;
       
  return panel;
}

And now for the explanation in English. Let's break this function up and see what each piece does.

First off, we create the ids for the new tab and panel. We want to make sure that they are ids that we can recreate later, so that we can use getElementById - but we also need to make them unique. So we concatenate the tabbed panel name, the word 'Tab' (or 'Panel' if this is the panel id), and a number (which is what is unique to this tab/panel combination).

var tabID = this.Name + 'Tab' + this.tabNumber;
var panelID = this.Name + 'Panel' + this.tabNumber;

Next, we create the panel div. Usually, people just append to the innerHTML of an element to add new html to a page, but in this case we actually need a reference to the created element. So instead, we use the function 'createElement' - which does exactly what the name says. The functions returns the new div element, and in the next few lines we set all of the needed properties. Depending on what you might want to use tabbed panels for, you might want to change or add to those properties.

var panel = document.createElement('div');
panel.style.left = '0px';
panel.style.top = '0px';
panel.style.width = '100%';
panel.style.height = '100%';
panel.style.display = 'none';
panel.tabNum = this.tabNumber;
panel.id = panelID;

Once the panel has been created, we insert it into the DOM. Here we use one method for IE, and a second (standard) method for everybody else (IE, of course, does not implement the standard method).

if(this.panelContainer.insertAdjacentElement == null)
  this.panelContainer.appendChild(panel)
else
  this.panelContainer.insertAdjacentElement("beforeEnd", panel); //Internet Explorer

Next, we create the tab cell for this panel. Here, we use a method available on table rows - insertCell. This takes the position to insert the cell at (in this case we are passing it before the end filler cell described in the HTML section - potentially you could insert it wherever you wanted to) and returns the newly created cell. Again we set a number of the properties of the cell - including the onClick method (which we have not yet defined), the contents of the cell (here the string passed in when CreateTab was called), and a reference to the current object (used later on in the onClick method). After all the properties have been defined, we call the method TabClickEl - which we have not yet defined. It will do the work of raising the given tab cell and its associated panel - because here we assume that the newly created tab/panel should be the visible one.

var cell = this.tabContainer.insertCell(this.tabContainer.cells.length - 1);
cell.id = tabID;
cell.className = this.lowTabStyle;
cell.tabNum = this.tabNumber;
cell.onclick = this.OnTabClicked;
cell.innerHTML = tabName;
cell.panelObj = this;
this.TabClickEl(cell);  

The final work that this method does is increment the tabNumber field (so the next new tab has a unique number) and then return the new panel (so that the caller can fill the new panel with whatever is supposed to go there).

this.tabNumber++;    
return panel;

Another major method in the object is TabClickEl (which is short for TabClickElement, because the argument the function takes is a tab cell element). This function, as mentioned above, does the raising and lowering of tabs and panels.

this.TabClickEl = function (element)
{
  if(this.currentHighTab == element)
    return;

  if(this.currentHighTab != null)
    this.currentHighTab.className = this.lowTabStyle;

  if(this.currentHighPanel != null)
    this.currentHighPanel.style.display = 'none';
     
  this.currentHighPanel = null;
  this.currentHighTab = null;

  if(element == null)
    return;

  this.currentHighPanel = document.getElementById(this.Name + 'Panel' + element.tabNum);
  if(this.currentHighPanel == null)
    return;

  this.currentHighTab = element;
  this.currentHighTab.className = this.highTabStyle;
  this.currentHighPanel.style.display = '';
}

The first if statement does a very simple check - if the tab you are trying to raise is already raised, don't do anything. Otherwise, if there is a currently raised tab, lower it, and if there is a currently visible panel, hide it. Now, if the tab cell passed in is null, don't raise anything. Next try and get the panel associated with the passed in tab - by recreating the panel id, and using getElementById. If the panel doesn't exist, don't raise anything and return. If the panel does exist, set the current high fields to the newly raised tab and panel, set the tab style to the style for raised tabs, and make the panel visible. Nothing particularly surprising here.

Now that we can create and select tabs, lets take a look at how we can close them. Getting rid of the tab and its associated panel is actually pretty simple - the slightly annoying thing is if the tab that is disappearing is the currently selected tab. If it is, then we need to figure out what tab should now become selected.

this.TabCloseEl = function(element)
{
  if(element == null)
    return;
 
  if(element == this.currentHighTab)
  {
    var i = -1;
    if(this.tabContainer.cells.length > 2)
    {
      i = element.cellIndex;
      if(i == this.tabContainer.cells.length-2)
        i--;
      else
        i++;
    }

    if(i >= 0)
      this.TabClickEl(this.tabContainer.cells[i]);
    else
      this.TabClickEl(null);
  }

  var panel = document.getElementById(this.Name + 'Panel' + element.tabNum);
  if(panel != null)
    this.panelContainer.removeChild(panel);

  this.tabContainer.deleteCell(element.cellIndex);
}

The function expects to be handed a tab cell element to deal with (which is why it is called TabCloseEl). First off, if that element is null, then there isn't anything to close, so we return. Next we check to see if the tab being closed is the currently selected tab - and if it is, drop into code to determine what tab will now be selected.

First we create a variable to store the index of the tab that will get selected, and set it to -1 (the 'nothing will get selected' number). Then we check to see if the tab being removed is the last tab left on the tabbed panel (because if it is, there won't be a new tab to select). The way we do this is by checking how many cells are currently contained by the tabContainer - and if it is 2 or less, this is the only cell left. The reason it is 2 or less (not 1 or less) is again because of the filler cell. If there are other cells that can be selected, we set the to-be-selected index variable to the index of the to-be-deleted cell and check to see if it is the right-most cell. If it is we decrement the index, otherwise we increment it. This is because the usual behavior for tabs is to select the tab to the right when the current tab is removed, but if the selected tab is the right-most tab, there is nothing to the right to select - so instead we select the tab to the left.

Now that we have an index, we check to see if it is greater than 0 (i.e., not the -1 'nothing will get selected' value), and if it is, we call TabClickEl on the cell to be selected (which we get by pulling it out of tabContainer using the index). If the index was still -1, we call TabClickEl on 'null' - which causes the current tab to be deslected, but nothing new to be selected.

Now that that is all out of the way, we can actually get around to deleting the tab. We get the panel associated with the tab by recreating the panel's id, do a quick check to make sure it actually exists (because, hey, you never know), and then remove it from the panel container. And finally, we delete the cell from the tab container using the cell index.

One more function to define in this object, and that is the onclick method that is attached to each of the tab cells. It is pretty short, because all it does in the end is call TabClickEl.

this.OnTabClicked = function(event)
{
  // Internet Explorer : Other
  var el = (event.target == null) ? window.event.srcElement : event.target;
  el.panelObj.TabClickEl(el);
}

Here, we first get the target that caused the call of this function (i.e., the tab cell). It is not exactly clean, thanks to IE, which does not follow the standard for getting event targets. Once we have the tab cell, we call TabClickEl on that tab cell. You might wonder why the code doesn't look like:

this.TabClickEl(el);

Well, I wish it did, but unfortunately that does not work. I don't exactly know why, but when the function is called through an event, 'this' is undefined - it seems the event does not know what instance of the class the function was attached to. So instead, we use a workaround. Back in the tab cell creation, we attached a reference to the object to the cell. So here, we use that reference to call the correct TabClickEl function.

And here is what all the javascript code looks like together:

function TabbedPanel(controllerName, tabElement, panelElement)
{
  this.Name = controllerName;
  this.tabNumber = 0;
  this.currentHighPanel = null;
  this.currentHighTab = null;
  this.panelContainer = panelElement;
  this.tabContainer = tabElement;
  this.lowTabStyle = 'lowTab';
  this.highTabStyle = 'highTab';

  this.CreateTab = function(tabName)
  {
    var tabID = this.Name + 'Tab' + this.tabNumber;
    var panelID = this.Name + 'Panel' + this.tabNumber;
    var panel = document.createElement('div');
    panel.style.left = '0px';
    panel.style.top = '0px';
    panel.style.width = '100%';
    panel.style.height = '100%';
    panel.style.display = 'none';
    panel.tabNum = this.tabNumber;
    panel.id = panelID;

    if(this.panelContainer.insertAdjacentElement == null)
      this.panelContainer.appendChild(panel)
    else
      this.panelContainer.insertAdjacentElement("beforeEnd",panel); //Internet Explorer

    var cell = this.tabContainer.insertCell(this.tabContainer.cells.length);
    cell.id = tabID;
    cell.className = this.lowTabStyle;
    cell.tabNum = this.tabNumber;
    cell.onclick = this.OnTabClicked;
    cell.innerHTML = tabName;
    cell.panelObj = this;
    this.TabClickEl(cell);  

    this.tabNumber++;

    return panel;
  }
     
  this.OnTabClicked = function(event)
  {
    // Internet Explorer : Other
    var el = (event.target == null) ? window.event.srcElement : event.target;
    el.panelObj.TabClickEl(el);
  }

  this.TabClickEl = function (element)
  {
    if(this.currentHighTab == element)
      return;

    if(this.currentHighTab != null)
      this.currentHighTab.className = this.lowTabStyle;

    if(this.currentHighPanel != null)
      this.currentHighPanel.style.display = 'none';

    this.currentHighPanel = null;
    this.currentHighTab = null;

    if(element == null)
      return;

    this.currentHighPanel = document.getElementById(this.Name
        + 'Panel' + element.tabNum);

    if(this.currentHighPanel == null)
      return;

    this.currentHighTab = element;
    this.currentHighTab.className = this.highTabStyle;
    this.currentHighPanel.style.display = '';
  }
   
  this.TabCloseEl = function(element)
  {
    if(element == null)
      return;
    if(element == this.currentHighTab)
    {
      var i = -1;
      if(this.tabContainer.cells.length > 2)
      {
        i = element.cellIndex;
        if(i == this.tabContainer.cells.length-2)
          i = this.tabContainer.cells.length-3;
        else
          i++;
      }
             
      if(i >= 0)
        this.TabClickEl(this.tabContainer.cells[i]);
      else
        this.TabClickEl(null);
           
    }

    var panel = document.getElementById(this.Name + 'Panel' + element.tabNum);
    if(panel != null)
      this.panelContainer.removeChild(panel);
       
    this.tabContainer.deleteCell(element.cellIndex);
  }
}

Putting It All Together

So how do we use the TabbedPanel object to dynamically create tabs? Once the above code is implemented, creating tabs is a very simple process. Let's start with the HTML created at the beginning of this tutorial.

<div class="tabControl">
  <table class="tabHolder" cellspacing="0" cellpadding="0"
    onselectstart="return false;">
    <tr id="mainTabArea">
      <td style="font-size:1px;">&nbsp;</td>
    </tr>
  </table>
  <div id="mainPanelArea" class="tabPanel">
  </div>
</div>

The only thing left to do is create an instance of the TabbedPanel class and start creating tabs.

//create the TabbedPanel object
var tabController = new TabbedPanel('tabControllerId',
    document.getElementById('mainTabArea'), document.getElementById('mainPanelArea'));
     
//create a tab named 'Example'
var newTabPanel = tabController.CreateTab('Example');
   
//fill the contents of the new tab's panel
newTabPanel.innerHTML = "The example tab's contents";
 
//close the currently selected tab
tabController.CloseTabEl(tabController.currentHighTab);

This should give you a jumping off point to do whatever you want to do with tabbed panels. If you'd like to see these tabs in action, Gaming Textures uses tab controls very similar to those created in this tutorial.

Manpreet
07/31/2007 - 08:25

thanks for the code. i was working all night to get this things done. finally i got this.

thanks!

reply

Tvks
11/19/2007 - 02:45

Hi,
This is indeed a great job..

Would have been more happy if you had given a sample code for download (along the js and css file).

Regards,

reply

Its wonderful
12/20/2007 - 07:34

Its wonderful but i cann't download the code(java script) and css from the 'here' option

reply

The Fattest
12/20/2007 - 10:03

This has been fixed you should now be able to obtain the code from those links.

reply

Anonymous
01/03/2008 - 10:00

hello im new to css and designing..
pls help on creating css for selected tab panels content,

i already have the css for selected tab but i don't know how to create the css for the selected contents..

example:
content1, content2, content3
when user selects content one it should change its color..different from the other,..

any help is appreciated..
thanks

reply

Khurram
01/14/2008 - 07:05

This looks very good, I am planning to play with code to implement my changes...

reply

Kanchan
05/09/2008 - 05:49

I reallly like your tabbed panels. Really very nice.

reply

Website Design
05/12/2008 - 07:42

Thanks, this looks useful and the source is very clean.

reply

Airwinx
06/29/2008 - 10:49

Hi, thank, i was looking for this exmaple for thousands of hrs...haha
finally you did it.....

reply

Gajender singh
07/08/2008 - 06:20

hi, This is Gajender singh, new in web design and this will really help me a lot
Thanks buddy

reply

Toan Dang
07/23/2008 - 03:59

It's cool.

Thanks you.

reply

israelquality
08/29/2009 - 02:29

hi
I think I know to resolve the issue you mentioned "you don't exactly know why..." , It is related to lower level language basis for class development.
for example C/C++ code , events attached to webbrowser as static functions. static functions are somewhat global , therfore not related to any of the specific instantiated objects of the class.
thw webbrowsers expose several function prototypes that must be obayed and followed by other development based on them. the prototype of events are mandatory static, any developer thet wish to attach an event for this browser must use it statically. no this operator for global functions (thay are not related to the object).

reply

Luis
09/07/2009 - 13:05

Hi

First of all thanks a lot for the code.

Secondly, I would like to know.. How can I maintain all tabs created once I push "F5"?

I'm trying to control that but I don't know how to cope with it, I mean imagine I create 3 tabs, after that when I push "F5" 2 of them disappear.

Thanks in advance

reply

Dutch
09/23/2009 - 14:58

How do you download the code?

reply

The Reddest
09/23/2009 - 15:22

Under the 'Source Files' section at the bottom of the post (right above the comments). The javascript for this tutorial is inside that HTML file.

reply

Luis
10/05/2009 - 00:42

Thank you very much for your reply.

I got the code properly, my English maybe is not as well as I expected and probably you didn't understand me.

I've seen the example and I got the code.

What I want to know , if it is possible, How can I maintain all tabs created once I refresh the screen?

If you create a few tabs and refresh the screen all tabs created disappear.

Could it be possible once you refresh the screen not to lose tabs and information inside?

reply

pityocamptes
12/18/2009 - 16:13

This is great! Thanks!

One quick question... instead of inserting TEXT into the panel, how would I insert either one or more (all within the same panel) java scripts? I was looking at inserting javascripts that show current financials and news. Some of my panels may only have one script, while another may contain one, two, or three, etc. - I wonder how you would format them, etc. Anyways, thoughts appreciated. Thanks!

reply

Anonymous
01/26/2010 - 00:49

putang ina mo!

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors