var isDOM=document.getElementById?1:0,
 isIE=document.all?1:0,
 isNS4=navigator.appName=='Netscape'&&!isDOM?1:0,
 isOp=self.opera?1:0,
 isDyn=isDOM||isIE||isNS4;

function getRef(i, p)
{
 p=!p?document:p.navigator?p.document:p;
 return isIE ? p.all[i] :
  isDOM ? (p.getElementById?p:p.ownerDocument).getElementById(i) :
  isNS4 ? p.layers[i] : null;
};

function getSty(i, p)
{
 var r=getRef(i, p);
 return r?isNS4?r:r.style:null;
};

if (!self.LayerObj) var LayerObj = new Function('i', 'p',
 'this.ref=getRef(i, p); this.sty=getSty(i, p); return this');
function getLyr(i, p) { return new LayerObj(i, p) };

function LyrFn(n, f)
{
 LayerObj.prototype[n] = new Function('var a=arguments,p=a[0],px=isNS4||isOp?0:"px"; ' +
  'with (this) { '+f+' }');
};
LyrFn('x','if (!isNaN(p)) sty.left=p+px; else return parseInt(sty.left)');
LyrFn('y','if (!isNaN(p)) sty.top=p+px; else return parseInt(sty.top)');

if (!self.page) var page = { win:self, minW:0, minH:0, MS:isIE&&!isOp };

page.elmPos=function(e,p)
{
 var x=0,y=0,w=p?p:this.win;
 e=e?(e.substr?(isNS4?w.document.anchors[e]:getRef(e,w)):e):p;
 if(isNS4){if(e&&(e!=p)){x=e.x;y=e.y};if(p){x+=p.pageX;y+=p.pageY}}
 else if (e && e.focus && e.href && this.MS && navigator.platform.indexOf('Mac')>-1)
 {
  e.onfocus = new Function('with(event){self.tmpX=clientX-offsetX;' +
   'self.tmpY=clientY-offsetY}');
  e.focus();x=tmpX;y=tmpY;e.blur()
 }
 else while(e){x+=e.offsetLeft;y+=e.offsetTop;e=e.offsetParent}
 return{x:x,y:y};
};




// *** CORE MENU OBJECT AND FUNCTIONS ***


// This is the base object that users create.
// It stores menu properties, and has a 'menus' associative array to store a list of menu nodes,
// and allow timeouts to refer to nodes by name (e.g. menuObject.menus.nodeName).
function FSMenu(myName, nested, cssProp, cssVis, cssHid)
{
 this.myName = myName;
 this.nested = nested;
 // Some CSS settings.
 this.cssProp = cssProp;
 this.cssVis = cssVis;
 this.cssHid = cssHid;
 this.cssLitClass = '';
 // The 'root' menu doesn't actually exist; it's an imaginary node that acts as a parent to
 // other menu nodes and shows/hides them as necessary.
 this.menus = { root: new FSMenuNode('root', this) };
 this.menuToShow = [];
 this.mtsTimer = null;
 this.showDelay = 0;
 this.switchDelay = 34;
 this.hideDelay = 400;
};

FSMenu.prototype.show = function(mN) { with (this)
{
 // Set menuToShow to the function parameters, and a timer to call the root menu over() function if
 // no other menu node picks up the show event. Use a loop to copy values so we don't leak memory.
 menuToShow.length = arguments.length;
 for (var i = 0; i < arguments.length; i++) menuToShow[i] = arguments[i];
 clearTimeout(mtsTimer);
 mtsTimer = setTimeout(myName + '.menus.root.over()', 10);
}};

FSMenu.prototype.hide = function(mN) { with (this)
{
 // Clear the above timer and route the hide event to the appropriate menu node.
 clearTimeout(mtsTimer);
 if (menus[mN]) menus[mN].out();
}};



