/*
2010-03-01T16:08:11-08:00 - Added doc.appendText().
2010-02-27T21:35:59-08:00 - Added callback function to buildPageSizeSelector, which
                         gets called after any size change if it exists.
2010-02-27T09:48:41-08:00 - Fixed buildToC2 support for other than h1-h3 in options.
                         Changed the format use non-bulletted first level item and
                         moved first margin out by 1em (first is 0, was 1em), others 
                         by .5em to compensate for bullets.
2010-02-26T13:15:08-08:00 - Add ability to pass elem ID to event.addListener
2010-02-17T11:58:35-08:00 - Update addLoadEvent to use current standards.
2010-01-31T14:25:04-08:00 - Fix layout on buildPageSizeSelector to go with 
                         new CSS. Should have been done this way oritinally.
                         Fix reload problem displaying selector text twice.
2009-11-30T12:47:30PST - Minor JSlint fixes, mostly == to ===. Add global
                         comment for JSlint.
2009-08-09T13:26:36PST - Change left margin on ToC2 from 0 to 1em.
2009-05-27T20:26:01PST - Add showToC option to buildToC2.
                         Add event object with addListenr and removeListener
2009-04-15T15:14:14PST - Add buildPageSizeSelector.
2009-03-23T11:29:13PST - Add V2 of buildToc. Keep support for legacy version.
2009-02-11T19:26:41PST - fix widget.buildToC, problem with innerHTML on anchors,
                         node.add findFirstText.
2009-02-09T19:56:53PST - add widget.buildToC
2009-01-23T12:59:55PST - add node function deleteNodes and deleteChildren
2008-12-27T15:40:37PST - initial posting

All code in this file runs in the com.YourFriendPaul.util namespace

// deprecated but convenient. Better to use event.addListener( 'window', 'load', func )
addLoadEvent( func )
getElementsByTagNames( elemList, inObj )

node.deleteNodes( node )
node.deleteChildren( node )
node.findFirstText( node )

widget.buildToC( div's-elemId ) -- this requires external CSS
widget.buildToC2( div's-elemId, [ optionsObject ] ) -- CSS generated on execution
widget.buildPageSizeSelector( selectorBoxId [, targetBoxId [, optionsObject ]] )

event.addListener( elem, type, func )
event.removeListener( elem, type, func )

doc.appendText( targetId, text, elemType, attrList )

addNamespace( ... ) -- accepts multiple args
nsUsedLevel( root, ns )
isNamespaceUsed( nsIn )
*/
// for JSlint:
/*global window */
/**********************************************************************/
// create the main namespace for everything
var com = com || {}; // "var" does nothing if com already exists
com.YourFriendPaul = com.YourFriendPaul || {}; // make the object if needed
// Useful for debug messages
if( com.YourFriendPaul.toString() != 'com.YourFriendPaul' ){ 
   com.YourFriendPaul.toString = function(){ return 'com.YourFriendPaul'; };
}

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
// Useful for debug messages
com.YourFriendPaul.util = com.YourFriendPaul.util || {};
if( com.YourFriendPaul.util.toString() != 'com.YourFriendPaul.util' ){ 
   com.YourFriendPaul.util.toString = function(){ return 'com.YourFriendPaul.util'; };
}


// orig.  concept from QuirksMode http://www.quirksmode.org/dom/getElementsByTagNames.html
// elList is an array of elements to search for, or a comma separate list.
// inObj is the parent object to search, document by default.
(function( $ ){

   $.getElementsByTagNames = function( elList, inObj ) {
   
      // If the MS extension sourceIndex exists, do nothing.
      // If it doesn't, add one.
      // Adding this allows sorting elements into the order that they
      //  appear in the document.
      function forceSourceIndex(){
         var allElems, eCount;
         // return if it's already supported
         if( document.getElementsByTagName("body")[0].sourceIndex ) { return; }
         
         allElems = document.getElementsByTagName("*");
   
         for( eCount=0; eCount<allElems.length; eCount++ ){
         
            allElems[ eCount ].sourceIndex = eCount;
         }
      }
   
      // search entire document if object is not specified
      var obj = inObj || document;
      var tagNames;
   
      if( typeof elList === 'string' ){
         tagNames = elList.split(',');
      }
   
      else{
   
         tagNames = elList;
      }
      
      //alert( 'getElementsByTagNames elList is ' + tagNames );
      
      var resultArray = [], i, j, tags, testNode;
      // Need support for this IE function to do sorting; build our own index if needed
      forceSourceIndex();
      
      // for every tag name in list
      for ( i=0; i<tagNames.length; i++ ) {
         tags = obj.getElementsByTagName( tagNames[i] );
         
         // for every element of that tag type
         for ( j=0; j<tags.length; j++ ) {
            resultArray.push( tags[ j ] );
         }
      }
      
      testNode = resultArray[0];
      // if no results, return
      if (!testNode){ return []; }
      
      // alert if sourceIndex hasn't been built already, a debug thing
      if( !testNode.sourceIndex ){ alert( "couldn't make sourceIndex" ); }
      
      if ( testNode.sourceIndex ) {
         resultArray.sort( function (a,b) {
               return a.sourceIndex - b.sourceIndex;
         } );
      }
      return resultArray;
   };
   
})( com.YourFriendPaul.util );

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
//
// The anonymous functions refered to in the return statement
// must have already been executed before they can be 
// added here. That means this needs to go at the end of the file,
// after all the other functions.
//

