Skip navigation.

Site Navigation

Here is the JavaScript code that produces the site navigation on each page.

/*
 * navigation.js - create table of contents, page navigation and site navigation menu.
 *
 * This script is based on quirksmode.js by Peter-Paul Koch,
 * see http://www.quirksmode.org/
 *
 * date: 20 oct 2005
 * authors: Peter-Paul Koch, Martin Moene
 */

/**
 * To do:
 *
 * - clean up stays focussed.
 * - display: list-item doesn't have the desired effect in IE5
 * - setNav should be less sensitive to the form of the URL given
 * - add separator lines to sitemap and -menu?
 */

/**
 * Node types:
 * 1. element_node
 * 2. attribute_node
 * 3. text_node
 * 4. cdata_section_node
 * 5. entity_reference_node
 * 6. entity_node
 * 7. processing_instruction_node
 * 8. comment_node
 * 9. document_node
 */

/**
 * when page is loaded, create table of contents and the site navigation bar:
 */
window.onload        = initialize;
window.onunload      = finalize;
// window.onbeforeprint = initializePrint;
// window.onafterprint  = finalizePrint;

var bugRiddenCrashPronePieceOfJunk = ( navigator.userAgent.indexOf('MSIE 5') != -1
                                    && navigator.userAgent.indexOf('Mac')    != -1 );

var W3CDOM = ( ! bugRiddenCrashPronePieceOfJunk
             &&  document.getElementById
             &&  document.getElementsByTagName
             &&  document.createElement          );

/**
 * initialize module.
 */
function initialize()
{
//alert( 'initialize' );
   if ( !W3CDOM )
   {
      alert( 'Browser does not support W3DOM' );
   }

//    // http://dean.edwards.name/weblog/2005/09/busted/
//    // quit if this function has already been called
//    if (arguments.callee.done)
//       return;
// 
//    // flag this function so we don't do the same thing twice
//    arguments.callee.done = true;

   editLinks();
   createTableOfContents();
   createPageNavigation();
   createSiteNavigation();
}

/**
 * finalize module.
 */
function finalize()
{
//alert( 'finalize' );
   top.setNav = '';

   if ( self.exit )
      self.exit();
}

// /**
//  * show urls before printing.
//  */
// var expandedURLs = [];
// 
// function initializePrint()
// {
//    expandedURLs = [];
// 
//    var links = document.getElementsByTagName( 'a' );
// 
//    if ( !links )
//    {
//       return ;
//    }
// 
//    for ( var i = 0; i < links.length; ++i )
//    {
//       if ( links[i].className.hasClass( 'external' )
//       && ! links[i].className.hasClass( 'free'     ) )
//       {
//          var span = document.createElement( 'span' );
//          var text = document.createTextNode( ' (' + links[i].href + ') ' );
//          span.appendChild( text );
//          links[i].parentNode.insertBefore( span, links[i].nextSibling );
//          expandedURLs[i] = span;
//       }
//    }
// }

// /**
//  * remove urls after printing.
//  */
// function finalizePrint()
// {
//    for ( var i = 0; i < expandedURLs.length; ++i )
//    {
//       if ( expandedURLs[i] )
//       {
//          expandedURLs[i].removeNode( true );
//       }
//    }
// }

/**
 * go through all links.
 * - links with the external attribute get a m2 target
 * - links with the popup attribute get an onclick handler and get (popup) appended
 * - links with a hreflang attribute get an node with (lang=ll) as new sibling
 */
function editLinks()
{
   var langspan       = document.createElement( 'span' );
   langspan.className = 'smaller lang';

   var x = document.getElementById( 'bodyContent' ).getElementsByTagName( 'a' );
   for (var i = 0; i < x.length; ++i )
   {
      /*
       * external links: add target m2:
       */
      if ( x[i].className == 'external' )
      {
         x[i].target = 'm2';
      }

      /*
       * popups: add onclick event, (popup):
       */
      if ( x[i].getAttribute( 'type' ) == 'popup' )
      {
         x[i].onclick = function () {
            return pop( this.href )
         }
         x[i].innerHTML += '<span class="smaller"> (popup)</span>';
      }

      /*
       * hreflang: add (lang=ll):
       */
      var hreflang = x[i].getAttribute( 'hreflang' );
      if ( hreflang )
      {
         var newspan = langspan.cloneNode( true );
         newspan.appendChild( document.createTextNode( ' (lang=' + hreflang + ')' ) );
         x[i].parentNode.insertBefore(newspan,x[i].nextSibling);
      }
   }
}

