/* -----------------------------------------------------------------------------
 * Class: TinySpicyMenu
 * This JavaScript class generates a fly-out menu created from the structure of
 * an existing unordered list.  The intent of this class is to make it as easy
 * as possible to create and modify a drop-down menu that contracts or expands
 * based on the content of each menu item (for easy internationalization) and to
 * provide drop-down menu items that will have a z-index priority over ANY
 * HTML element (yes, even select boxes and iframes in IE).
 *
 * Populating Your Menu:
 * To create a new menu, start with an unordered list with an ID of
 * "tsNavigation."  This will serve as the container for your top-level menu
 * items that will remain visible at all times.
 *
 * Next, create your first batch of LI elements which will serve as the
 * top-level menu items.  If your LI elements have a class property assigned to
 * them, this script will grab it and use it as the default (i.e. "mouseout")
 * class name for that element.
 *
 * When creating submenus, simply add another unordered list to your top-level
 * LI element.  Again, class names will be extracted from each LI element and
 * used as the default class name.
 *
 * This script will add the class name "over" to each LI element's mouseover
 * event.  So if you want to provide a different style for you LI element
 * upon mouseover, be sure to define a class named "over."
 *
 * Assigning Actions to Your Menu Items:
 * This class extracts the action assigned to an LI's child anchor tag and
 * assigns that action to the onclick event of the LI element.  This works with
 * actions assigned via the href OR onclick properties of the anchor tag.  So
 * all you need to do to add navigation actions to your menu is to include an
 * anchor tag with an href or onclick property describing the action.
 *
 * Browser Bugs Addressed:
 * Internet Explorer treats DOM-generated iFrames as iFrames with a non-SSL
 * source.  This will generate the popup "This page contains secure and
 * insecure items.  Continue?" alert.  To address this, we use the IE-
 * specific wannabe-DOM-but-really-isn't method insertAdjacentHTML.
 *
 * FireFox has a tendency to proportionately misinterpret the occupied 
 * horizontal space within list items.  This presents a problem when we attempt 
 * to programmatically determine the widest list item and then assign that width 
 * to all the list items in the submenu.  The result is an unpleasantly wide 
 * submenu.  To address this, when Firefox is detected in the User-Agent 
 * variable, we assess the widest list item by the item contents, not by the 
 * width of the line item itself.
 */
