/*

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 = 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);
  // 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)
{
 // 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));
 }

 // 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.

 // This is an NS4 hack as its mouse events are notoriously unreliable. Remove as 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);
};

*/







// *** 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, mRoot = getRef(id), nodes, count = 1;
 var items = mRoot.getElementsByTagName('li');
 // Loop through all list items under the given menu.
 for (var i = 0; i < items.length; i++)
 {
  nodes = items[i].childNodes;
  if (!nodes) continue;
  a = ul = null;
  // Scan each item to see if it contains both A and UL tags; must be a submenu if so!
  if (nodes) for (var n = 0; n < nodes.length; n++)
  {
   if (!nodes[n].nodeName || (nodes[n].nodeType != 1)) continue;
   // Because Konqueror's nodeNames are in lower case.
   if (nodes[n].nodeName.toUpperCase() == 'A') a = nodes[n];
   if (nodes[n].nodeName.toUpperCase() == 'UL') ul = nodes[n];
   if (a && ul)
   {
    // Assign this list an ID attribute, and event handlers to the anchor to show it.
    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';
   }
  }
 }
}};




// *** 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)

// '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));
 };
}
