/*
Summary:  Add a table of contents before the first <h2> in your
          document.
Remarks:  The table of contents comprises an list of links to the
          headings in your document. The list items are indented
          according to the level of the heading.
Version:  1.02 
          14 Apr 2006 12:07
Author:   John Bentley (www.softmake.com.au)     
Depends on:
    tableOfContents.css
Utilities From:
    getElementsByTagNames()   w3CDOM.js
    addLoadEvent()            events.js    
Usage:
    1. Create a XHTML document with at least one <h2> element.
    2. Copy the following files to the directory of your document:
        tableOfContents.css
        tableOfContents.js
    3. To your XHTML document add ...
      <link type="text/css" rel="stylesheet" href="tableOfContents.css" />
      <script type="text/javascript" src="tableOfContents.js"></script>
      
      No need to trigger anything from the load event. This will
      happen automatically.
      
    4. Adjust headingRange below if needed. (Or override variable
      within the script of the XHTML document. eg headingRange = "h2";)
    5. Edit any CSS in tableOfContents.css to fit your site.
    6. In your XHTML optionally give your headings an id. Headings
       without an explicit id will be given one via code. 
       Eg (heading0, heading1,love,heading3,...)
*/

/* Adjust the following to pick up on the level of headings you wish to include. 
    Make it contiguous, eg 'h3,h4,h5' not 'h3,h5'. */
var headingRange = "h2,h3,h4";

addLoadEvent(function() {
  generateTableOfContents();
  // more code to run on page load 
});

function generateTableOfContents () {

  var tableOfContentsHolder = document.createElement("div");
  tableOfContentsHolder.className = "tableOfContentsHolder";  
  
  var tableOfContents = document.createElement("div");
  tableOfContents.className = "tableOfContents";  
  
  var P = document.createElement("p");
  var textNode = document.createTextNode("Table Of Contents");
  P.appendChild(textNode); 

  var UL = document.createElement("ul");
  var headings = getElementsByTagNames(headingRange);
  for (var i = 0; i < headings.length; i++ ) {
    var LI = document.createElement("li");
    var A = document.createElement("a");
    // Only give an id to headings that have none.
    if(headings[i].id == ""){
      headings[i].id = "heading" + i;
    }
    A.setAttribute("href", "#" + headings[i].id);
    A.className = "toc" + headings[i].tagName.toLowerCase();
    textNode = document.createTextNode(headings[i].innerText);
    A.appendChild(textNode); 
    LI.appendChild(A);
    UL.appendChild(LI); 
  }
  
  // Used to clear text below the floated Table Of Contents
  var clearDiv = document.createElement("div");
  clearDiv.style.clear = "both";

  tableOfContents.appendChild(P);
  tableOfContents.appendChild(UL);
  tableOfContentsHolder.appendChild(tableOfContents); 
  tableOfContentsHolder.appendChild(clearDiv);
  
  var firstHeading = headings[0];
  firstHeading.parentNode.insertBefore(tableOfContentsHolder, firstHeading);
}

/* Utility */

/*  Returns: An array of elements given a list of tag names.
    Remarks: Note the W3C getElementsByTagName, singular, only permits
              one tag.
    Example: 
        var headings = getElementsByTagNames("h2,h3,h4");
    Source: http://www.quirksmode.org/dom/getElementsByTagNames.html */
function getElementsByTagNames(list,obj) {
	if (!obj) var obj = document;
	var tagNames = list.split(',');
	var resultArray = new Array();
	for (var i=0;i<tagNames.length;i++)
	{
		var tags = obj.getElementsByTagName(tagNames[i]);
		for (var j=0;j<tags.length;j++)
		{
			resultArray.push(tags[j]);
		}
	}
	var testNode = resultArray[0];
	if (testNode.sourceIndex)
	{
		resultArray.sort(function (a,b) {
				return a.sourceIndex - b.sourceIndex;
		});
	}
	else if (testNode.compareDocumentPosition)
	{
		resultArray.sort(function (a,b) {
				return 3 - (a.compareDocumentPosition(b) & 6);
		});
	}
	return resultArray;
}

/* 
Summary:  adds a function to the onload event without overwriting
          previous functions assignments to onload.
Usage Examples: 
          addLoadEvent(nameOfSomeFunctionToRunOnPageLoad);
          // Or
          addLoadEvent(function() {
            // more code to run on page load 
          });
Source:  Simon Willson > Executing JavaScript on page load  
         http://simon.incutio.com/archive/2004/05/26/addLoadEvent */             
function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

/*  Summary: Sets and returns all the text, without HTML markup,
          within an element.
    Remarks: Emulation of IE's innerText property Works in NN6+ (including Mozilla).  
    Source: GoodmanJavaScriptBible > 33_p1005_Objects_ExtendingCoreObjectsWithCustomPropertiesAndMethods.htm*/ 
if (typeof HTMLElement != "undefined" 
      && typeof HTMLElement.prototype.__defineGetter__ != "undefined") {
  HTMLElement.prototype.__defineGetter__("innerText", function () {
    var rng = document.createRange();
    rng.selectNode(this);
    return rng.toString();
  })
}

if (typeof HTMLElement != "undefined" 
      && typeof HTMLElement.prototype.__defineSetter__ != "undefined") {
  HTMLElement.prototype.__defineSetter__("innerText", function (txt) {
    var rng = document.createRange();
    rng.selectNodeContents(this);
    rng.deleteContents();
    var newText = document.createTextNode(txt);
    this.appendChild(newText);
    return txt;
  })
}