(function ( ns ){

   /**********************************************************************/
   // Adds functions to window.onload. They will be executed in the order added.
   // use like this:
   //
   // addLoadEvent(nameOfSomeFunctionToRunOnPageLoad); 
   //
   // or like this:
   //
   // addLoadEvent(function() { 
   //     /* more code to run on page load */ 
   //   });
   //
   // Originally from http://simonwillison.net/2004/May/26/addLoadEvent/
   //
/*
function addLoadListener(fn)  
{  

*/
   function addLoadEvent( fn ) {
   
      var oldonload = null;
      
      if ( typeof window.addEventListener !== 'undefined' ){
        
         window.addEventListener('load', fn, false);  
      }  
      else if (typeof document.addEventListener !== 'undefined'){  
      
         document.addEventListener('load', fn, false);  
      } else if (typeof window.attachEvent !== 'undefined'){
        
         window.attachEvent('onload', fn);  
      }
      else if ( typeof window.onload !== 'function' ) {
      
         window.onload = fn; 
      }
      else { 

         oldonload = window.onload;

         window.onload = function() {
            if ( oldonload ) { oldonload(); }
            fn(); 
         };
      }
   }

   ns.addLoadEvent = addLoadEvent;

//   return{
//      addNamespace: com.YourFriendPaul.addNamespace,
//      onLoadAdder: addLoadEvent
//   }

})( com.YourFriendPaul.util );

// ********************************************************
// node functions
// 
// This is complicated because it tries to remove memory leaks in IE
//  by removing any function references before deleting the element.
//  It also deletes all children to make sure they are not left in memory.
// ********************************************************
com.YourFriendPaul.util.node = (function(){


   // deletes all children of a node or element
   function deleteChildren( node ){

      if( null === node ) { return; }
      if( node.hasChildNodes() ){
      
         while( node.lastChild ) { 
            this.deleteNodes( node.lastChild );
         }
      }
   }

   // deletes all children and then the node
   function deleteNodes( node ){
   
      var a, i, l, n;
      if( null === node ) { return; }
      if( node.hasChildNodes() ){
      
         // find the last lastChild
         while( node.lastChild ) { 
            deleteNodes( node.lastChild );
         }
      }

      // remove any functions that are attached
      a = node.attributes;
      if ( a ) {
         l = a.length;
         for ( i = 0; i < l; i += 1 ) {
            n = a[i].name;
            if ( typeof node[ n ] === "function" ) {
               node.removeAttribute( n );
               node[ n ] = null;
            }
         }
      }
      node.parentNode.removeChild( node );
   }

   // Finds the first text node attached to node
   // Returns null if not found
   function findFirstText( node ){

      if( null === node || typeof node === "undefined" ) { return null; }
      if( !node.hasChildNodes() ){ return null; }
      
      //alert( node.nodeName + ", " + node.nodeValue + ", has childern: " + node.childNodes.length );
      var iCount=0, tempNode;
      for( iCount=0; iCount< node.childNodes.length; iCount++ ){
         
         //alert( "child" + iCount + " is type " + node.childNodes[ iCount ].nodeType );
         // return the child if it is a text node
         if( node.childNodes[ iCount ].nodeType === 3  ) { 
            return node.childNodes[ iCount ];
         }
         
         if( node.childNodes[ iCount ].hasChildNodes() ){
         
            tempNode = findFirstText( node.childNodes[ iCount ] );
            // return the child if it is a text node
            if( tempNode !== null && tempNode.nodeType === 3  ) { 
               return tempNode;
            }
         }
      }
      // not found
      return null;
   }

   
   return {
   findFirstText: findFirstText,
   deleteChildren: deleteChildren,
   deleteNodes: deleteNodes
   };

})();

// ********************************************************


// replacement for deprecated target attribute
//
// To modify a link that would have used  target=_blank  before it was deprecated, use 
// this attribute in the anchor tag:  rel="external".
// Run externalLinks() using onload() for the body element.
// Or better yet, use the "addLoadEvent" function to have
// more than one onload function.
// Origianlly from http://www.sitepoint.com/article/1041/3

