//
// MenuLearn DHTML Menu - Version 1.0.0
//
// Written by Kyle Herbert
// Copyright 2004 (c) First Net Impressions, LLC. All Rights Reserved.
// Visit http://www.firstnetimpressions.com/
//
// This is *NOT* free software.
//  

function menuGlobalsObject()
{
  this.timer = 0;
  this.delay = 150;
  this.zindex = 1;
  this.stack = new Array();
  this.px = (document.childNodes) ? 'px' : 0;
}

function menuTraitsObject()
{
  this.orient = 'horizontal';
  this.place = 'bottom';
  this.width = 150;
  this.height = 0;
  this.xoffset = 0;
  this.yoffset = 0;
}

var menuGlobals = new menuGlobalsObject();

function menuLearn()
{
  // Traverse in turn each named-menu provided

  for(cnt = 0; cnt < arguments.length; cnt++)
  {
    // Verify browser's ability to provide a div object reference
    // Verify that the div retrieved is a named-menu

    var parent = menuGetDiv(arguments[cnt]);

    if(!parent)
    {
      continue;
    }

    if(parent.className.substring(0, 9) != 'menu-name')
    {
      continue;
    }

    // Import traits associated with the div
    // Determine the zindex for this level of menus

    var traits = menuImportTraits(parent);
    var zindex = cnt + menuGlobals.zindex;

    // Hide the div while it is being modified
    // Set the div positioning to 'absolute'
    // Ensure no borders, margins, or padding surround the div
    // Assign z-index to the div
    // Make sure the div overflow is visible

    menuSetDivVisibility(parent, false);
    menuSetDivPosition(parent, 'absolute');
    menuSetDivChromeless(parent);
    menuSetDivZIndex(parent, zindex);
    menuSetDivOverflow(parent, 'visible');

    // Find the static-menu defined inside the named-menu
    // Get the dimensions of the static-menu after construction

    var offset = 0;
    var max_width = 0;
    var max_height = 0;

    for(var node = 0; node < parent.childNodes.length; node++)
    {
      // Grab the child node

      var child = parent.childNodes[node];

      // Skip children with undefined class names - they aren't ours
      // Skip everything not defined as a static-menu

      if(typeof(child.className) == 'undefined')
      {
        continue;
      }

      if(child.className.substring(0, 11) != 'menu-static')
      {
        continue;
      }

      // Give the child a unique ID

      child.id = parent.id + '-' + offset++;

      // Grab the child traits

      var child_traits = menuImportTraits(child);

      // Traverse the static-menu

      menuTraverse(child, child_traits, zindex + 1, parent);

      // Obtain the constructed static-menu's dimensions

      max_width = menuGetDivWidth(child);
      max_height = menuGetDivHeight(child);

      // IE may leave the child offset from the parent's origin - correct it
      // Menu traversal will leave this menu hidden - show it

      menuSetDivLocation(child, child_traits.xoffset, child_traits.yoffset);
      menuSetDivVisibility(child, true);

      // Only one static-menu is allowed per named-menu space

      break;
    }

    // Size the div to fit its child static-menu
    // Return the div to its 'relative'ly positioned state
    // Show the fully modified menu

    menuSetDivSize(parent, max_width, max_height);
    menuSetDivPosition(parent, 'relative');
    menuSetDivVisibility(parent, true);
  }
}