/**
 * create table of contents:
 */
function createTableOfContents()
{
//alert( 'createTableOfContents' );
   /*
    * (deep) collect all relevant headings, keeping the order of the text:
    */
   var headings = new Array();

   collectHeadings( document.getElementById( 'bodyContent' ).childNodes, headings );

   /*
    * do not show a small table of contents:
    */
   if ( noTableOfContents( headings ) )
      return ;

   /*
    * find the table of contents location, create its title
    * and append the (hideable) division for its contents:
    */
   var toc = document.getElementById( 'toc' );

   var title         = toc.appendChild( document.createElement( 'p' ) );
   title.onmousedown = showhideTOC;
   title.className   = 'section';
   title.innerHTML   = 'Page Contents';

   var contents      = toc.appendChild( document.createElement( 'div' ) );
   contents.onclick  = showhideTOC;

   /*
    * for all h2 and h3's add links to the table of contents location,
    * and add targets to the corresponding headings:
    *
    * Note: the header nodeName-s use uppercase.
    */
//    var hasTop = false;
   for ( var i = 0; i < headings.length; ++i )
   {
      var level = headings[i].nodeName.substring( 1 );

      // skip h1, h2
      if ( 1 <= level && level <= 2 )
         continue;

      // create entry for h3, h4...
      var entry       = document.createElement( 'a' );
      entry.innerHTML = headings[i].innerHTML;
      entry.href      = '#link' + i;
      entry.className = 'page';

      if ( level >= 4 )
      {
          entry.className += ' indent';
      }

      /*
       * because we do not use frames and table of contents shifts (out of sight)
       * when the page scrolls, there's not much use for the Top link.
       */
//       if ( !hasTop && 2 == level )
//       {
//          hasTop = true;
//
//          entry.innerHTML = 'Top';
//          entry.href      = '#top';
//          target.id       = 'top';
//       }

      contents.appendChild( entry );

      /*
       * create target and insert it before the header:
       */
      var target = document.createElement( 'a' );
      target.id = 'link' + i;

      headings[i].parentNode.insertBefore( target, headings[i] );
   }
}

/**
 * do not show a small table of contents:
 */
function noTableOfContents( nodes )
{
   var count = 0;
   for( var i = 0; i < nodes.length; ++i )
   {
      if ( 'H3' == nodes[i].nodeName
      ||   'H4' == nodes[i].nodeName )
      {
         ++count;
      }
   }

   return count < 2;
}

/**
 * deep collect of h1 to h5 nodes:
 */
function collectHeadings( node, container )
{
   for ( var i = 0; i < node.length; ++i )
   {
      if ( 0 == node[i].nodeName.indexOf( 'H' ) && node[i].nodeName.substring( 1 ) < 5 )
      {
         container.push( node[i] )
      }

      if ( node[i].hasChildNodes() )
      {
//         alert( 'collectHeadings: recurse' );
         collectHeadings( node[i].childNodes, container );
      }
   }
}

/**
 * true if container contains given x.
 */
function has( x, container )
{
   for ( var i = 0; i < container.length; ++i )
   {
      if ( x == container[i] )
         return true;
   }
   return false;
}

/**
 * create the page navigation menu.
 */