com.YourFriendPaul.util.linkTargetBlank = function() {

   if ( !document.getElementsByTagName ) { return; }
   var anchors = document.getElementsByTagName( "a" );
   var anch;
   for ( var i=0; i<anchors.length; i++ ) {

      anch = anchors[ i ];
      if ( anch.getAttribute( "href" ) && anch.getAttribute( "rel" ) === "external" ){ 
         anch.target = "_blank";
      }
   }
};

com.YourFriendPaul.util.event = {};

(function( $ ){

   $.addListener = function( elem, eType, funcName ){
   
      var oldE, onName = "on" + eType, elemName = elem;
      
      if( typeof elem === 'string' ){
      
         elem = document.getElementById( elemName );
         if( null === elem ){ 
            //alert( 'addListener: no name for ' + elemName );
            //alert( 'addListener :' + elem.id + ', ' + eType + ', ' + funcName );
            return elem; 
         }
      }
      //alert( 'addListener :' + elem.id + ', ' + eType + ', ' + funcName );
      if( elem.addEventListener ){
         // add event on the 'bubbling' phase
         return elem.addEventListener( eType, funcName, false );
      }
      
      if( elem.attachEvent ){
         
         return elem.attachEvent( onName, funcName );
      } 
      
      if( elem[ onName ] !== null ){
         
         oldE = elem[ onName ];
         elem[ onName ] = function(e){ oldE(e); funcName(e); };
      }
      
      else {
         
         elem[ onName ] = funcName;
      }
      
      return elem[ onName ];
   };

   $.removeListener = function( elem, eType, funcName ){
   
      var onName = "on" + eType;
      
      if ( elem.removeEventListener ){
         return elem.removeEventListener( eType, funcName, false);
      }
      
      if( elem.detachEvent ){
         
         return elem.detachEvent( onName, funcName );
      }
      
      // don't even try with the functions added like onload=...
      return null;
      
   };
   

})( com.YourFriendPaul.util.event );


// ===============================================================================
// ===============================================================================

com.YourFriendPaul.util.widget = {};

if( com.YourFriendPaul.util.widget.toString() != 'com.YourFriendPaul.util.widget' ){ 
   com.YourFriendPaul.util.widget.toString = function(){ return 'com.YourFriendPaul.util.widget'; };
}