function TinySpicyMenu(topLevelID) {
    var topLevelUL       = document.getElementById(topLevelID);
    var _SIZE_TO_WIDTH   = 0;
    var _SIZE_TO_HEIGHT  = 1;
    
    var _isIE = (navigator.userAgent.indexOf("MSIE") != -1);
    var _isFF = (navigator.userAgent.toUpperCase().indexOf('FIREFOX') != -1);
  
    var _ieIFrame;
    if (_isIE) {        
        document.body.insertAdjacentHTML("afterBegin",'<iframe style="display:none" src="javascript:false" id="ieIFrame"></iframe>');
        var _newIFrame = document.getElementById("ieIFrame");    
        _ieIFrame = _newIFrame.cloneNode(false);
        _ieIFrame.style.display = "block";
    }

    var _sourceIFrame = (_isIE) ? _ieIFrame : document.createElement("iframe");

    _sourceIFrame.style.zIndex     = "0";
    _sourceIFrame.style.border     = "none";
    _sourceIFrame.style.position   = "absolute";
    _sourceIFrame.style.background = "#000"; // Makes it visible in Firefox
  
    this.errMsg   = errMsg;
    this.reRender = reRender;
    
    // -[ STEP 1: Top-Level ]---------------------------------------------------
    // We threw an error here before, but this caused problems on pages where there is no top level nav
    if (topLevelUL && topLevelUL.hasChildNodes()) {
        var listElements = topLevelUL.tsChildLIs = getChildNodesByNodeName(topLevelUL, "LI");
      
    // -[ STEP 2: Fly-outs]-----------------------------------------------------
        for (var i = 0; i < listElements.length; i++) {
            var listElement = listElements[i];
            var anchorElement = retrieveAnchor(listElement);
            if (anchorElement != null) {
                copyActionFromAnchorToLI(listElement,anchorElement);
                listElement.onclick = function() { executeDefaultAction(this); };
            }
            listElement.tsDefaultClassName = (listElement.className) ? listElement.className : undefined;
            listElement.tsMouseoverClassName = (listElement.className) ? listElement.className + " over" : "over";
            listElement.tsDisableParentLIAction = function() { return };
            listElement.tsEnableParentLIAction = function() { return };
            assignMouseOverAndOut(listElement);
            discoverSubmenu(listElement);
            
            if (listElement.tsHasSubmenu) assignPosition(listElement.tsSubMenuUL);
        }
        
        hideEverything();
    }

		function callHrefAndStopEventFromBubbling(event) {
			var anchorElement = this;
			var onclick = anchorElement.tsOnclick;
			gotToURL(anchorElement.href, anchorElement.target, onclick);
			Event.stop(event);
		}
		
		
    function copyActionFromAnchorToLI(listElement,anchorElement) {
				var anchorOnclick = anchorElement.onclick;
        if (anchorOnclick) anchorElement.tsOnclick = anchorOnclick;
				
				anchorElement.onclick = null;
				
				//Since we can have image inside the anchor tag and we have the onclick function stored with anchor.tsonclick
				//We need the anchorElement to get access to the tsonclick function, even if they clicked on an image(inside anchor element).
				Event.observe(anchorElement, "click", callHrefAndStopEventFromBubbling.bind(anchorElement));
				listElement.tsAction = anchorElement.href;
				listElement.tsTarget = anchorElement.target;
    }
		
  
    /* ---------------------------------------------------------------------------
     * Function: discoverSubmenu
     * This function performs multiple tasks through the submenu discovery
     * process.
     *
     * The first step involves tying the parent LI and the child UL together
     * via references to eachother set as custom properties for both elements.
     *
     * The second step iterates through each child LI of the UL item passed into
     * this function.  For each LI item discovered, behavior is assigned and
     * the widest width of the widest LI is stored (to be used to apply that
     * width to all LI items for this submenu later).
     *
     * Parameters:
     *      menuItemLI - Any given menu LI element
     *
     * Returns:
     *      nothing
     */     
    function discoverSubmenu(menuItemLI) {
        var subMenuUL = getSingleNodeByNodeName(menuItemLI, "UL");
        if (subMenuUL == null) {
            menuItemLI.tsHasSubmenu = false;
            return;
        }
        var childLIs  = getChildNodesByNodeName(subMenuUL, "LI");
        
        menuItemLI.tsHasSubmenu = true;
        menuItemLI.tsSubMenuUL  = subMenuUL;
        subMenuUL.tsParentLI    = menuItemLI;
        subMenuUL.tsChildLIs    = childLIs;
        
        if (_isIE) {
            menuItemLI.tsMouseoverEvents.push('this.tsSubMenuUL.style.display = "block"');
            menuItemLI.tsMouseoutEvents.push('this.tsSubMenuUL.style.display = "none"');
        } else {
            menuItemLI.addEventListener("mouseover", function() { this.tsSubMenuUL.style.display = "block" }, false);
            menuItemLI.addEventListener("mouseout", function() { this.tsSubMenuUL.style.display = "none" },false);
        }
        
        var widestLIWidth = 0;
        var ffExtraSpace  = 0;
        for (var i = 0; i < childLIs.length; i++) {
            var listElement = childLIs[i];
            var thisLIsOffsetWidth = 0;
            
            if (_isFF && listElement.hasChildNodes()) {
                var validChild = null;
               validChild = (listElement.firstChild.nodeType == 3) ? listElement.childNodes[1] : listElement.firstChild;
                
                if (widestLIWidth == 0) ffExtraSpace = listElement.offsetWidth - validChild.offsetWidth; // Calculate extra spacing added via styles                
                if (validChild) thisLIsOffsetWidth = validChild.offsetWidth + ffExtraSpace;
            } else {
                thisLIsOffsetWidth = listElement.offsetWidth;
            }
            
            if (widestLIWidth < thisLIsOffsetWidth) {
                widestLIWidth = thisLIsOffsetWidth;
            }
      
            listElement.tsWidthDifference = getWidthDifference(thisLIsOffsetWidth, listElement);
            listElement.tsParentUL = subMenuUL;
            listElement.tsDefaultClassName = (listElement.className) ? listElement.className : undefined;
            listElement.tsMouseoverClassName = (listElement.className) ? listElement.className + " over" : "over";
            
            var anchorElement = retrieveAnchor(listElement);
            if (anchorElement != null) {
                copyActionFromAnchorToLI(listElement,anchorElement);
                listElement.onclick = function() { executeDefaultAction(this); };
            }
            listElement.tsDisableParentLIAction = function() { 
                if (this.tsParentUL.tsParentLI) {
                    this.tsParentUL.tsParentLI.onclick = null;
                    if (this.tsParentUL.tsParentLI.tsDisableParentLIAction) this.tsParentUL.tsParentLI.tsDisableParentLIAction();
                }
            };
            
            listElement.tsEnableParentLIAction = function() {
                var thisLIelement = this.tsParentUL.tsParentLI;
                var thisAnchorElement = retrieveAnchor(thisLIelement);
                if (thisLIelement && thisAnchorElement) {
                    copyActionFromAnchorToLI(thisLIelement,thisAnchorElement);
                    thisLIelement.onclick = function() { executeDefaultAction(this); };
                    if (thisLIelement.tsEnableParentLIAction) thisLIelement.tsEnableParentLIAction();
                }
            }
      
            assignMouseOverAndOut(listElement);
            discoverSubmenu(listElement);
        }
        subMenuUL.tsWidestLIWidth = widestLIWidth;
    }
    
    /* -------------------------------------------------------------------------
     * Function: assignPosition
     * Calculates and positions a submenu UL element in relation to its parent
     * LI element.  After positioning is complete, this function prepends an
     * iframe of equal width and height behind the UL's child LI elements.
     *
     * Parameters:
     *      ULRef - A reference to the UL element object
     *
     * Returns:
     *      nothing
     */
    function assignPosition(ULRef) {
        var parentXY    = findPosition(ULRef.tsParentLI);
        var widestWidth = null;
        
        if (typeof(ULRef.tsParentLI.tsParentUL) == "undefined") { 
        // 1st-level fly-out
            var parentLIHeight = ULRef.tsParentLI.offsetHeight;
            var parentLIWidth  = ULRef.tsParentLI.offsetWidth;
            var alignment = $(ULRef.getElementsByTagName("LI")[0]).getStyle("text-align");
            if(alignment == "left"){            
              if (ULRef.tsWidestLIWidth < parentLIWidth) ULRef.tsWidestLIWidth = parentLIWidth;
              widestWidth = ULRef.tsWidestLIWidth;
              ULRef.style.left = parentXY[0] + "px";
              ULRef.style.top  = (parentXY[1] + ULRef.tsParentLI.offsetHeight) + "px";
            }
            if(alignment == "center"){            
              if (ULRef.tsWidestLIWidth < parentLIWidth) ULRef.tsWidestLIWidth = parentLIWidth;
              widestWidth = ULRef.tsWidestLIWidth;
              ULRef.style.left = parentXY[0] + parentLIWidth/2 - widestWidth/2 + "px";
              ULRef.style.top  = (parentXY[1] + ULRef.tsParentLI.offsetHeight) + "px";
            }
        } else {
        // All other fly-outs
            widestWidth      = ULRef.tsWidestLIWidth;
            ULRef.style.left = ULRef.tsParentLI.tsParentUL.tsWidestLIWidth - 1 + "px";
            ULRef.style.top  = -1 + "px";
        }
        
        // Add iframe
        var newIframe   = _sourceIFrame.cloneNode(false);
        var xDifference = 0;
        var yDifference = 1;
        
        //It is very peculiar, if the following two lines are not present IE 6 and 7 throw and invalid argument error on resize
        var newHeight = ULRef.offsetHeight - yDifference;
        if (newHeight < 0) newHeight = 0;
       	newIframe.style.width  = (ULRef.tsWidestLIWidth - xDifference)  + "px";
     	newIframe.style.height = newHeight + "px";

        ULRef.insertBefore(newIframe, ULRef.tsChildLIs[0]);
        
        for (var i = 0; i < ULRef.tsChildLIs.length; i++) {
            var LIWidthDifference = ULRef.tsChildLIs[i].tsWidthDifference;
            ULRef.tsChildLIs[i].style.width = (widestWidth - LIWidthDifference) + "px";
            if (ULRef.tsChildLIs[i].tsHasSubmenu) assignPosition(ULRef.tsChildLIs[i].tsSubMenuUL);
        }
    }
    
    /* -------------------------------------------------------------------------
     * Function: hideEverything
     * Loops through the first fly-out LI elements and calls <hideSubmenuUL> on
     * each of those LI element's child UL element.
     *
     * Parameters:
     *      none
     *
     * Returns:
     *      nothing
     */
    function hideEverything() {
        for (var i = 0; i < topLevelUL.tsChildLIs.length; i++) {
            if (topLevelUL.tsChildLIs[i].tsHasSubmenu) hideSubmenuUL(topLevelUL.tsChildLIs[i].tsSubMenuUL);
        }
    }
    
    /* -------------------------------------------------------------------------
     * Function: hideSubmenuUL
     * Hides submenu UL elements (using deisplay:none) from the bottom of the
     * hierarchy upward.
     *
     * Parameters:
     *      ULRef - A reference to the UL element object
     *
     * Returns:
     *      nothing
     */
    function hideSubmenuUL(ULRef) {
        for (var i = 0; i < ULRef.tsChildLIs.length; i++) {
            if (ULRef.tsChildLIs[i].tsHasSubmenu) hideSubmenuUL(ULRef.tsChildLIs[i].tsSubMenuUL);
        }
        ULRef.style.display = "none";
        ULRef.style.visibility = "visible";
    }
    
    /* -------------------------------------------------------------------------
     * Function: getHeightDifference
     * Calls the <getDifference> function on the provided element object
     * reference.
     *
     * Parameters:
     *      offsetHeight - The offsetHeight of the provided element object
     *                     reference
     *      elementObj   - A reference to the element object to measured
     *
     * Returns:
     *      A number representing the difference between the offset height
     *      before and after having the original offset height assigned to the
     *      style.height attribute of the provided element object.
     */
    function getHeightDifference(offsetHeight, elementObj) {
        return getDifference(_SIZE_TO_HEIGHT, offsetHeight, elementObj);
    }
    
    /* -------------------------------------------------------------------------
     * Function: getWidthDifference
     * Calls the <getDifference> function on the provided element object
     * reference.
     *
     * Parameters:
     *      offsetWidth - The offsetWeight of the provided element object
     *                    reference
     *      elementObj  - A reference to the element object to measured
     *
     * Returns:
     *      A number representing the difference between the offset width
     *      before and after having the original offset width assigned to the
     *      style.width attribute of the provided element object.
     */
    function getWidthDifference(offsetWidth, elementObj) {
        return getDifference(_SIZE_TO_WIDTH, offsetWidth, elementObj);
    }
    
    /* -------------------------------------------------------------------------
     * Function: getDifference
     * Determines the difference between the offset measurement of an element
     * object and the assignment of that offset measurement to the applicable
     * style property of that element object.
     *
     * The process is simple: using the offset of the object provided to this
     * function, set that offset value as the applicable style property of that
     * object (i.e. style.width or style.height).  Then take the offset
     * measurement of the object again and minus the original offset value.
     * This aids in setting the correct height of an object even after padding
     * and border have been applied via stylesheet definition.
     *
     * Parameters:
     *      sizeType   - A number representing our intent to determine the width
     *                   or the height difference of the given object.
     *      offset     - The original offset value
     *      elementObj - A reference to the element object being measured
     *
     * Returns:
     *      A number representing the difference between the offset before and
     *      after having the original offset assigned to the style attribute of
     *      the provided element object.
     */
    function getDifference(sizeType, offset, elementObj) {
        switch (sizeType) {
            case _SIZE_TO_HEIGHT:
                elementObj.style.height = offset + "px";
                var difference = elementObj.offsetHeight - offset;
                //elementObj.style.height = (offset - difference) + "px";
                //elementObj.style.height = (_isIE) ? "auto" : undefined;
                return (difference);
                break;
            case _SIZE_TO_WIDTH:
                elementObj.style.width = offset + "px";
                var difference = elementObj.offsetWidth - offset;
                //elementObj.style.width = (offset - difference) + "px";
                //elementObj.style.width = (_isIE) ? "auto" : undefined;
                return (difference);
                break;
        }
        return null;
    }

    /* -------------------------------------------------------------------------
     * Function: findPosition
     * Returns the X and Y coordinates of the given object.  This method was
     * extracted from <http://www.quirksmode.org/> and then modified to fit.
     *
     * Parameters:
     *      obj - A reference to the element object to be measured
     *
     * Returns:
     *      And array with the X coordinate as element 0 and the Y coordinate
     *      as element 1.
     */
    function findPosition(obj) {
        var XY = new Array();
        XY[0] = 0;
        XY[1] = 0;
        
        if (obj.offsetParent) {
            while (obj.offsetParent) {
                XY[0] += obj.offsetLeft;
                XY[1] += obj.offsetTop;
                obj = obj.offsetParent;
            }
        }
        
        return XY;
    }
    
    /* -------------------------------------------------------------------------
     * Function: getChildNodesByNodeName
     * Returns an array of child nodes matching the nodeName parameter provided.
     *
     * Parameters:
     *      objRef   - A reference to the element object to be evaluated
     *      nodeName - The nodeName we'll be searching for
     *
     * Returns:
     *      An array of element objects matching the nodeName provided
     */
    function getChildNodesByNodeName(objRef, nodeName) {
        var applicableChildNodes = [];
        
        if (objRef.hasChildNodes()) {
            for (var i = 0; i < objRef.childNodes.length; i++) {
                if (objRef.childNodes[i].nodeName == nodeName) applicableChildNodes.push(objRef.childNodes[i]);
            }
        }
        
        return applicableChildNodes;
    }
    
    /* -------------------------------------------------------------------------
     * Function: getSingleNodeByNodeName
     * Returns a reference to an element object matching the nodeName provided.
     *
     * Parameters:
     *      objRef   - A reference to the element object to be evaluated
     *      nodeName - The nodeName we'll be searching for
     *
     * Returns:
     *      A reference to the element object matching the nodeName provided
     */
    function getSingleNodeByNodeName(objRef, nodeName) {
        if (objRef.hasChildNodes()) {
            for (var i = 0; i < objRef.childNodes.length; i++) {
                if (objRef.childNodes[i].nodeName == nodeName) return objRef.childNodes[i];
            }
        }
        return null;
    }
    
    /* -------------------------------------------------------------------------
     * Function: executeDefaultAction
     * If a menu LI element contains an anchor tag, this function will extract
     * the string contained in the href property OR the string contained in
     * the onclick property if it exists and then apply it to the conclick
     * attribute of the parent LI element.
     *
     * In the case of the href property, this function will prepend the string
     * "document.location=" so that the string can be eval'ed and handled
     * properly by JavaScript.
     *
     * Parameters:
     *      LIref - A reference to the LI element object
     *
     * Returns:
     *      Nothing - it fires the approriate action from the LIref object
     */
   function executeDefaultAction(LIref) {
        var anchor = retrieveAnchor(LIref);
        if (anchor != null) {
						var onclick = anchor.tsOnclick;
						var returnValue = gotToURL(LIref.tsAction, LIref.tsTarget, onclick);
						if(onclick) return returnValue;
        }
    }
		
		function gotToURL(url, target, onclick) {
			if (url) {
        if (target) {
          window.open(url, target);
        } else {
			      document.location = url;
				}
      }
			if (onclick) return onclick();
		}

    function retrieveAnchor(thisElement) {
	return getSingleNodeByNodeName(thisElement, "A");
    }

    /* -------------------------------------------------------------------------
     * Function: errMsg
     * Tiny function that adds decoration to a JavaScript error to be thrown.
     *
     * Parameters:
     *      msg - String containing an error message
     *
     * Returns:
     *      A string with extra stuff
     */
    function errMsg(msg) {
        return "[" + this.scriptName + "]: " + msg;
    }
    
    /* -------------------------------------------------------------------------
     * Function: assignMouseOverAndOut
     * Assigns predefined mouseover and mouseout actions to the provided LI
     * element object.
     *
     * Currently, this function simply changes the class name of the given
     * object based on its tsDefaultClassName and tsMouseoverClassName
     * properties, defined in the <discoverSubmenu> function.
     *
     * In addition, this function adds the tsEnableParentLIAction and
     * tsDisableParentLIAction element functions (also defined in the
     * <discoverSubmenu> function) to the mouseover and mouseout events of the
     * given LI element object.  The implementation of all this is done
     * differently for IE than it is for all other (read Firefox and Safari)
     * browsers.
     *
     * Parameters:
     *      LIRef - A reference to the LI element object
     *
     * Returns:
     *      nothing
     */
    function assignMouseOverAndOut(LIRef) {
        if (_isIE) {
            LIRef.tsMouseoverEvents = [];
            LIRef.tsMouseoutEvents  = [];
            
            LIRef.tsMouseoverEvents.push('this.className = this.tsMouseoverClassName');
            LIRef.tsMouseoverEvents.push('this.tsDisableParentLIAction()');
            LIRef.tsMouseoutEvents.push('this.className = this.tsDefaultClassName');
            LIRef.tsMouseoutEvents.push('this.tsEnableParentLIAction()');
            
            LIRef.onmouseover = function() {
                for (var k = 0; k < this.tsMouseoverEvents.length; k++) { eval(this.tsMouseoverEvents[k]) }
            }
            
            LIRef.onmouseout = function() {
                for (var k = 0; k < this.tsMouseoutEvents.length; k++) { eval(this.tsMouseoutEvents[k]) }
            }
        } else {
            LIRef.addEventListener("mouseover", function() { this.className = this.tsMouseoverClassName },false);
            LIRef.addEventListener("mouseover", function() { this.tsDisableParentLIAction() }, false);
            LIRef.addEventListener("mouseout", function() { this.className = this.tsDefaultClassName },false);
            LIRef.addEventListener("mouseout", function() { this.tsEnableParentLIAction() }, false);
        }
    }
    
    /* -------------------------------------------------------------------------
     * Function: reRender
     * Repositions the the sub-menu and tertiary menu elements based on the
     * window size.  Add a call to this function to the browser's onresize()
     * event.
     *
     * Parameters:
     *			none
     *
     * Returns:
     *      nothing
     */
    function reRender() {
        if (topLevelUL && topLevelUL.tsChildLIs) { 
          for (var i = 0; i < topLevelUL.tsChildLIs.length; i++) {
              if (topLevelUL.tsChildLIs[i].tsHasSubmenu) assignPosition(topLevelUL.tsChildLIs[i].tsSubMenuUL);
          }
        }
    }
}

function createTinySpicyMenu(topLevelID) {
	tsmenu = new TinySpicyMenu(topLevelID);
}

function menu_autoload(){
	createTinySpicyMenu('tsNavigation');
}

function tinySpicyResize() {
  if (typeof tsmenu != 'undefined') {
    tsmenu.reRender();
  }
}

/* 
	Adding TinySpicy to the window onload call.  Checking to see if dojo is present, and using that if it is. 
	Otherwise we are adding it to the normal window onload.
*/

var loaded;
if(!loaded){
	Event.observe(window, "load", menu_autoload);
  	Event.observe(window, "resize", tinySpicyResize);
	loaded = true;
}