// Each menu is represented by a FSMenuNode object.
// This is the node constructor function, with the properties and functions needed by that node.
// It's passed its own name in the menus[] array, and a reference to the parent FSMenu object.
function FSMenuNode(id, obj)
{
 this.id = id;
 this.obj = obj;
 this.lyr = this.child = this.par = this.timer = null;
 this.args = [];
 var node = this;


 // These next over/out functions are an example of 'closures' in JavaScript.
 // Since they're instantiated here, they can use the node's variables as if they were their own.

 this.over = function(evt) { with (node) with (obj)
 {
  // Basically, over() gets called when the onmouseover event reaches the menu container, which might
  // be a DIV or UL tag in the doucment. The event starts with a tag inside that container that calls
  // FSMenu.show() and sets the menuToShow array. Browsers will then 'bubble' the event upwards, so
  // it calls this function, which picks up the menuToShow array and creates/shows the appropriate
  // menu node as a child of this menu node. Otherwise, if no menu nodes pick up the event, the
  // default 'mtsTimer' timeout will call upon the 'root' menu node to show the menu.


  // Ensure NS4 calls the show/hide function within this layer first.
  if (isNS4 && evt && lyr.ref) lyr.ref.routeEvent(evt);
  clearTimeout(timer);

  if (menuToShow)
  {
   // Pull information out of menuToShow[], and clear the default root.show() timeout.
   clearTimeout(mtsTimer);
   var a = menuToShow, m = a[0];
   if (!menus[m] || !menus[m].lyr.ref) menus[m] = new FSMenuNode(m, obj);
   var c = menus[m];
   if (c == node) return;
   // Stop any impending show/hide of the child menu.
   clearTimeout(c.timer);
   if (c != child && c.lyr.ref)
   {
    // We have a genuinely new child menu to show. Give it some properties, set a timer to show it.
    // Again, try and avoid memory leaks, but copy over the a/menuToShow arguments.
    c.args.length = a.length;
    for (var i = 0; i < a.length; i++) c.args[i] = a[i];
    c.par = node;
    // Decide which delay to use -- switchDelay if we already have a chlid menu, showDelay otherwise.
    var delay = child ? switchDelay : showDelay;
    // Calling show() will automatically set this menu's "child" reference.
    if (delay) c.timer = setTimeout(myName + '.menus["' + c.id + '"].show()', delay);
    else c.show();

    // Try, try, try to avoid leaking memory...
    menuToShow.length = 0;
   }
  }

  // For non-nested menus, mimic event bubbling.
  if (!nested && par) par.over();
 }};

 this.out = function(evt) { with (node) with (obj)
 {
  // Basically the same as over(), this cancels impending events and sets a hide timer.
  if (isNS4 && evt && lyr && lyr.ref) lyr.ref.routeEvent(evt);
  clearTimeout(timer);
  timer = setTimeout(myName + '.menus["' + id + '"].hide()', hideDelay);
  if (!nested && par) par.out();
 }};


 // Finally, now we have created our menu node, get a layer object for the right ID'd element
 // in the page, and assign it onmouseout/onmouseover events.
 if (id != 'root') with (this) with (lyr = getLyr(id)) if (ref)
 {
  if (ref.addEventListener)
  {
   ref.addEventListener('mouseover', this.over, false);
   ref.addEventListener('mouseout', this.out, false);
  }
  else
  {
   if (isNS4) ref.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT);
   ref.onmouseover = this.over;
   ref.onmouseout = this.out;
  }
 }
};



FSMenuNode.prototype.show = function() { with (this) with (obj)
{
 // This is called to show the menu node of which it's a method.
 // It sets the parent's child to this, and hides any existing children of the parent node.
 if (!par) return;
 if (par.child && par.child != this) par.child.hide();
 // This next 'par' check seems to stop some Opera 7 versions throwing errors.
 if (!par) return;
 par.child = this;

 // This is the positioning routine, it can be deleted if you're not using it.
 // It pulls values out of the stored args[] array, and uses the page.elmPos function in the
 // cross-browser code to find the pixel position of the parent item + menu.
 var offR = args[1], offX = args[2], offY = args[3], lX = 0, lY = 0,
  doX = ''+offX!='undefined', doY = ''+offY!='undefined';
 if (offR && (doX||doY))
 {
  with (page.elmPos(offR, par.lyr ? par.lyr.ref : 0)) lX = x, lY = y;
  if (doX) lyr.x(lX + eval(offX));
  if (doY) lyr.y(lY + eval(offY));
 }

 // Append the 'highlighted' CSS classname to the current CSS classname.
 if (offR && cssLitClass && !isNS4) offR.className += (offR.className?' ':'') + cssLitClass;
 // Show the menu and trigger any 'onshow' events.
 if (obj.onshow) obj.onshow(id);
 lyrVis(1);
}};

