// CASCADING POPUP MENUS by Angus Turnbull http://gusnz.cjb.net
//
// Scroll down past the first half of this script to edit. Please leave the above line in
// the source, and a link to my site in your page somewhere. After all, if you found these
// scripts useful, won't your visitors?
//   I suggest cutting and pasting this entire SCRIPT element into a .JS file and including
// it via <script language="JavaScrtipy1.2" src="myfilename.js">. Saves an awful lot of
// coding that way when you want to update it :). 


// *** CROSS-BROWSER COMPATIBILITY ***

var isDOM; // =  (document.getElementById ? true : false); 
var isIE4; // = ((document.all && !isDOM) ? true : false);
var isNS4; // = (document.layers ? true : false);
var isDyn; // = (isDOM || isIE4 || isNS4);

function getRef(id)
{
  if (isDOM) 
    return document.getElementById(id);

  if (isIE4) 
    return document.all[id];

  if (isNS4) 
    return document.layers[id];
}

function getSty(id)
{
  return (isNS4 ? getRef(id) : getRef(id).style);
} 


// *** MOUSEOVER/OUT CONTROL FUNCTIONS ***

// Hide timeout.
var popTimer;
// Arrays holding highlighted menu items.
var litNow;

function popOver(menuNum, itemNum)
{
  clearTimeout(popTimer);

  // Hide all other menus & dim old highlighted items, still showing this menu.
  hideAllBut(menuNum);

  // Get tree of parent menu items and light them up - global variable!
  litNow = getTree(menuNum, itemNum);
  changeCol(true);

  // Get target menu to show - if it's nonzero, position & show it.
  targetNum = menu[menuNum][itemNum].target;
  if (targetNum > 0)
  {
    // Get current menu position - menu position plus item position in menu.
    thisX = parseInt(menu[menuNum][0].ref.left) + parseInt(menu[menuNum][itemNum].ref.left);
    thisY = parseInt(menu[menuNum][0].ref.top) + parseInt(menu[menuNum][itemNum].ref.top) + 5;

    // Add those to the target's offset to set the target's position, show it.
    with (menu[targetNum][0].ref)
    {
      left = thisX + menu[targetNum][0].x;
      top = thisY + menu[targetNum][0].y;
      visibility = 'visible';
    }
  }
}

function popOut(menuNum, itemNum)
{
  // If it's a root menu item that doesn't trigger a popout, hide now, else set timeout
  // to hide all menus in 1/2 sec... remember, another mouseover clears the timeout.
  if ((menuNum == 0) && !menu[menuNum][itemNum].target) 
    hideAllBut(0);
  else 
    popTimer = setTimeout('hideAllBut(0)', 500);
}

function popClick(menuNum, itemNum)
{
  with (menu[menuNum][itemNum])
  {
    switch (type)
    {
      // A JavaScript function? Eval() it and break out of switch.
      case 'js:': 
        eval(href); 
        break;
      // Otherwise, point to the window if nothing else and navigate.
      case '': 
        type = 'window';
      default:
        eval(type + '.location.href = "' + href + '"');
        break;
    }
  }

  // Whatever happens, hide the menus when clicked.
  hideAllBut(0);
  return false;
}


function getTree(menuNum, itemNum)
{
  // Array index is the menu number. The contents are null (if that menu is not a parent)
  // or the item number in that menu that is an ancestor (to light it up).
  itemArray = new Array(menu.length);

  while(1)
  {
    itemArray[menuNum] = itemNum;
    // If we've reached the top of the hierarchy, return.
    if (menuNum == 0) 
      return itemArray;

    itemNum = menu[menuNum][0].parentItem;
    menuNum = menu[menuNum][0].parentMenu;
  }
}

// Pass an array and a boolean to specify colour change, true = over colour.
// N.B: Uses global litNow array which contains items in hierarchy.
function changeCol(isOver)
{
  // Cycle through array searching for items to change.
  for (count = 0; count < litNow.length; count++)
  {
    // If item number is present, change its colour.
    if (litNow[count])
    {
      // Nest two WITH's, the last being more specific to allow item hover colours.
      with (menu[count][0]) 
        with (menu[count][litNow[count]])
        {
          newCol = isOver ? overCol : backCol;

          // Change the colours of the div/layer background.
          if (isNS4) 
            ref.bgColor = newCol;
          else 
            ref.backgroundColor = newCol;
        }
    }
  }
}

function hideAllBut(menuNum)
{
  // Get array of parent menus (item numbers irrelevant, just pass '1').
  var keepMenus = getTree(menuNum, 1);

  // ...and work through it, hiding menus that are not its ancestors/itself.
  for (count = 0; count < menu.length; count++)
    if (!keepMenus[count]) menu[count][0].ref.visibility = 'hidden';

  // Dim all the items in litNow array.
  changeCol(false);
}


