/*
  April 2005
  Michael Rivera

  Yet another drop-down menu script.

  This is a *good* script, because
  it transforms simple lists into
  dynamic menus, magically.

  Delays the dissapearing-act for a moment.

  "Generally" compatible with Mozilla / IE / Netscape 6

  Uses '_pm' prefix (pm = "Popup Menu") for its globals

  Example usage, assuming 'rootmenu' is id of
  a UL (unordered list) node:

    function init() {
      addPop( 'rootmenu' , [ ['165px',0] , ['165px',0] ] );
    }

    addEvent(window, 'load', init);

*/

_pm = null; 
_pmDelay = 1000; // miliseconds
_pmCurrentMenu = null;
_pmShowableRoots = [];
_pmTimeoutStack = [];
_pmUniversalIDs = 0;
_pmMouseOnChild = null;

function getUniversalID() {
  _pmUniversalIDs += 1;
  return _pmUniversalIDs;
}

function childIsHighlighted(LInode) {
  // Behavior with null value is undefined
  if(_pmMouseOnChild == null) return false;
  
  if(LInode.targetULNode) {
    for(var i=0; i<LInode.targetULNode.childNodes.length; i++) {
      if(LInode.targetULNode.childNodes[i].universalID
	 && LInode.targetULNode.childNodes[i].universalID == _pmMouseOnChild) {
	_pmMouseOnChild = LInode.universalID;
	return true;
      }
    }
  }

  return false;
}

/*
  Clear any time outs, hide current menu,
  and set this menu to be the current menu.
*/
function showMenuNow() {
  clearTimeouts();

  InternetExplorer = (document.all && this.currentStyle)
  NN6AndMozilla = document.getElementById && !document.all;

  // This is a hack to avoid hiding
  // a child menu that was immediately just shown.
  if( childIsHighlighted(this) ) return;

  // Hide all except your parent
  recursiveHide(_pmCurrentMenu, this.parentULNode);
  recursiveHideParent(_pmCurrentMenu, this.parentULNode);

  _pmCurrentMenu = this.targetULNode;

  var nodeToShow = this.targetULNode;
  if(InternetExplorer) {
    nodeToShow.style.left = this.leftIndentOnShow;
    nodeToShow.style.top = this.topIndentOnShow;
    nodeToShow.visibility = "visible";
  }
  else if(NN6AndMozilla) {
    nodeToShow.style.left = this.leftIndentOnShow;
    nodeToShow.style.top = this.topIndentOnShow;
    nodeToShow.style.visibility = "visible";
  }

  // Set, so that parents can recognize
  // that their child is being shown.
  _pmMouseOnChild = this.universalID;
}

function hideMenuSoon() {
  timeoutID = setTimeout("hideAll()", _pmDelay);
  _pmTimeoutStack[_pmTimeoutStack.length] = timeoutID;
}

function clearTimeouts() {
  for(var i=0; i < _pmTimeoutStack.length; i++)
    clearTimeout(_pmTimeoutStack[i]);
  _pmTimeoutStack = [];
}

/*
  Hides everything except ULNodeToAvoid,
  and ULNodeToAvoid's parent.
*/
function recursiveHide(targetULNode, ULNodeToAvoid) {
  if(targetULNode == null) return;
  if(targetULNode == ULNodeToAvoid) return;

  // Recursively hide children
  for(var i=0; i < targetULNode.childNodes.length; i++) {
    if(targetULNode.childNodes[i].tagName == 'LI'
       && targetULNode.childNodes[i].targetULNode) {
      recursiveHide(targetULNode.childNodes[i].targetULNode, ULNodeToAvoid);
    }
  }

  // Then, hide self
  InternetExplorer = (document.all && targetULNode.currentStyle)
  NN6AndMozilla = document.getElementById && !document.all; 

  if(InternetExplorer) {
    targetULNode.style.left="-9000px";
    targetULNode.visibility = "hidden";
  }
  else if(NN6AndMozilla) {
    targetULNode.style.visibility = "hidden";   
  }
}

