Unobtrusive Tabbed Content

Bobby van der Sluis has been making a great many inroads into the world of unobtrusive JavaScript. I recently adapted one of his scripts for use on a project and thought I’d share a couple of simple modifications that I found useful.

Showing the First Section

The original code includes an initialization function that looks like this:

function initShowHide() {
    // Hide all toggleable sections with JavaScript for the improbable case that CSS is disabled
    // Note that in this case the 'flash of visible content' still will occur            
    hide();
    var toggle = document.getElementById('toggle');
    var as = toggle.getElementsByTagName('a');
    for (var i = 0; i < as.length; i++) {
        as[i].onclick = function() {
            show(this);
            return false;
        }
    }
}

This function hides all of the elements inside of the div with the id toggleable and assigns an onclick function to links inside of the div with id toggle. However, it is common to want to start with one of the sections showing. To do so we add a line to the end of the initShowHide() function:

show(as[0]);


This code takes the first element in the ‘as’ array and calls the show function as though the link had been clicked. This will toggle the first div nested with the div with id toggleable to visible.

The new initShowHide() function looks like:

function initShowHide() {
    // Hide all toggleable sections with JavaScript for the improbable case that CSS is disabled
    // Note that in this case the 'flash of visible content' still will occur         
    hide();
    var toggle = document.getElementById('toggle');
    var as = toggle.getElementsByTagName('a');
    for (var i = 0; i < as.length; i++) {
        as[i].onclick = function() {
            show(this);
            return false;
        }
    }
    // show first element by default
    show(as[0]);
}

The Style

In addition to this I found that when JavaScript was disabled and all of your tabbed sections were shown it was difficult to tell where one section ended and another began. In my case it was a series of fieldsets in a form that were repeated for each tab. When JavaScript was disabled and they were all shown at once it was very confusing to read. Since the tabs act as headers when JavaScript is enabled I didn’t want redundant headers showing just beneath them. The solution lay in adding to the CSS file that is linked using a document.write().

In my markup I added a new header element at the top of each div that I would be toggling. At the top of Mr. van der Sluis’s code there are a few lines that are run when a page is visited:

if (document.getElementById && document.getElementsByTagName && document.createTextNode) {
    document.write('<link rel="stylesheet" type="text/css" href="../css/js_hide.css" />');
    window.onload = initShowHide;
}

The document.write() links to another .css file that is only used if the JavaScript that toggles the sections will be working. I added a quick declaration to this CSS file to hide my redundant headings when the tabbed interface was working.

#toggleable h5 {
    display: none;
}

That left me with nice headings breaking up each section when JavaScript was disabled and the tabs acting as the headings when in was working.

Showing Nested divs

If you look at the hide() function you’ll see that it gets all of the divs inside of the div with id toggleable and sets their display property to none. However, the show() function looks for the div with and id matching the link that is clicked and sets the display property of just that div to a value of block. This means that the show() function does not make visible any nested divs that the hide() function removed.

To correct this problem we update the show function with a few extra lines:

var nested = document.getElementById(id).getElementsByTagName('div');
for (var i = 0; i < nested.length; i++) {
    nested[i].style.display = 'block';
}

This code creates an array of the divs nested inside of the div with an id matching the tab link that was clicked. It then loops through the array and sets each of divs’ display property to a value of block. The final show() function should look like:

function show(s) {
    hide();
    var id = s.href.match(/#(\w.+)/)[1];
    document.getElementById(id).style.display = 'block';
    // Try to get a reference to an element with the id 'active_tab' (a previously active tab header)
     var oldActive = document.getElementById('active_tab');
    // If this element exists, remove its ID attribute
    if (oldActive) oldActive.removeAttribute('id');
    // set clicked link as id  = active_tab
    s.setAttribute('id', 'active_tab');
    // get all divs inside of element id and display them
    var nested = document.getElementById(id).getElementsByTagName('div');
    for (var i = 0; i < nested.length; i++) {
        nested[i].style.display = 'block';
    }
}

Now any nested divs you have with your container divs should appear and disappear properly as well. This makes it possible to contain larger and more complex sections of a page within your toggleable div section.

The final .js and .css files are below for your use.

js_hide.css

#toggleable div {
    display: none;
}
#toggleable h5 {
    display: none;
}

show_hide.js

/*
This code is based on a code example from the article "Javascript navigation - cleaner, not meaner" 
by Christian Heilmann
URL: http://www.evolt.org/article/Javascript_navigation_cleaner_not_meaner/17/60273/index.html
*/

// If there is enough W3C DOM support for all our show/hide behavior:
// 1. Call the stylesheet that by default hides all toggleable sections
// 2. Apply the show/hide behavior by calling the initialization function
if (document.getElementById && document.getElementsByTagName && document.createTextNode) {
    document.write('<link rel="stylesheet" type="text/css" href="../css/js_hide.css" />');
    window.onload = initShowHide;
}

function initShowHide() {
    // Hide all toggleable sections with JavaScript for the improbable case that CSS is disabled
    // Note that in this case the 'flash of visible content' still will occur           
    hide();
    var toggle = document.getElementById('toggle');
    var as = toggle.getElementsByTagName('a');
    for (var i = 0; i < as.length; i++) {
        as[i].onclick = function() {
            show(this);
            return false;
        }
    }
    // show first element by default
    show(as[0]);
}

function show(s) {
    hide();
    var id = s.href.match(/#(\w.+)/)[1];
    document.getElementById(id).style.display = 'block';
    // Try to get a reference to an element with the id 'active_tab' (a previously active tab header)
     var oldActive = document.getElementById('active_tab');
    // If this element exists, remove its ID attribute
    if (oldActive) oldActive.removeAttribute('id');
    // set clicked link as id  = active_tab
    s.setAttribute('id', 'active_tab');
    // get all divs inside of element id and display them
    var nested = document.getElementById(id).getElementsByTagName('div');
    for (var i = 0; i < nested.length; i++) {
        nested[i].style.display = 'block';
    }
}

function hide() {
    var toggleable = document.getElementById('toggleable').getElementsByTagName('div');
    for (var i = 0; i < toggleable.length; i++) {
        toggleable[i].style.display = 'none';
    }
}

Kevin Hall

Kevin Hall

I am one of the owners of Infinite Web Design. I'll be glad to talk with you about who we are, what we do, or any of the topics we write about here.
Kevin Hall

Latest posts by Kevin Hall (see all)