(function( $ ){

   //========================= first version ToC code ======================
   // build the ToC and attach it as a child to elemId
   // This first version is deprecated
   $.buildToC = function( elemId ){

      var TOCstate = 'block';
   
      var text = {
         showContents: "show page contents",
         hideContents: "hide page contents",
         space: " ",
         tmpOnClickLoc: "tmpOnClickLoc"
      };
      
      function showhideTOC() {
         TOCstate = (TOCstate === 'none') ? 'block' : 'none';
         var newText = (TOCstate === 'none') ? text.showContents : text.hideContents;
         document.getElementById('contentheader').innerHTML = newText;
         document.getElementById('innertoc').lastChild.style.display = TOCstate;
      }
   

      // build the ToC and append it to z
      function createLinks( z, tocClassNameBase ){
      
         var toBeTOCced, tNode, i, headerId, tmp;
         
         toBeTOCced = com.YourFriendPaul.util.getElementsByTagNames('h1,h2,h3');
         // nothing to show
         if( toBeTOCced.length < 2 ){ return 0; }
      
         for ( i=0; i<toBeTOCced.length; i++ ) {
   
            // create an id for the element if one doesn't exist
            headerId = toBeTOCced[i].id || 'TocLink' + i;
            toBeTOCced[i].id = headerId;
   
            tmp = document.createElement('a');
            tmp.href = '#' + headerId;
   
            // find the text to use in the ToC
            tNode = com.YourFriendPaul.util.node.findFirstText( toBeTOCced[i] );
            
            tmp.innerHTML = ( tNode === null ) ? headerId : tNode.nodeValue;
            tmp.className = 'TocItem';
            z.appendChild(tmp);
            if ( toBeTOCced[i].nodeName === 'H1' ){ tmp.className += text.space + tocClassNameBase + 1; }
            if ( toBeTOCced[i].nodeName === 'H2' ){ tmp.className += text.space + tocClassNameBase + 2; }
            if ( toBeTOCced[i].nodeName === 'H3' ){ tmp.className += text.space + tocClassNameBase + 3; }
            if ( toBeTOCced[i].nodeName === 'H4' ){ tmp.className += text.space + tocClassNameBase + 4; }
            if ( toBeTOCced[i].nodeName === 'H5' ){ tmp.className += text.space + tocClassNameBase + 5; }
            if ( toBeTOCced[i].nodeName === 'H6' ){ tmp.className += text.space + tocClassNameBase + 6; }
         }
         // number of entries made
         return i;
      }
   
      // orig only looked at h2-h6
      function createTOC() {
         var y, x, a, z, entries;
         // create extra do-nothing div to fix display issue in IE6
         y = document.createElement('div');
         x = y.appendChild( document.createElement( "div" ) );
         x.id = 'innertoc';
         a = x.appendChild(document.createElement('span'));
         a.onclick = showhideTOC;
         a.id = 'contentheader';
         a.innerHTML = text.showContents;
         z = x.appendChild(document.createElement('div'));
         z.onclick = showhideTOC;
         entries = createLinks( z, text.tocClassNameBase );
         return entries ? y : null;
      }




      var tocDiv, elem;
      elem = document.getElementById( elemId );
      if( null === elem ) { return; }

      // text is specific to this version, so add it here
      text.tocClassNameBase = "TocIndent";
      
      tocDiv = createTOC();
      if( null !== tocDiv ){
         elem.appendChild( tocDiv );
         showhideTOC();
         // allow object to be deleted now that we're done with it
         tocDiv = null;
      }
   };
   // end of buildToC

   //========================= second version code ======================
   // build the ToC and attach it as a child to elemId
   // first arg is id of div to put the ToC
   // second arg is an options object

   $.buildToC2 = function(){
   
      var TOCstate = 'block';
      
      var text = {
         showContents: "show page contents",
         hideContents: "hide page contents",
         space: " ",
         tocClassNameBase2: "toc2indent",
         tmpOnClickLoc: "tmpOnClickLoc"
      };
      
   
      // can set state with optional parameter, else toggle
      function showhideTOC2( show ) {
         TOCstate = (TOCstate === 'none') ? 'block' : 'none';
         if( show === true || show === false ){ TOCstate = ( show === true ? 'block' : 'none' ); }
         var newText = (TOCstate === 'none') ? text.showContents : text.hideContents;
         document.getElementById( "tocContentheader" ).innerHTML = newText;
         document.getElementById( "tocContent" ).style.display = TOCstate;
      }
   
      // handle clicks on the li elements in the ToC
      function liClick(){
      
         var attrVal = this.getAttribute( text.tmpOnClickLoc );
         location.replace( attrVal );
         return false;
      }
      
      // A comma separate sting will be separate into an array of those elements
      // A hyphenated string will fill in the items between the hyphen. Be lazy
      //  and assume this could only be a heading element because those are all
      //  that are numbered.
      function parseTagList( inList ){
      
         var minH, maxH, count, tagNum = 0;
         var tags = inList.split( /,\s*/ );

         // either no comma separated or less that 2 types
         if( tags.length < 2 ){
         
            // parse for hyphenated list
            tags = inList.split( /-\s*/ );
            
            // if only one element, give up and just use it, otherwise assume H list
            if( tags.length > 1 ){
            
               minH = parseInt( tags[ 0 ].charAt( 1 ), 10 );
               maxH = parseInt( tags[ 1 ].charAt( 1 ), 10 );
               for( count = minH; count <= maxH; count++ ){
               
                  tags[ tagNum++ ] = 'h' + count;
               }
               //alert( inList + '. tags is ' + tags );
            }
         }
         
         // this will determines the priority of the tags
         //tags = tagList.split( /,\s*/ );
         return tags;
      }

      // build the ToC and append it to container
      // returns number of ToC entries
      function createLinks( container, tocClassNameBase, curSettings ){
      
         var toBeTOCced, tNode, i, headerId, tmp, tmpLi, tags = [], tp, bc;
         
         // this will determines the priority of the tags
         tags = parseTagList( curSettings.tagTypes );
         toBeTOCced = com.YourFriendPaul.util.getElementsByTagNames( tags );

         // nothing to show
         if( toBeTOCced.length < 2 ){ return 0; }
      
         for ( i=0; i<toBeTOCced.length; i++ ) {
   
            // create an id for the element if one doesn't exist
            headerId = toBeTOCced[i].id || 'TocLink' + i;
            toBeTOCced[i].id = headerId;
   
            tmpLi = document.createElement('li');
            container.appendChild( tmpLi );
            tmp = document.createElement('span');
            // don't modify browser history
            // tmp.href = 'javascript:location.replace("#' + headerId + '");';
            // tmp.href = "javascript:void()";
            // save the onclick handler target
            tmpLi.setAttribute( text.tmpOnClickLoc, "#" + headerId );
            tmpLi.onclick = liClick;
   
            // find the text to use in the ToC
            tNode = com.YourFriendPaul.util.node.findFirstText( toBeTOCced[i] );
            
            tmp.innerHTML = ( tNode === null ) ? headerId : tNode.nodeValue;
            tmp.className = 'TocItem';
            tmpLi.appendChild( tmp );
            
            // go through the list and compare each element to the tag list
            for( tp = 0; tp < tags.length; tp++ ){

               // add class for indent formatting; doesn't have to start with h1
               if ( toBeTOCced[i].nodeName === tags[ tp ].toUpperCase() ){ 

                  bc = tp+1;
                  tmpLi.className += text.space + tocClassNameBase + bc; 
               }
            }
         }
         // number of entries made
         return i;
      }
   
      function createToC2( curSettings ){
         var y, x, a, z, entries;
         // create extra do-nothing div to fix display issue in IE6
         y = document.createElement( "div");
         x = y.appendChild( document.createElement( "div" ) );
         x.id = "tocInnertoc";
         a = x.appendChild( document.createElement( "div" ) );
         a.onclick = showhideTOC2;
         a.id = "tocContentheader";
         a.innerHTML = text.showContents;
         z = x.appendChild( document.createElement( "ul"));
         z.id = "tocContent";
         // z.onclick = showhideTOC2;
         entries = createLinks( z, text.tocClassNameBase2, curSettings );
         return entries ? y : null;
      }
   
      
      //  elemId  is the top level elem where this will be inserted
      function applyCSS( elemId, curSettings ){
      
         var lastStyleSheet, rules;
         var settings = curSettings;

         function getFromSettings( name ){
         
            return settings[ name ];
         }
         
         // all of this extra work is to allow overrides
         var objList = {
            "#ToCstyle": { 
               "float": "right", 
               "margin": null, 
               "font-size": null, 
               "width": null,
               "border-style": "solid",
               "border-width": null,
               "border-color": null,
               "background-color": null
            },
            
            "#tocContentheader": {
               cursor: "pointer",
               padding: ".5em",
               "text-align": "center"
            }, 
   
            "#tocContent": {
               //padding: "1em .5em 2em 1em",
               margin: "0",
               padding: ".5em .5em 1em 1em",
               "border-color": null,
               "border-style": "solid",
               "border-width": "1px 0 1px 1px"
            }
         };
         
         // puts rules at the beginning of the sheet
         function insertRule( sheet, selector, styles ){
   
            if( sheet.insertRule ){
               sheet.insertRule( selector + "{" + styles + "}", 0 );
            }
            else {
               sheet.addRule( selector, styles, 0);
            }
         }
   
         function buildStylesText( obj ){
         
            var text = "";
            for ( var name in obj ) {
               if ( obj.hasOwnProperty( name )) {
   
                  text += name + ": " + ( obj[ name ] ? obj[ name ] : getFromSettings( name ) ) + "; ";
               }
            }
            
            return( text );
         }
   
         function insertRuleFromObj( sheet, objName ){
            insertRule( sheet, objName, buildStylesText( objList[ objName ] ) );
         }
   
         lastStyleSheet = document.styleSheets[ document.styleSheets.length - 1 ];
         rules = lastStyleSheet.cssRules ?  lastStyleSheet.cssRules : lastStyleSheet.rules;
         
         // add rules for each possible element in the list
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "1", "margin-left: 0em; list-style-type: none" );
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "2", "margin-left: 1.5em" );
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "3", "margin-left: 2.5em" );
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "4", "margin-left: 3.5em" );
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "5", "margin-left: 4.5em" );
         insertRule( lastStyleSheet, "." + text.tocClassNameBase2 + "6", "margin-left: 5.5em" );
         
         insertRule( lastStyleSheet, "#tocContent li", "display: list-item; margin-top: .5em; cursor: pointer; text-decoration: underline" );
         insertRule( lastStyleSheet, "#" + elemId, buildStylesText( objList[ "#ToCstyle" ] )  );
         insertRuleFromObj( lastStyleSheet, "#tocContentheader" );
         insertRuleFromObj( lastStyleSheet, "#tocContent" );
      }
      
      
      function overrideDefaults( defaults, options ){
      
         var settings = defaults;
         for ( var oName in options ) {
            if (options.hasOwnProperty( oName )) {
            
               for ( var sName in settings ) {
                  if ( settings.hasOwnProperty( sName )) {
                  
                     if( sName === oName ){
                        settings[ sName ] = options[ oName ];
                     }
                  }
               }
            }
         }
         return settings;
      }

      function run( args ){
      
         var elem, elemId, tocDiv, settings, options;
         
         elemId = args[0] || "ToC";
         options = args[1] || {};
         elem = document.getElementById( elemId );

         if( null === elem ) { return; }
   
         settings = overrideDefaults( $.buildToC2.defaults, options );
         
         // if not enough entries, will return null, else the ToC fragment
         tocDiv = createToC2( settings );
         
         if( null !== tocDiv ){
         
            elem.appendChild( tocDiv );
            showhideTOC2( settings.showToC );
            applyCSS( elemId, settings );
            tocDiv = null;
         }
      }
      
      // do this stuff separately to localize variables
      run( arguments );
   };

   $.buildToC2.defaults = {
         
      width: "180px",      // #ToC
      margin: "0 0 1em 0",      // #ToC
      "background-color": "#dddddd",   // #ToC
      "border-color": "#999999",   // #ToC
      "border-width": "5px 0",
      "font-size": "80%",   // #ToC
      tagTypes: "h1-h3",
      showToC: true
   };
})( com.YourFriendPaul.util.widget );