/*
  Post-recursively hide all parents; stop hiding
  when ULNodeToAvoid is hit, or when a root
  menu node is hit.
*/
function recursiveHideParent(targetULNode, ULNodeToAvoid) {
  if(targetULNode == null) return;
  if(targetULNode == ULNodeToAvoid) return;

  InternetExplorer = (document.all && targetULNode.currentStyle)
  NN6AndMozilla = document.getElementById && !document.all; 

  if(InternetExplorer) {
    targetULNode.style.left="-9000px";
    targetULNode.visibility = "hidden";
  }
  else if(NN6AndMozilla) {
    targetULNode.style.visibility = "hidden";   
  }

  if(targetULNode.parentLINode
     && targetULNode.parentLINode.parentULNode
     && !targetULNode.parentLINode.parentULNode.isRootID)
    recursiveHideParent(targetULNode.parentLINode.parentULNode,
	                ULNodeToAvoid);
}


/*
  Only called when mouse has not hovered over any menu
  for _pmDelay amount of time.

  Resets the menu states.
*/
function hideAll() {
  // Sometimes timeouts happen even if they
  // are cleared; check timeoutStack to assure
  // this won't happen.

  if(_pmTimeoutStack.length == 0) return;

  _pmTimeoutStack = []
  _pmCurrentMenu = null;
  _pmMouseOnChild = null;

  // Hide all roots
  for(var i=0; i < _pmShowableRoots.length; i++) {
    var curRoot = _pmShowableRoots[i];

    // Recursively hide children
    for(var j=0; j < curRoot.childNodes.length; j++) {

      if(curRoot.childNodes[j].tagName == 'LI'
	 && curRoot.childNodes[j].targetULNode)
	recursiveHide(curRoot.childNodes[j].targetULNode, null);
    }
  }

}

/*
  Recursively sets instructions for popup menus
  of this list.
 */
function addPop(rootName, leftTopIndentations) {
  menuNode = document.getElementById(rootName);

  rootID = getUniversalID();
  menuNode.isRootID = rootID;
  _pmShowableRoots[_pmShowableRoots.length] = menuNode;

  InternetExplorer = (document.all && menuNode.currentStyle)
  NN6AndMozilla = document.getElementById && !document.all; 

  /*
    Will do a breadth-first search through the list-tree
  */
  var nodeQueue = new Array();
  FRONTINDEX = 0;
  NODEINDEX = 0;
  OFFSETSINDEX = 1;

  nodeQueue[nodeQueue.length] = new Array(menuNode, leftTopIndentations);
  while(nodeQueue.length != 0) {

    /*
      Pop (node, indents) off of queue
    */
    var nextNode = nodeQueue[FRONTINDEX][NODEINDEX];
    var curOffsets = nodeQueue[FRONTINDEX][OFFSETSINDEX];
    nodeQueue = nodeQueue.slice(1);

    /*
      Search for LI tags with a child UL
      (items that should trigger a popup)
    */
    for(i=0; i<nextNode.childNodes.length; i++) {

      if(nextNode.childNodes[i].tagName == 'LI') {

	var menuLIEntry = nextNode.childNodes[i];
	var possibleULNodes = menuLIEntry.childNodes;

	menuLIEntry.parentULNode = nextNode;
	menuLIEntry.universalID = getUniversalID()
	menuLIEntry.rootID = rootID;

	/*
	  Search for child UL in this menu
	*/
	for(j=0; j<possibleULNodes.length; j++) {

	  /* Is this a sub menu? */

	  if (possibleULNodes[j].tagName == "UL") {
	    menuLIEntry.leftIndentOnShow = curOffsets[FRONTINDEX][0];
	    menuLIEntry.topIndentOnShow = curOffsets[FRONTINDEX][1];
	    menuLIEntry.onmouseover=showMenuNow;
	    menuLIEntry.onmouseout=hideMenuSoon;
	    menuLIEntry.targetULNode = possibleULNodes[j];
            possibleULNodes[j].parentLINode = menuLIEntry;

	    /*
	      Attempt to shift to next value in indentation,
	      prior to enqueueing this new menu.
	      If there is no deeper such level, use the current level.
	    */
	    var newOffsets = curOffsets;
	    if(newOffsets.length > 1) {
	      newOffsets = newOffsets.slice(1);
	    }

	    /* Enqueu this UL list to be searched */
	    nodeQueue[nodeQueue.length] = new Array(possibleULNodes[j],
						    newOffsets
						    );
	  }
	}
      }
    }
  }
}

function addEvent(obj, evType, func){ 
 if (obj.addEventListener){ 
   obj.addEventListener(evType, func, true); 
   return true; 
 } else if (obj.attachEvent){ 
   var r = obj.attachEvent("on"+evType, func); 
   return r; 
 } else { 
   return false; 
 } 
}
