O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

13.7. Creating Tab Pages

Problem

You have divided content that you want to hide or display based on the web page reader’s actions.

Solution

Create a tabbed page effect and hide or display tab pages based on clicking the tab label:

// click on tab
function displayPage() {
  var current = this.parentNode.getAttribute("data-current");
  document.getElementById("tabnav_" + current).setAttribute("style",
                          "background-color: #fff");
  document.getElementById("tabpage_" + current).style.display="none";

  var ident = this.id.split("_")[1];
  this.setAttribute("style","background-color: #f00");
  document.getElementById("tabpage_" + ident).style.display="block";
  this.parentNode.setAttribute("data-current",ident);
}

Discussion

Tabbed pages have been popular for some time, and rightfully so. They’re a great way to make use of limited web page space in a manner that’s intuitive—we’re all familiar with tabs from other applications, including browsers.

The tabbed page concept is simple: display a list of tabs to click on the top, and pages underneath. All tabs are shown, but only one page is shown at a time. Clicking any of the tabs resets the page:

  • The highlighted tab is changed to the one just clicked.

  • The currently displayed page is hidden or set to nondisplay.

  • The clicked tab’s style is changed (so it’s highlighted).

  • The associated content page is displayed.

In the solution, part of a tabbed page application is shown: the part that demonstrates what happens when you click the tab. In this case, the tab is associated by an identifier number attached to the end of the tab identifier for the associated tab page, so there doesn’t have to be any form of container relation between the two. You want to avoid a container relationship between tab and page as much as possible, because it makes it difficult to ensure the page displays well when JavaScript is turned off.

The current tab picked is stored in the parent node’s custom data attribute, data-current. Using a custom data-* attribute, as these values are called, means we can avoid global variables. The next time a tab is clicked, it’s easy to find the current selection in order to reset the page.

Note

The custom data-* attributes were introduced with HTML5. You can use any name that begins with data-, and the page is still considered conforming to HTML5.

There are probably dozens of different approaches you can use to create tabbed pages, including making your own library, or using another that can be dropped into a page to automatically build the pages based on class settings.

The approach in Example 13-4 incorporates the approach outlined in the solution. It also makes use of the fact that elements form their own document trees, and that we can query an element tree the same as we can query the document tree. Using this approach, we can add as many tabbed page containers to the page as we wish. For each container, the application displays the navigation bar, turns off the display for all of the pages except the first, and highlights the first tab, and then adds the onclick event handler to each tab.

Example 13-4. Creating a reusable, multiple-count, tabbed page application

<!DOCTYPE html>
<head>
<title>Tabbed Pages</title>
<style>
  .tabcontainer
  {
    padding: 5px; width: 500px;
    margin: 20px;
  }
  .tabnavigation ul
  {
    padding: 0; margin: 0; display: none;
  }
  .tabnavigation ul li
  {
    padding: 3px; display: inline;
    border: 1px solid #000; background-color: #fff;
  }
  .tabnavigation ul li:hover
  {
    cursor: pointer;
  }
  .tabpages
  {
    position: relative; z-index: 2;
    border: 1px solid #000; background-color: #fff;
  }
  .tabpage
  {
    margin: 0 10px;
  }
</style>
<script>

// set up display
// for each container display navigation
// hide all but first page, highlight first tab
window.onload=function() {

  // for each container
  var containers = document.querySelectorAll(".tabcontainer");
  for (var j = 0; j < containers.length; j++) {

    // display and hide elements
    var nav = containers[j].querySelector(".tabnavigation ul");
    nav.style.display="block";

    // set current tab
    var navitem = containers[j].querySelector(".tabnavigation ul li");
    var ident = navitem.id.split("_")[1];
    navitem.parentNode.setAttribute("data-current",ident);
    navitem.setAttribute("style","background-color: #f00");

    var pages = containers[j].querySelectorAll(".tabpage");
    for (var i = 1; i < pages.length; i++) {
      pages[i].style.display="none";
    }

    var tabs = containers[j].querySelectorAll(".tabnavigation ul li");
    for (var i = 0; i < tabs.length; i++) {
      tabs[i].onclick=displayPage;
    }
  }
}

// click on tab
function displayPage() {
  var current = this.parentNode.getAttribute("data-current");
  document.getElementById("tabnav_" + current).setAttribute("style",
"background-color: #fff");
  document.getElementById("tabpage_" + current).style.display="none";

  var ident = this.id.split("_")[1];
  this.setAttribute("style","background-color: #f00");
  document.getElementById("tabpage_" + ident).style.display="block";
  this.parentNode.setAttribute("data-current",ident);
}
</script>
</head>
<body>
<div class="tabcontainer">
   <div class="tabnavigation">
      <ul>
         <li id="tabnav_1">Page One</li>
         <li id="tabnav_2">Page Two</li>
         <li id="tabnav_3">Page Three</li>
      </ul>
   </div>

   <div class="tabpages">
      <div class="tabpage" id="tabpage_1">
         <p>page 1</p>
      </div>
      <div class="tabpage" id="tabpage_2">
         <p>page 2</p>
      </div>
      <div class="tabpage" id="tabpage_3">
         <p>page 3</p>
      </div>
   </div>
</div>
<div class="tabcontainer">
   <div class="tabnavigation">
      <ul>
         <li id="tabnav_4">Page Two One</li>
         <li id="tabnav_5">Page Two Two</li>
       </ul>
    <div>

    <div class="tabpages">
       <div class="tabpage" id="tabpage_4">
          <p>Page 4</p>
       </div>
       <div class="tabpage" id="tabpage_5">
          <p>Page 5</p>
       </div>
    </div>
</div>
</body>

Figure 13-4 shows the application with two containers, different tabbed pages open in each. The application works with Chrome, Firefox, Opera, Safari, and IE8. It doesn’t work with IE7 because of the use of querySelectorAll.

A tabbed page application with two containers, and different tabbed pages open in each

Figure 13-4. A tabbed page application with two containers, and different tabbed pages open in each

See Also

See Recipe 14.8 for how to make tabbed pages accessible using ARIA roles and attributes.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required