function createPageNavigation()
{
//alert( 'createPageNavigation' );
   /*
    * collect all relevant links (Top, Up, Previous, Next...):
    */
   var hrefs = '';
   var links = '';
   var skips = [ 'stylesheet', 'alternate stylesheet', 'icon', , 'author', 'me' ];

   var x = document.getElementsByTagName( 'link' );

   for ( var i = 0; i < x.length; ++i )
   {
      /*
       * skip unwanted links:
       */
      if ( has( x[i].getAttribute('rel'), skips ) )
      {
         continue;
      }

      /*
       * create link:
       */
      var href = x[i].getAttribute('href');
      var attr = x[i].getAttribute('rel' );

      hrefs += href;
      links += href.length ? ' <a href="' + href + '">' + attr + '</a>' : '';
   }

   /*
    * find the footer location and append the links:
    */
   var footer = document.getElementById( 'footer' );

   if ( footer )
   {
      if ( links.length > 0 )
      {
         footer.innerHTML = links + '<br />' + footer.innerHTML;
      }

//       footer.innerHTML
//          += '<a href="/">home</a>&nbsp;&nbsp;'
//          +  '<a href="sitemap/">sitemap</a>&nbsp;&nbsp;'
//          +  '<a href="{$contact}">contact</a>&nbsp;&nbsp;'
//          +  '<a href="about/copyright/">copyrights</a>';
   }

   /*
    * show page navigation unless there are no links:
    */
   if ( hrefs.length <= 0 )
      return ;

   /*
    * find the navigation location, create its title
    * and append the (hideable) division for its contents:
    */
   var nav = document.getElementById( 'pnav' );

   var title         = nav.insertBefore( document.createElement( 'p' ), nav.firstChild );
   title.onmousedown = showhidePNav;
   title.className   = 'section';
   title.innerHTML   = 'Page Navigation';

   var contents      = nav.appendChild( document.createElement( 'div' ) );
   contents.onclick  = showhidePNav;
   contents.innerHTML = links;
}

/**
 * create the site navigation menu.
 */
function createSiteNavigation()
{
//alert( 'createSiteNavigation' );
   /*
    * find the navigation location, create its title
    * and append the (hideable) division for its contents:
    */
   var nav = document.getElementById( 'nav' );

   var title         = nav.insertBefore( document.createElement( 'p' ), nav.firstChild );
//   title.onmousedown = showhideNav;
   title.className   = 'section';
   title.innerHTML   = 'Site Navigation';

   /*
    * - indent second level links,
    * - set title of link to text from original links,
    * - remove text:
    */
   var x = nav.getElementsByTagName( 'a' );

   for ( var i = 0; i < x.length; ++i )
   {
      /*
       * give secondlevel links class indent:
       */
      x[i].className += x[i].parentNode.className == 'content' ? ' indent' : "";

      /*
       * set title, remove text:
       */
      var linkText = x[i].nextSibling;

      if ( linkText.nodeType == 3 )
      {
         x[i].title = linkText.nodeValue.substring( 2 );
         linkText.parentNode.removeChild( linkText );
      }
   }

   /*
    * activate h3-s and h4-s and add number of pages:
    */
   var hs = [ 'h3', 'h4' ];

   for ( var h = 0; h < hs.length; ++h )
   {
      var x = nav.getElementsByTagName( hs[h] );
      for ( var i = 0; i < x.length; ++i )
      {
         x[i].onmousedown = clickNav;
         x[i].title       = showNumberOfPages( x[i] );
      }
   }

   closeNav();

   setNav( document.location, 'currentPage' );
}

/**
 * close the site navigation.
 */
function closeNav()
{
   var cts = document.getElementById( 'nav' ).getElementsByTagName( 'div' );
   for (var i = 0; i < cts.length; ++i )
   {
      if ( cts[i].className == 'content' )
      {
         cts[i].style.display = 'none';
      }
   }
}

/**
 * open or close (toggle) a submenu.
 */
function clickNav(e)
{
   if ( !e )
   {
      var e = window.event;
   }

   if ( e.target )
   {
      var tg = e.target;
   }
   else if ( e.srcElement )
   {
      var tg = e.srcElement;
   }

   while ( tg.nodeType != 1 ) // Safari GRRRRRRRRRR
   {
      tg = tg.parentNode;
   }

   var nextSib = tg.nextSibling;

   while ( nextSib.nodeType != 1 )
   {
      nextSib = nextSib.nextSibling;
   }

   var nextSibStatus     = (nextSib.style.display == 'none') ? 'block' : 'none';
   nextSib.style.display = nextSibStatus;

   fixIEBug();
}