FSMenuNode.prototype.hide = function() { with (this) with (obj)
{
 // Same as show() above, but this clears the child/parent settings and hides the menu.
 if (!par) return;
 // This is an NS4 hack as its mouse events are notoriously unreliable. Remove if needed.
 if (isNS4 && self.isMouseIn && isMouseIn(lyr.ref)) return show();
 // Restore the original CSS class of the triggering element.
 if (args[1] && cssLitClass && !isNS4)
  args[1].className = args[1].className.replace(new RegExp('\\s*' + cssLitClass + '$'), '');
 // Hide the menu node element, and trigger an 'onhide' event if set.
 if (lyr)
 {
  if (obj.onhide) obj.onhide(id);
  lyrVis(0);
 }
 // Route the hide call through any child nodes, and clear the par/child references.
 if (child) child.hide();
 if (par && par.child == this) par.child = null;
 par = null;
}};

FSMenuNode.prototype.lyrVis = function(sh) { with (this) with (obj)
{
 // Called by show() and hide() above, this sets the CSS properties of the menu node element.
 lyr.sty[cssProp] = sh ? cssVis : cssHid;
}};



// ANIMATION:  Here's an optional replacement for the lyrVis() function above.
// Uncomment it by removing the /* and */ lines to try it out :).

/*

FSMenuNode.prototype.lyrVis = function(sh) { with (this) with (obj)
{
 lyr.timer |= 0;
 lyr.counter |= 0;
 with (lyr)
 {
  clearTimeout(timer);
  if (sh) sty[cssProp] = cssVis;
  sty.zIndex = 1000 + sh;

  // CLIPPING ANIMATION: Use the next two lines. N.B: Not well suited to nested lists.
  //var cP = Math.pow(Math.sin(Math.PI*counter/200),0.75);
  //if (!isNS4) sty.clip = 'rect(0px, '+ref.offsetWidth+'px, '+(ref.offsetHeight*cP)+'px, 0px)';

  // ALPHA ANIMATION: Use this, and uncommment the LyrFn('alpha'....) lines below too.
  lyr.alpha(counter==100 ? null : counter);
  counter += 10*(sh?1:-1);

  counter += 10*(sh?1:-1);
  if (counter>100) { counter = 100 }
  else if (counter<0) { counter = 0; sty[cssProp] = cssHid }
  else timer = setTimeout(myName + '.menus["' + id + '"].lyrVis(' + sh + ')', 80);
 }
}};

LyrFn('alpha','var f=ref.filters,d=(p==null),o=d?"inherit":p/100; if (f) {' +
 'if (!d&&sty.filter.indexOf("alpha")==-1) sty.filter+=" alpha(opacity="+p+")"; ' +
 'else if (f.length&&f.alpha) with(f.alpha){if(d)enabled=false;else{opacity=p;enabled=true}} }' +
 'else if (isDOM)sty.opacity=sty.MozOpacity=o');


*/


// SELECT BOX / IFRAME HIDING: Uncomment this by removing the /* and */ portions.
// It will automatically apply to all menu objects in the document.

/*

FSMenu.prototype.toggleElements = function(show)
{
 if (!isDOM) return;
 var tags = ['select', 'iframe']; // Add more here as needed.
 for (var t in tags)
 {
  var elms = document.getElementsByTagName(tags[t]);
  for (var e = 0; e < elms.length; e++) elms[e].style.visibility = show ? 'visible' : 'hidden';
 }
};
FSMenu.prototype.onshow = function()
{
 this.toggleElements(0);
};
FSMenu.prototype.onhide = function()
{
 this.toggleElements(1);
};

*/




// This next block is only required for NS4 compatibility, you may freely delete it otherwise.
// This script will "run" in NS4, but I recommend you use my other menu script if you want NS4
// users to have an experience comparable to users of modern browsers (http://www.twinhelix.com)

if (isNS4)
{
 var fsmMouseX, fsmMouseY, fsmOR=self.onresize, nsWinW=innerWidth, nsWinH=innerHeight;
 document.fsmMM=document.onmousemove;

 self.onresize = function()
 {
  if (fsmOR) fsmOR();
  if (nsWinW!=innerWidth || nsWinH!=innerHeight) location.reload();
 };

 document.captureEvents(Event.MOUSEMOVE);
 document.onmousemove = function(e)
 {
  fsmMouseX = e.pageX;
  fsmMouseY = e.pageY;
  return document.fsmMM?document.fsmMM(e):document.routeEvent(e);
 };

 function isMouseIn(sty)
 {
  with (sty) return ((fsmMouseX>left) && (fsmMouseX<left+clip.width) &&
   (fsmMouseY>top) && (fsmMouseY<top+clip.height));
 };
}