// *** MENU CONSTRUCTION FUNCTIONS ***
function Menu(isVert, popInd, x, y, width, pad, overCol, backCol, borderClass, textClass)
{
  // True or false - a vertical menu?
  this.isVert = isVert;
  // The popout indicator used (if any) for this menu.
  this.popInd = popInd
  // Position and size settings.
  this.x = x;
  this.y = y;
  this.width = width;
  this.pad = pad;
  // Colours of menu and items.
  this.overCol = overCol;
  this.backCol = backCol;
  // The stylesheet class used for item borders and the text within items.
  this.borderClass = borderClass;
  this.textClass = textClass;
  // Parent menu and item numbers, indexed later.
  this.parentMenu = null;
  this.parentItem = null;
  // Reference to the object's style properties (set later).
  this.ref = null;
}

function Item(text, href, type, length, spacing, target)
{
  this.text = text;
  this.href = href;
  this.type = type;
  this.length = length;
  this.spacing = spacing;
  this.target = target;
  // Reference to the object's style properties (set later).
  this.ref = null;
}

function createMenus()
{
  isDOM = (document.getElementById ? true : false); 
  isIE4 = ((document.all && !isDOM) ? true : false);
  isNS4 = (document.layers ? true : false);
  isDyn = (isDOM || isIE4 || isNS4);
  popTimer = 0;
  litNow = new Array();

  if (!isDyn)
    return;

  // Loop through menus, using properties of menu description object, i.e. x, y, width etc...
  for (currMenu = 0; currMenu < menu.length; currMenu++) with (menu[currMenu][0])
  {
    // Variable for holding HTML for items and positions of next item.
    var str = '', itemX = 0, itemY = 0;

    // In NS4, since borders are assigned to the table rather than layer, increase padding.
    if (isNS4) 
      pad++;

    // Remember, items start from 1 in the array (0 is menu object itself, above).
    // Also use properties of each item nested in the other with() for construction.
    for (currItem = 1; currItem < menu[currMenu].length; currItem++) with (menu[currMenu][currItem])
    {
      var itemID = 'menu' + currMenu + 'item' + currItem;

      // The width and height of the menu item - dependent on orientation!
      var w = (isVert ? width : length);
      var h = (isVert ? length : width);

      // Create a div or layer text string with appropriate styles/properties.
      if (isDOM || isIE4)
      {
        str += '<div id="' + itemID + '" style="position: absolute; left: ' + itemX +
         '; top: ' + itemY + '; width: ' + w + '; height: ' + h + '; visibility: inherit; ';

        if (backCol) 
          str += 'background: ' + backCol;

        str += '" ';
      }
      if (isNS4)
      {
        str += '<layer id="' + itemID + '" left="' + itemX + '" top="' + itemY + '" width="' + 
         w + '" height="' + h + '" visibility="inherit" ';
        if (backCol) 
          str += 'bgcolor="' + backCol + '" ';
      }
      if (borderClass) 
        str += 'class="' + borderClass + '" ';
   
      // Add mouseover and click handlers and finish div/layer.
      str += 'onMouseOver="popOver(' + currMenu + ',' + currItem + ')" onMouseOut="popOut(' +
        currMenu + ',' + currItem + ')" onClick="popClick(' + currMenu + ',' + currItem + ')">';

      // Add contents of item...
      if (target > 0)
      {
        // Set target's parents to this menu item.
        menu[target][0].parentMenu = currMenu;
        menu[target][0].parentItem = currItem;

        // Add a popout indicator - before text so it shows up below text in NS4.
        if (popInd)
        {
          if (isNS4) 
            str += '<layer class="' + textClass + '" left="'+ (w - 15) + '" top="' + pad + '">' + popInd + '</layer>';
          else 
            str += '<div class="' + textClass + '" style="position: absolute; left: ' + (w - 15) + '; top: ' + pad + '">' + popInd + '</div>';
        }
      }
    
      // For NS4, if a border is assigned a spacer table must be added to push border out to layer edges.
      // The text layer must completely overlay this table as well for proper click capturing.
      // Add a link both to generate an onClick event and to stop the ugly I-beam text cursor appearing.
      if (isNS4) 
        str += (borderClass ? '<table width="' + (w - 8) + '" border="0" cellpadding="0" ' +
         'cellspacing="0"><tr><td height="' + (h - 8) + '"></td></tr></table>' : '') + 
         '<layer left="' + pad + '" top="' + pad + '" width="' + (w - pad) + '" height="' +
         (h - pad) + '"><a class="' + textClass + '" href="javascript:popClick(' + currMenu + ',' + currItem + ')">' + text + '</a></layer>';     
         // IE4+/NS6 is an awful lot easier to work with sometimes.
      else 
        str += '<div class="' + textClass + '" style="position: absolute; left: ' + pad + '; top: ' + pad + '">' + text + '</div>';

      // Finish off item.
      if (currMenu == 0)
        str += '<img src=images/blank.gif HEIGHT=18 WIDTH=1 align=right>';
            
      str += (isNS4 ? '</layer>' : '</div>');
      

      // Move next item position down or across by this item's length and additional spacing.
      // Add or subtract one for partial solution to NS6 border problem.
      var shrink = (isDOM && !document.all ? -1 : 1)
      if (isVert) 
        itemY += length + spacing - shrink;
      else 
        itemX += length + spacing - shrink;

      // End loop through items and with([menu[currMenu][currItem]).
    }

    // Now, write the menu to the document depending on browser capabilities.
    // N.B: Still using properties of menu[currMenu][0] like 'ref' etc...
   
    // Insert a div tag to the end of the BODY with menu HTML in place for IE4+.
    if (document.all)
    {
      // Give a small width and height to stop IE4 sizing to full body. Thanks to Jeff Blum
      // for pointing this out. Also, thanks to Paul Maden for helping debug this, apparently
      // the width must be a miniumum of 3 for it to work in IE4.
      document.body.insertAdjacentHTML('beforeEnd', '<div id="menu' + currMenu + 'div" ' +
      'style="position: absolute; width: 3; height: 3; visibility: hidden">' + str + '</div>');
      ref = getSty('menu' + currMenu + 'div');
      
    }
    // In NS6+ or other DOM but non-IE browsers, create a new DIV node and add text...
    else if (isDOM)
    {
      var newDiv = document.createElement('div');
      document.body.appendChild(newDiv);
      newDiv.innerHTML = str;
      ref = newDiv.style;
    
      ref.position = 'absolute';
      ref.visibility = 'hidden';
    }
    // In NS4, create a reference to a new layer and write the items to it.
    else if (isNS4)
    {
      ref = new Layer(0);
      ref.document.write(str);
      ref.document.close();
    }

    // Chuck some positions in here. Only really relevant for root menu.
    ref.left = x;
    ref.top = y;
    // Set the default cursor for the menu to be the hand (or 'pointer' if you're the W3C or
    // Mozilla Project and just trying to be difficult :)...
    if (!isNS4) 
      ref.cursor = (document.all ? 'hand' : 'pointer');

    // Now items have been written, loop through them again to set up references.
    for (currItem = 1; currItem < menu[currMenu].length; currItem++)
    {
      itemName = 'menu' + currMenu + 'item' + currItem;
      if (isDOM || isIE4) 

        menu[currMenu][currItem].ref = getSty(itemName);
      if (isNS4)
      {
        menu[currMenu][currItem].ref = ref.document[itemName];
        // Also capture clicks on that item layer's document...
        with (ref.document[itemName])
        {
          document.captureEvents(Event.CLICK);
          document.onclick = new Function('popClick(' + currMenu + ', ' + currItem + ')');
        }
      }
    } 
  	// End loop through menus and with (menu[currMenu][0]).
  }

	// *** CENTRING FUNCTION ***  Uncomment this to centre menus.
	//resizeHandler()

	// Show the root menu now that's all over. Phew!
  menu[0][0].ref.visibility = 'visible';
}