function menuTraverse(parent, traits, zindex, ancestor)
{
  // Hide the div while it is being modified
  // Set the div positioning to 'absolute'
  // Ensure no borders, margins, or padding surround the div
  // Assign z-index to the div
  // Make sure div overflow is visible

  menuSetDivVisibility(parent, false);
  menuSetDivPosition(parent, 'absolute');
  menuSetDivChromeless(parent);
  menuSetDivZIndex(parent, zindex);
  menuSetDivOverflow(parent, 'visible');

  // Give a unique ID to each 'menu' child of this menu
  // Position each 'menu' child of this menu 'absolute'ly
  // Initialize menu-items
  // Obtain dimensions for container big enough to hold menu-items

  var offset = 0;
  var max_width = 0;
  var max_height = 0;

  for(var node = 0; node < parent.childNodes.length; node++)
  {
    // Grab the child node

    var child = parent.childNodes[node];

    // Skip children with undefined class names - they aren't ours
    // Skip everything not prefixed with 'menu' - they aren't ours

    if(typeof(child.className) == 'undefined')
    {
      continue;
    }

    if(child.className.substring(0, 4) != 'menu')
    {
      continue;
    }

    // Give the child a unique ID
    // Positioned all 'menu' children 'absolute'ly so
    // they don't influence following items
    // Orient each child as was defined by its parent
    // (on horizontal menus this keeps the width of child
    // popups from influencing the spacing of the header
    // items)

    child.id = parent.id + '-' + offset++;
    menuSetDivPosition(child, 'absolute');
    menuSetDivOrientation(child, traits.orient);

    // The remainder only applies to menu-items

    if(child.className.substring(0, 9) != 'menu-item')
    {
      continue;
    }

    // Mozilla does not measure the widths of absolutely
    // positioned div's nested inside absolutely positioned
    // div's very well.  Seems like this is because the parent
    // div does not have dimensions assigned to it and the
    // child is inheriting the emptiness.  Position the child
    // 'relative'ly, measure it, then position it 'absolute'ly
    // again before locating it.

    menuSetDivPosition(child, 'relative');
    var child_width = menuGetDivWidth(child);
    var child_height = menuGetDivHeight(child);
    menuSetDivPosition(child, 'absolute');

    // Move the menu item into position
    // Update parent container dimensions

    if(traits.orient == 'vertical')
    {
      menuSetDivLocation(child, 0, max_height);

      if(child_width > max_width)
      {
        max_width = child_width;
      }

      max_height += child_height;
    }
    else
    {
      menuSetDivLocation(child, max_width, 0);

      if(child_height > max_height)
      {
        max_height = child_height;
      }

      max_width += child_width;
    }
  }

  // Resize the parent menu so as to fit all its child items
  // Once the parent has been sized, it should no longer be
  // necessary to perform the relative->measure->absolute hack
  // while measuring child items.

  menuSetDivSize(parent, max_width, max_height);

  // Run through the child nodes once more to initialize
  // menu-item's and build menu-popup's.

  var sibling = null;

  for(var node = 0; node < parent.childNodes.length; node++)
  {
    // Grab the child node

    var child = parent.childNodes[node];

    // Skip children with undefined class names - they aren't ours
    // Skip everything not prefixed with 'menu' - they aren't ours

    if(typeof(child.className) == 'undefined')
    {
      continue;
    }

    if(child.className.substring(0, 4) != 'menu')
    {
      continue;
    }

    // Process menu-item's

    if(child.className.substring(0, 9) == 'menu-item')
    {
      if(traits.orient == 'vertical')
      {
        var child_height = menuGetDivHeight(child);
        menuSetDivSize(child, max_width, child_height);
      }
      else
      {
        var child_width = menuGetDivWidth(child);
        menuSetDivSize(child, child_width, max_height);
      }

      menuSetDivCursor(child, 'hand');

      menuSetTrigger(child.id, '');

      sibling = child;

      continue;
    }

    // Process menu-popup's

    if(child.className.substring(0, 10) == 'menu-popup')
    {
      var child_traits = menuImportTraits(child);

      menuTraverse(child, child_traits, zindex + 1, parent);

      menuPlacePopup(sibling, child, child_traits);

      menuSetTrigger(sibling.id, child.id);

      delete child_traits;
    }
  }
}

function menuGetDiv(id)
{
  // IE5, NS6, Mozilla, Opera

  if(document.getElementById)
  {
    return document.getElementById(id);
  }

  // IE4

  if(document.all)
  {
    return document.all[id];
  }

  // NS

  if(document.layers)
  {
    return document.layers[id];
  }

  // Unsupported

  return null;
}

function menuGetDivWidth(div)
{
  return (div.clip) ? div.clip.width : div.offsetWidth;
}

function menuGetDivHeight(div)
{
  return (div.clip) ? div.clip.height : div.offsetHeight;
}

function menuSetDivSize(div, width, height)
{
  var style = (div.style) ? div.style : div;
  var xchrome = 0;
  var ychrome = 0;

  // Because the menu item div's are hidden during construction
  // we can size and resize them without causing annoying flicker.
  // We adjust the size by the calculated amount of "chrome"
  // surrounding the div.

  style.width = width + menuGlobals.px;
  style.height = height + menuGlobals.px;
  style.pixelWidth = width;
  style.pixelHeight = height;

  // Calculate amount of "chrome" around the div

  xchrome = menuGetDivWidth(div) - width;
  ychrome = menuGetDivHeight(div) - height;

  // Set div dimensions to the desired size adjusted by the amount of chrome

  style.width = (width - xchrome) + menuGlobals.px;
  style.height = (height - ychrome) + menuGlobals.px;
  style.pixelWidth = (width - xchrome);
  style.pixelHeight = (height - ychrome);
}