/**
 * activate a new page and indicate current page in sitenavigation.
 *
 * Note link should end with '/' in sitemap if it specifies a directory.
 */
function setNav( page, newID )
{
//alert( 'setNav: page:' + page + 'newID:' + newID );

   var nav = document.getElementById( 'nav' );

//    page = page + ""; //ensure string
//    var hashpos = page.indexOf('#');
//    if ( -1 != hashpos )
//    {
//       page = page.substring( 0, hashpos );
//    }
//alert( 'setNav: page:' + page );

   var x = nav.getElementsByTagName('a');
   var i;

   for ( i = 0; i < x.length; ++i )
   {
//alert( 'setNav: xi.href:' + x[i].href );
      if ( x[i].href == page )
      {
         x[i].id = newID;
         break;
      }
   }

   if ( i < x.length && newID == 'currentPage' )
   {
      var parDiv = x[i];
      while ( parDiv.parentNode.tagName == 'DIV' )
      {
         parDiv = parDiv.parentNode;
         parDiv.style.display = 'block';
      }
//      x[i].focus();   // gives dashed/dotted box
   }
}

/**
 * close the navigation, except for the current link:
 */
function cleanNav()
{
   /* Close all */

   closeNav();

   fixIEBug();

   /* Open div containing current link */

   setNav( document.location, 'currentPage' );
}

/**
 * return number of pages for this label:
 */
function showNumberOfPages( label )
{
   var nextSib = label.nextSibling;

   while ( nextSib.nodeType != 1 )
   {
      nextSib = nextSib.nextSibling;
   }

   return nextSib.getElementsByTagName('a').length + ' pages';
}

/**
 * pop-up -
 */
var popUp = null;

function pop( url )
{
   if ( popUp && !popUp.closed )
   {
      popUp.location.href = url;
   }
   else
   {
      popUp = window.open( url,
         'width=500,height=700,scrollbars=yes.resizable=yes,toolbar=yes,location=yes' );
   }
   popUp.focus();
   return false;
}

/**
 * toggle the table of contents' visibility:
 *
 * initial state 'none' corresponds to div#toc div { display:none; } in file toc.css
 */
var TOCstate = 'none';

function showhideTOC()
{
   TOCstate = toggle( TOCstate, 'toc' );
}

/**
 * toggle the page navigation visibility:
 *
 * initial state 'block' corresponds to div#pnav div { display:block; } in file nav.css
 */
var PageNavstate = 'block';

function showhidePNav()
{
   PageNavstate = toggle( PageNavstate, 'pnav' );
}

/**
 * toggle the site navigation visibility:
 *
 * initial state 'block' corresponds to div#toc div { display:block; } in file nav.css
 */
var SiteNavstate = 'block';

function showhideNav()
{
   SiteNavstate = toggle( SiteNavstate, 'nav' );
}

/**
 * toggle the given id's visibility:
 */
function toggle( state, id )
{
   state = (state == 'none') ? 'block' : 'none';
   document.getElementById( id ).lastChild.style.display = state;
   return state;
}

/**
 * defeat IE6 fixed bug by opening and closing the last div.
 */
function fixIEBug()
{
   var x = document.getElementById( 'defeatIEBug' );
   var currentStyle =  x.style.display;
   x.style.display  =  (currentStyle == 'none') ? 'block' : 'none';
   x.style.display  =  currentStyle;
}

/*
 * IE5 additions:
 */

/*
 * push and shift for IE5:
 */

function Array_push()
{
   var A_p = 0
   for (A_p = 0; A_p < arguments.length; A_p++)
   {
      this[this.length] = arguments[A_p]
   }
   return this.length
}

if ( typeof Array.prototype.push == "undefined" )
{
   Array.prototype.push = Array_push
}

function Array_shift()
{
   var A_s = 0
   var response = this[0]
   for (A_s = 0; A_s < this.length-1; A_s++)
   {
      this[A_s] = this[A_s + 1]
   }
   this.length--
   return response
}

if ( typeof Array.prototype.shift == "undefined" )
{
   Array.prototype.shift = Array_shift
}

/*
 * end of file
 */