// ===============================================================================
// ===============================================================================
//
//   buildPageSizeSelector
//
// ===============================================================================
// build a page size selector box and attach it as a child to elemId
// first arg is id of div to put the selector box
// second arg is id of div to change the size of
// third arg is an options object

(function( $ ){

   function addListener( eType, elem, funcName ){
   
      com.YourFriendPaul.util.event.addListener( elem, eType, funcName );
   }



   // this is what actually changes the size
   $.sizeClick = function( e ){
   
      //alert( "clicked " + (this === window) ? 'window' : this.id );
      var ei = {
         event: e || window.event,
         target: null,
         keycode: null
      };
      
      ei.keycode = ei.event.keyCode || ei.event.which;
      ei.target = ei.event.target || ei.event.srcElement;
      if( ei.target.nodeType === 3 ){ // defeat Safari bug
            ei.target = ei.target.parentNode;
      }
      //alert( "clicked " + ei.target.innerHTML + ": " + ei.target.clickValue );
      // targetValue is the element to change the style of. It was added
      // as a parameter of the clicked li element
      ei.target.targetValue.style.width =  ei.target.clickValue;
      
      // execute a function now that we're done
      if( ei.target.callback !== null ){
      
         ei.target.callback();
      }
   };
   
   // this is the main public function
   $.buildPageSizeSelector = function( ){

   
      var elemBox, elemTarget, settings, options, elemId, divToChangeId;
      var innerName = "innerWidthChanger";
      
      // build the html and append it to container
      // selections is an array
      // returns number of selection entries
      function createHtml( elemBox, elemTarget, settings ){
      
         var tNode, cNode, iSelNum, tmpLi;
         var selections = settings.selections;
         
         // Create extra do-nothing div to fix display issue in IE6
         // This also gives a way to see if the box already exists
         tNode = document.createElement( "div");
         tNode.id = innerName;
         elemBox.appendChild( tNode );
         cNode = document.createElement( "span" );
         tNode.appendChild( cNode );
         cNode.appendChild( document.createTextNode( "Page width: " ) );
         cNode = document.createElement( "ul" );
         tNode.appendChild( cNode );
         
         // now add the li buttons to the ul
         for ( iSelNum = 0; iSelNum < selections.length; iSelNum++ ) {
   
            tmpLi = document.createElement('li');
            cNode.appendChild( tmpLi );
            tmpLi.appendChild( document.createTextNode( selections[ iSelNum ].label ) );
            tmpLi.clickValue = selections[ iSelNum ].value;
            // the element to change
            tmpLi.targetValue = elemTarget;
            // add the callback, will be null if option wasn't used
            tmpLi.callback = settings.callback;
            // need to add the click event handler here
            // it should work because it's already attached to the document
            addListener( "click", tmpLi, $.sizeClick );
         }
         // number of entries made
         return iSelNum;
      } // createHtml
   
   
      
      
      //  elemId is the top level container elem
      function applyStyles( elemId, curSettings ){
      
         var lastStyleSheet;
         var settings = curSettings;

         // all of this extra work is to allow overrides
         // Each element of objList will be a new style rule.
         var objList = {
            "#WidthChanger": { 
               "float": "right", 
               "line-height": "1.25em",
               "margin": null, 
               "font-size": null, 
               //"width": null,
               "border-style": "solid",
               "border-width": null,
               "border-color": null,
               "background-color": null
            },
            
            // essentially the box title
            "label": {
               margin: "0 .5em 0 0",
               padding: "0 .25em 0 0",
               "float": "left",
               "line-height": "1.25em",
               color: "#333333"
            },
            
            "li": {
               display: "inline",
               "float": "left",
               margin: "0 .5em",
               padding: "0 .25em",
               "list-style-type": "none", 
               cursor: "pointer", 
               "text-decoration": "underline",
               "line-height": "1.25em",
               color: "#333333"
            }
         };
         
         function applySettings( settings, objList ){
         
            objList[ "#WidthChanger" ].padding = settings.boxPadding;
            objList[ "#WidthChanger" ].margin = settings.boxMargin;
            objList[ "#WidthChanger" ][ "background-color" ] = settings[ "box-background-color" ];
            objList[ "#WidthChanger" ][ "border-color" ] = settings[ "box-border-color" ];
            objList[ "#WidthChanger" ][ "border-width" ] = settings[ "box-border-width" ];
            objList[ "#WidthChanger" ][ "font-size" ] = settings[ "font-size" ];
            objList.label.color = settings.color;
            objList.li.color = settings.color;
         }
         
         // puts rules at the beginning of the sheet
         function insertRule( sheet, selector, styles ){
   
            if( sheet.insertRule ){
               sheet.insertRule( selector + "{" + styles + "}", 0 );
            }
            else {
               sheet.addRule( selector, styles, 0);
            }
         }
   
         function buildStylesText( obj ){
         
            var text = "";
            for ( var name in obj ) {
               if ( obj.hasOwnProperty( name )) {

                  //text += name + ": " + ( obj[ name ] ? obj[ name ] : settings[ name ] ) + "; ";
                  text += name + ": " + obj[ name ] + "; ";
               }
            }
            
            return( text );
         }
   
         applySettings( settings, objList );
         lastStyleSheet = document.styleSheets[ document.styleSheets.length - 1 ];
         //rules = lastStyleSheet.cssRules ?  lastStyleSheet.cssRules : lastStyleSheet.rules;
         
         insertRule( lastStyleSheet, "#" + elemId, buildStylesText( objList[ "#WidthChanger" ] ) );
         // need opposite float on innerWidthChanger to work in both IE and FF
         insertRule( lastStyleSheet, "#" + elemId + " #" + innerName, "float: left; line-height: 1.25em" );
         insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " span", buildStylesText( objList.label ) );
         insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " ul", 
         "display: inline-table; list-style: none; margin: 0; padding: 0" );
         insertRule( lastStyleSheet, "#" + elemId + " #" + innerName + " li", buildStylesText( objList.li ) );
      }
      // end applyStyles
      
      // copy the value for any properties of options that are also found in defaults
      function overrideValues( defaults, options ){
      
         var settings = defaults;
         var matchFound = false;
         // loop through the options object
         for ( var oName in options ) {
            if ( options.hasOwnProperty( oName )) {
            
               matchFound = false;
               // look for a match in defaults object
               for ( var sName in settings ) {
                  if ( settings.hasOwnProperty( sName )) {
                  
                     if( sName === oName ){
                        settings[ sName ] = options[ oName ];
                        matchFound = true;
                     }
                  }
                  // not a failure, so pretend it worked
                  else { matchFound = true; }
               }
               
               if( matchFound !== true ){
               
                  // want to have a warning if something changes
                  alert( "Undefined or obsolete option used: " + oName );
               }
            }
         }
         return settings;
      } // end overrideValues

      elemId = arguments[ 0 ] || "widthChanger";
      divToChangeId = arguments[ 1 ] || "allContentWrapper";
      options = arguments[ 2 ] || {};
      
      elemBox = document.getElementById( elemId );
      if( null === elemBox ) { return; }
      elemTarget = document.getElementById( divToChangeId );
      if( null === elemTarget ) { return; }
      var exists = document.getElementById( innerName );
      // The box is there and the page is being reloaded, don't make two!
      if( null !== exists ) { return; }

      settings = overrideValues( $.buildPageSizeSelector.defaults, options );
      
      var tocDiv = createHtml( elemBox, elemTarget, settings );
      if( null !== tocDiv ){
      
         applyStyles( elemId, settings );
         tocDiv = null;
      }
   };

   $.buildPageSizeSelector.defaults = {
         
      boxPadding: "1em",
      boxMargin: "0 0 1.5em 1.5em",
      "box-background-color": "#dddddd",
      "box-border-color": "#999999",
      "box-border-width": "1px",
      "font-size": "83%",
      color: "#333333",   // font color
      selections: [ { label: "750px", value: "750px" }, { label: "standard", value: "60em" },
      { label: "950px", value: "950px" }, { label: "full width", value: "100%" } ],
      'callback': null
   };
})( com.YourFriendPaul.util.widget );