var popOldWidth = window.innerWidth;
function resizeHandler()
{
 if (isNS4 && popOldWidth != window.innerWidth) location.reload()

 // Uncomment these next lines to *** CENTRE/RIGHT ALIGN YOUR MENU ***
 // You must also uncomment the resizeHandler() call at the end of createMenus() above.
 // Edit this expression to anything you want -- note the menu width is hard-coded :).

 //var winWidth = (document.all ? document.body.clientWidth : window.innerWidth)
 //menu[0][0].ref.left = (winWidth / 2) - 100;
}


// Optional 'coloured item' object you can add to your menu array. Use to create individual
// mouseover colours for items in a menu rather than have them all the same.
// Delete this if you aren't using it, it's not necessary.
function colItem(text, href, type, length, spacing, overCol, backCol, target)
{
 this.text = text;
 this.href = href;
 this.type = type;
 this.length = length;
 this.spacing = spacing;
 this.overCol = overCol;
 this.backCol = backCol;
 // If you want, add borderClass and textClass in here too. Make sure to add them as
 // parameter to this function above! You can add most parameters of the 'menu' object
 // used in writing menus to the document, such as popout indicators if you want.
 // Alternatively, it could be simpler to just use a bit of JS: menu[x][y].popInd = '...';
 // before creating the menus.
 this.target = target;
 this.ref = null;
}



// This is just the moving command for the example.   *** DELETE ME ***
moveRoot = new Function('with(menu[0][0].ref) left = ((parseInt(left) < 100) ? 100 : 5);');