function menuSetDivVisibility(div, toggle)
{
  // IE4, IE5, NS6, Mozilla, Opera

  if(div.style)
  {
    div.style.visibility = (toggle) ? 'visible' : 'hidden';
    return true;
  }

  // Netscape

  if(div.visibility)
  {
    div.visibility = (toggle) ? 'show' : 'hide';
    return true;
  }

  // Unsupported

  return false;
}

function menuSetDivPosition(div, position)
{
  div.style.position = position;
}

function menuSetDivLocation(div, left, top)
{
  var style = (div.style) ? div.style : div;

  style.left = left + menuGlobals.px;
  style.top = top + menuGlobals.px;
}

function menuSetDivZIndex(div, zindex)
{
  var style = (div.style) ? div.style : div;

  style.zIndex = zindex;
}

function menuSetDivOverflow(div, overflow)
{
  var style = (div.style) ? div.style : div;

  style.overflow = overflow;
}

function menuSetDivOrientation(div, orientation)
{
  var style = (div.style) ? div.style : div;
  var cssfloat = (orientation == 'vertical') ? 'none' : 'left';

  // IE uses styleFloat while others use cssFloat.

  style.cssFloat = cssfloat;
  style.styleFloat = cssfloat;
}

function menuSetDivCursor(div, cursor)
{
  var style = (div.style) ? div.style : div;

  // Remember that pre-IE6 versions of IE use 'pointer' instead of 'hand'
  // It's safe to set both styles regardless of actual browser

  style.cursor = cursor;

  if(cursor == 'hand')
  {
    style.cursor = 'pointer';
  }
}

function menuSetDivChromeless(div)
{
  var style = (div.style) ? div.style : div;

  style.border = '0';
  style.margin = '0';
  style.padding = '0';
}

function menuImportTraits(element)
{
  var traits_object = new menuTraitsObject();
  var traits_string = element.getAttribute('traits');

  if(traits_string > '')
  {
    var traits_array = traits_string.split(';');

    if(traits_array.length > 0)
    {
      for(var cnt = 0; cnt < traits_array.length; cnt++)
      {
        var keyval = traits_array[cnt].split(':');

        if(keyval.length == 2)
        {
          keyval[0] = menuTrimString(keyval[0]);
          keyval[1] = menuTrimString(keyval[1]);

          if(keyval[0] == 'orient')
          {
            traits_object.orient = keyval[1];
          }
          else if(keyval[0] == 'place')
          {
            traits_object.place = keyval[1];
          }
          else if(keyval[0] == 'width')
          {
            traits_object.width = parseInt(keyval[1]);
          }
          else if(keyval[0] == 'height')
          {
            traits_object.height = parseInt(keyval[1]);
          }
          else if(keyval[0] == 'xoffset')
          {
            traits_object.xoffset = parseInt(keyval[1]);
          }
          else if(keyval[0] == 'yoffset')
          {
            traits_object.yoffset = parseInt(keyval[1]);
          }
        }
      }
    }
  }

  return traits_object;
}

function menuTrimString(str) 
{
  // Remove leading spaces

  while(str.substring(0, 1) == ' ')
  {
    str = str.substring(1, str.length);
  }

  // Remove trailing spaces

  while(str.substring(str.length - 1, str.length) == ' ')
  {
    str = str.substring(0, str.length - 1);
  }

  // Return result

  return str;
}

function menuPlacePopup(sibling, child, popup_traits)
{
  var sibling_style = (sibling.style) ? sibling.style : sibling;
  var child_style = (child.style) ? child.style : child;
  var xorigin = parseInt(sibling_style.left) + popup_traits.xoffset;
  var yorigin = parseInt(sibling_style.top) + popup_traits.yoffset;

  if(popup_traits.place == 'top')
  {
    child_style.left = xorigin + menuGlobals.px;
    child_style.top = yorigin - menuGetDivHeight(child) + menuGlobals.px;
  }
  else if(popup_traits.place == 'left')
  {
    child_style.left = xorigin - menuGetDivWidth(child) + menuGlobals.px;
    child_style.top = yorigin + menuGlobals.px;
  }
  else if(popup_traits.place == 'right')
  {
    child_style.left = xorigin + menuGetDivWidth(sibling) + menuGlobals.px;
    child_style.top = yorigin + menuGlobals.px;
  }
  else if(popup_traits.place == 'bottom')
  {
    child_style.left = xorigin + menuGlobals.px;
    child_style.top = yorigin + menuGetDivHeight(sibling) + menuGlobals.px;
  }
}