// ===============================================================================
// ===============================================================================
/*
    appendText()
*/
(function( base ){
   
   base.doc = base.doc || {};
   var $ = base.doc;

   if( $.toString() != base.toString() + '.doc' ){ 
      $.toString = function(){ return base.toString() + '.doc'; }; 
   }

   
   // find index position of {item}
   // in Array {arr} - return -1, if
   // item not found
   // case insensitive
   function findIndex( s, arr ) {
   
      var i, idx;
      var last = arr.length;
      var searchFor = s.toLowerCase();
      
      for ( i = 0; i < last; i++) {
      
         idx = ( searchFor == arr[i].toLowerCase() ) ? i : -1;
         // quit on first "found"
         if( -1 != idx){ break; }
      }
      
      return idx;
   }

   function addAttributes( elem, attribs ){
   
      var count = 0;
      var attr = [];

      if( attribs.length <= 0 ){ return; }
      
      for( count=0; count<attribs.length; count++ ){
      
         attr = attribs[ count ].split( '=' );
         
         if( attr.length === 2 ){
         
            elem.setAttribute( attr[ 0 ], attr[ 1 ] );
         }
      }
   }

   /*
      Append text to a target element, surrounded by an optional elemType tag
      This is only intended as a debugging aid, so limited tags are supported.
      
      targetId - the id of the element where the text will be appended.
      If it is not a string, it is assumed to be an element instead of an id
      text - the text to append, it is appended to a new element if that 
      parameter is included. The text is ignored if the elemType is 'ul'.
      elemType - an optional tag type to wrap the text in.
      attrList - an array of attributes to add to the tag 
               created by elemType. The format is 'attr=value'. Note that the values are
               not quoted.
   */
   $.appendText = function( targetId, text, elemType, attrList ){
   
      var targElem = null;
      var wrapElem = null;
      var validElem = false;
      var validElemList = [ 'p', 'div', 'li', 'ul', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
      
      var surElem = elemType || "";
      attrList = attrList || [];
      
      surElem = surElem.toLowerCase();
      
      // assume that targetId is the element if it's not a string, otherwise get it from the string
      if( null != targetId ){
      
         targElem = ( typeof targetId === 'string' ) ? 
               document.getElementById( targetId ) : targetId;
      }
      
      if( null === targElem ){ 
         throw Error( "Invalid target in " + $.toString() + ".appendText: " + targetId ); 
      }
      
      // make the wrapping element if needed
      if( surElem != '' ){
      
         if( findIndex( surElem, validElemList ) > -1 ){
   
            try {

               wrapElem = document.createElement( surElem );
            }
            catch( e ) {
            
               throw Error( "Invalid character in element type in " + $.toString() + ".appendText: " + surElem ); 
            }

            targElem.appendChild( wrapElem );

            if( attrList.length > 0 ){
            
               addAttributes( wrapElem, attrList )
            }
         }
         
         else {
         
            throw Error( "Invalid element type in " + $.toString() + ".appendText: " + surElem ); 
         }
      }

      // attach to the wrapper if we made one
      if( null !== wrapElem ){
      
         // no text on a ul element
         if( surElem != 'ul' ){
         
            wrapElem.appendChild( document.createTextNode( text ) );
         }
         
         return wrapElem;
      }
      
      // append the text directly to the target
      else{
      
         targElem.appendChild( document.createTextNode( text ) );
         return targElem;
      }
   };
   
})( com.YourFriendPaul.util );


// ===============================================================================
// ===============================================================================
/*
The namespace stuff is really pretty useless, but it stays because
it's still be using it in some older code
*/

// can add one or multiple namespaces to 'com.YourFriendPaul'
com.YourFriendPaul.util.addNamespace = function(){
   var args = arguments;
   var root = null;	// returns null if nothing is done
   var nameParts, iCount, nameStart, iArg;
   
   for( iArg=0; iArg<args.length; iArg++ ){
   
      nameParts = args[ iArg ].split(".");
      root = com.YourFriendPaul;
      // skip over com.YourFriendPaul if it is there
      nameStart = ( nameParts[ 0 ] === "com" && nameParts[ 1 ] === "YourFriendPaul" ) ? 2 : 0;
      for( iCount=nameStart; iCount<nameParts.length; iCount++ ){
         // if root[ nameParts[ iCount ] ] is defined and not null, don't change it, else make it an object
         root[ nameParts[ iCount ] ] = root[ nameParts[ iCount ] ] || {};
         root = root[nameParts[ iCount ]];
      }
   }
   return root;
};

/**********************************************************************/
/**********************************************************************/
/**********************************************************************/
com.YourFriendPaul.util.nsUsedLevel = function( root, ns ){

   var prop;
   
   if( ns  ){
      for( prop in root ){
         if( null !== prop ){
      
            if( prop === ns ){
               return true;
            }
         }
      }
   }
   
   return  false;
};

// returns true if nsIn exists in com.YourFriendPaul
com.YourFriendPaul.util.isNamespaceUsed = function( nsIn ){

   var root = com.YourFriendPaul;
   var found = false;
   var nameParts, iCount, nameStart;
   var ns = String( nsIn || "" );
   
   nameParts = ns.split(".");
   // skip over com.YourFriendPaul if it is there
   nameStart = ( nameParts[ 0 ] === "com" && nameParts[ 1 ] === "YourFriendPaul" ) ? 2 : 0;
   
   for( iCount=nameStart; iCount<nameParts.length; iCount++ ){
   
      if( nameParts[ iCount ] ){
      
         if( !root.util.nsUsedLevel( root, nameParts[ iCount ] ) ){

            return found;
         }

         root = root[ nameParts[ iCount ] ];
      }
   }

   found = true;
   return found;

};
