/*

FREESTYLE MENUS v1.0 RC (c) 2001-2004 Angus Turnbull, http://www.twinhelix.com
Altering this notice or redistributing this file is prohibited.

*/



// This is the full, commented script file, to use for reference purposes or if you feel
// like tweaking anything. I used the "CodeTrimmer" utility availble from my site
// (under 'Miscellaneous' scripts) to trim the comments out of this JS file.



// *** COMMON CROSS-BROWSER COMPATIBILITY CODE ***


// This is taken from the "Modular Layer API" available on my site.
// See that for the readme if you are extending this part of the script.

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)');

function addEvent(o, n, f)
{
 var a='addEventListener', h='on'+n;
 if (o[a]) return o[a](n, f, false);
 if (o[h])
 {
  o._c |= 0;
  var b = '_b' + (++o._c);
  o[b] = o[h];
 }
 o[h] = function(e)
 {
  e=e||self.event;
  var r = true;
  if (o[b]) r = o[b](e) != false && r;
  o._f=f;
  r = o._f(e) != false && r;
  return r;
 }
};




// *** 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 = 125;
 this.hideDelay = 500;
};

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 = this.visible = 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 document. 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);
  // Stop this menu hiding itself.
  clearTimeout(timer);
  clearTimeout(mtsTimer);

  if (menuToShow.length)
  {
   // Pull information out of menuToShow[], and clear the default root.show() timeout.
   var a = menuToShow, m = a[0];
   if (!menus[m] || !menus[m].lyr.ref) menus[m] = new FSMenuNode(m, obj);
   var c = menus[m];
   // Just clear the menuToShow array and return if we're "showing" the current menu...!
   if (c == node)
   {
    menuToShow.length = 0;
    return
   }
   // Otherwise, stop any impending show/hide of the child menu, and check if it's a new child.
   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];
    // Decide which delay to use -- switchDelay if we already have a child menu, showDelay otherwise.
    var delay = child ? switchDelay : showDelay;
    c.timer = setTimeout('with(' + myName + ') { menus["' + c.id + '"].par = menus["' +
     node.id + '"]; menus["' + c.id + '"].show() }', delay ? delay : 1);
   }
   // 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 (isNS4) ref.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT);
  addEvent(ref, 'mouseover', this.over);
  addEvent(ref, 'mouseout', this.out);
 }
};



FSMenuNode.prototype.show = function() { with (this) with (obj)
{
 if (!lyr || !lyr.ref) return;

 // 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.child && par.child != this) par.child.hide();
 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 (self.page && 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));
 }

 // Highlight the triggering element, if any.
 if (offR) lightParent(offR, 1);

 // Show the menu and trigger any 'onshow' events.
 visible = 1;
 if (obj.onshow) obj.onshow(id);
 setVis(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 (!lyr || !lyr.ref) return;

 // This is an NS4 hack as its mouse events are notoriously unreliable. Remove as needed.
 if (isNS4 && self.isMouseIn && isMouseIn(lyr.ref)) return show();
 // Dim the triggering element.
 if (args[1]) lightParent(args[1], 0);

 // Hide the menu node element, and trigger an 'onhide' event if set.
 if (lyr)
 {
  visible = 0;
  if (obj.onhide) obj.onhide(id);
  setVis(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;
}};



// Here are the functions that show/hide each menu node, and highlight the parent of a node.
// Both are called from the show() and hide() functions of FSMenuNode above.
// Feel free to override with flashier effects/animations (see the "extras" file for one).

// This is passed a reference to the parent triggering element, and whether it is lit or not.
FSMenuNode.prototype.lightParent = function(elm, lit) { with (this) with (obj)
{
 if (!cssLitClass || isNS4) return;
 // By default the cssLitClass value is appended/removed to any existing class.
 if (lit) elm.className += (elm.className?' ':'') + cssLitClass;
 else elm.className = elm.className.replace(new RegExp('\\s*' + cssLitClass + '$'), '');
}};

// This is passed one parameter, whether it's shown or not (boolean).
FSMenuNode.prototype.setVis = function(sh) { with (this) with (obj)
{
 // Sets the show/hide CSS property of the menu node, to controls its display.
 lyr.sty[cssProp] = sh ? cssVis : cssHid;
}};







// *** LIST MENU ACTIVATION ***

// This is only required for activating list-type menus.
// You can delete it if you're using div-menus only.

FSMenu.prototype.activateMenu = function(id, subIText) { with (this)
{
 if (!isDOM) return;
 var a, ul, li, mRoot = getRef(id), nodes, count = 1;
 var lists = mRoot.getElementsByTagName('ul');
 // Loop through all sub-lists under the given menu.
 for (var i = 0; i < lists.length; i++)
 {
  // Find a parent LI node, if any, by recursing upwards from the UL.
  li = ul = lists[i];
  while (li)
  {
   if (li.nodeName.toLowerCase() == 'li') break;
   li = li.parentNode;
  }
  if (!li) continue;

  // Now, find the anchor tag that triggers this menu; it should be a child of the LI.
  a = null;
  for (var j = 0; j < li.childNodes.length; j++)
  {
   if (li.childNodes[j].nodeName.toLowerCase() == 'a') a = li.childNodes[j];  }
  if (!a) continue;

  // We've found a menu node by now, so give it an ID and event handlers.
  var menuID = myName + '-id-' + count++;
  // Only assign a new ID if it doesn't have one already.
  if (ul.id) menuID = ul.id;
  else ul.setAttribute('id', menuID);
  // Attach events to the triggering anchor tag.
  addEvent(a, 'mouseover', new Function('e', myName + '.show("' + menuID + '", this)'));
  addEvent(a, 'mouseout', new Function('e', myName + '.hide("' + menuID + '")'));
  // Append a 'subind' class arrow indicator to the anchor tag content.
  if (subIText)
  {
   var subI = document.createElement ? document.createElement('span') : 0;
   if (subI)
   {
    subI.appendChild(document.createTextNode(subIText));
    // To use rich HTML in your, replace above line with this: subI.innerHTML = subIText;
    subI.className = 'subind';
    a.insertBefore(subI, a.firstChild);
   }
  }
  // You can do more processing here... for instance, dynamically hide the UL tags
  // and leave them visible by default in the CSS, for better accessibility?
  //ul.style.display = 'none';
 }
}};

// SELECT BOX / IFRAME HIDING: This will help mixing menus and forms/frames/Flash/etc.
// To activate, paste at the end of the fsmenu.js file.

FSMenu.prototype.toggleElements = function(show)
{
 // CONFIGURATION: Here's a list of tags that will be hidden by menus. Modify to fit your site.
 var tags = ['select', 'iframe'];

 if (!isDOM) return;
 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()
{
 for (var m in this.menus) if (this.menus[m].visible) return;
 this.toggleElements(1);
};



// *** DIV MENU & v4 BROWSER COMPATIBILITY ***

// You may freely delete this section if you're only using "list" type menus.
// This script will "run" in NS4, but I recommend you use my "Cascading Popup Menus" script if you
// want NS4 users to have an experience comparable to users of modern browsers.
// You can download it from my site, http://www.twinhelix.com if you're interested.

// 'page' object from layer API code, detects positions of page elements.
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};
};

// Various NS4 hacks and tweaks. You can delete this if you don't care about NS4 support.
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));
 };
}