function menuSetTrigger(id, popup)
{
  // 'id' should never be empty, but a poorly created menu having a
  // popup defined before the triggering menu item will result in this
  // situation.  Cover it.

  if(id == '')
  {
    return;
  }

  // Associate mouse-over and mouse-out processing with the menu item.
  // Through the mouse-over event, we can keep the top element of the
  // menu stack always equal to the most recently touched menu item.
  // Through the mouse-out function, we can detect when the mouse has
  // left the menu entirely, and thus erase any popup menus currently
  // displayed.  We delay the processing of the mouse-out event in
  // order to give the user time to move the mouse over a sibling item
  // in the current menu or to an item in a popup menu triggered by
  // this item.

  if(document.getElementById)
  {
    eval("document.getElementById('" + id + "').onmouseover = function () {menuMouseOver('" + id + "', '" + popup + "');};");
    eval("document.getElementById('" + id + "').onmouseout = function () {menuGlobals.timer = setTimeout(\"menuMouseOut('" + id + "', '" + popup + "')\", " + menuGlobals.delay + ");};");
  }
  else if(document.layers && document.layers[id])
  {
    eval("document.layers['" + id + "'].onmouseover = function () {menuMouseOver('" + id + "', '" + popup + "');};");
    eval("document.layers['" + id + "'].onmouseout = function () {menuGlobals.timer = setTimeout(\"menuMouseOut('" + id + "', '" + popup + "')\", " + menuGlobals.delay + ");};");
  }
  else if(document.all)
  {
    eval("document.all['" + id + "'].onmouseover = function () {menuMouseOver('" + id + "', '" + popup + "');};");
    eval("document.all['" + id + "'].onmouseout = function () {menuGlobals.timer = setTimeout(\"menuMouseOut('" + id + "', '" + popup + "')\", " + menuGlobals.delay + ");};");
  }
}

function menuStackShrink(id)
{
  // We only shrink the stack when the mouse has moved onto a different
  // menu item, or when the mouse has moved out of the menu entirely.
  // In either case, the previous menu item must be popped off the stack
  // and returned to its non-hover state.

  if(menuGlobals.stack.length > 0)
  {
    var previous = menuGlobals.stack.pop();
    var menu = document.getElementById(previous);
    var re = new RegExp('menu-hover-item', 'gi');
    menu.className = menu.className.replace(re, 'menu-item');
  }

  // Now the menu stack only contains the id's of popup menus followed
  // to reach the previous menu item.  Pop these off one at a time until
  // the current menu item is reachable.

  while(menuGlobals.stack.length > 0)
  {
    var prior = menuGlobals.stack.pop();

    if(prior.length <= id.length)
    {
      if(prior == id)
      {
        break;
      }

      if(prior == id.substring(0, prior.length))
      {
        menuGlobals.stack.push(prior);
        break;
      }
    }

    menuSetDivVisibility(menuGetDiv(prior), false);
  }
}

function menuStackGrow(id, popup)
{
  // If this item id triggers a popup, make it visible and push it onto the stack

  if(popup > '')
  {
    menuSetDivVisibility(menuGetDiv(popup), true);
    menuGlobals.stack.push(popup);
  }

  // Push this item id onto the menu stack and change its hover state.

  menuGlobals.stack.push(id);
  var menu = document.getElementById(id);
  var re = new RegExp('menu-item', 'gi');
  menu.className = menu.className.replace(re, 'menu-hover-item');
}

function menuMouseOver(id, popup)
{
  // Cancel the mouse-out timer; we're staying in the menu for now.
  // Shrink the menu stack to the point where the current menu item is attainable.
  // Grow the menu stack to include the current menu item and popup (if one exists).

  clearTimeout(menuGlobals.timer);
  menuStackShrink(id);
  menuStackGrow(id, popup);
}

function menuMouseOut(id, popup)
{
  // This function will *only* be called when the mouse leaves the
  // menu entirely.  As long as the mouse stays in the menu, the
  // mouse-over function will keep the mouse-out timer cleared, thus
  // preventing this function from being executed.

  menuStackShrink('');
}
