/*

  CROSS-BROWSER MODULAR DHTML API (c) 2001-2005 Angus Turnbull, http://www.twinhelix.com
  This notice may not be altered or removed. See my site for licensing and more scripts!

*/


// OK, here goes... this code is horribly compacted by the way, and intented for fast
// execution rather than easy understanding. You might want to run my codetrimmer script
// over it before deployment to get rid of comments for better performance.

// *** BROWSER DETECTION VARIABLES ***
// Requires: None.

// isDOM: W3C-DOM compatible browser? (IE5+, NS6+, others like Opera, Konqueror, etc...)
// isIE: Internet Explorer (v4 and up), also Opera emulating it.
// isIE4, isNS4: Each of the 4-series browsers (not DOM compatible).
// isOp: Any Opera version (useful as Opera emulates IE above).
// isWin: True is Windows, false if Mac/Linux etc.
// isDyn: Any DHTML-capable browser.

var isDOM=document.getElementById?1:0,
 isIE=document.all?1:0,
 isNS4=navigator.appName=='Netscape'&&!isDOM?1:0,
 isIE4=isIE&&!isDOM?1:0,
 isOp=self.opera?1:0,
 isDyn=isDOM||isIE||isNS4,
 isWin=navigator.platform.indexOf('Win')!=-1?1:0;



// *** DOCUMENT OBJECT MODEL (DOM) REFERENCE FUNCTIONS ***
// Requires: Brower Detection Variables "isDOM, isIE, isNS4, isOp"

// Get a reference to an element by its ID, and optionally a parent element/frame.
// Pass a parameter 'par' as either null to find a element in the current document,
// a reference to a parent element using another getRef call or similar,
// or reference to another window (e.g. parent.content) for frames usage.
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;
};

// Returns a reference to the .style property of an element, same parameters.
function getSty(i, p)
{
 var r=getRef(i, p);
 return r?isNS4?r:r.style:null;
};



// *** LAYER OBJECT CONSTRUCTOR ***
// Requires: DOM Reference Functions.

// For complex scripts, wraps up the above in one object and defines a few useful methods.
// This is built to be light and quick to download -- my major priority.
// You must pass an ID, and optionally a reference to a parent layer for NS4, like getRef().
// Usage: var xyz = getLyr('layerID'); or for frames: getLyr('layerID', parent.content);
// You can then access xyz.ref or .sty, which are reference from the functions above, or
// other functions of the layer object defined below, e.g. xyz.vis('visible').

// Only create a class if one is not defined already (for easy script combination).
if (!self.LayerObj) var LayerObj = new Function('i', 'p',
 'this.ref=getRef(i, p); this.sty=getSty(i, p); return this');

// This is the function you call to create a layer object.
function getLyr(i, p) { return new LayerObj(i, p) };

// Allows addition of functions to object (see below...)
function LyrFn(n, f)
{
 LayerObj.prototype[n] = new Function('var a=arguments,p=a[0],px=isNS4||isOp?0:"px"; ' +
  'with (this) { '+f+' }');
};



// *** FUNCTIONS OF LAYER OBJECT ***
// Requires: Layer Object Constructor.

// These functions add abilities to the layer object defined above.
// You can include only the ones you need, e.g. positioning and colours but no others.
// For many, calling with a parameter will set the value, while calling without will
// return the current value for the element, e.g. layer.x() returns the left position.
// NOTE: These are not meant to be perfect, just using them does not guarantee your
// code will work in any given browser. Be sure to test thoroughly!

// Sets or gets the position of the layer, pass a number to set or nothing to get.
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)');

// Sets the dimensions, or gets the layer's document dimensions. Note that NS4/Op5&6 can't
// resize properly, so they just reclip. Two versions are provided -- one without Opera 5
// support but works in Opera 6+, and a commented one with Opera 5 support if needed.

LyrFn('w','if (p) (isNS4?sty.clip:sty).width=p+px; ' +
 'else return (isNS4?ref.document.width:ref.offsetWidth)');
LyrFn('h','if (p) (isNS4?sty.clip:sty).height=p+px; ' +
 'else return (isNS4?ref.document.height:ref.offsetHeight)');
//LyrFn('w','if (p) (isNS4?sty.clip:sty)[isOp?"pixelWidth":"width"]=p+px; ' +
// 'else return isNS4?ref.document.width:(isOp?sty.pixelWidth:ref.offsetWidth)');
//LyrFn('h','if (p) (isNS4?sty.clip:sty)[isOp?"pixelHeight":"height"]=p+px; ' +
// 'else return isNS4?ref.document.height:(isOp?sty.pixelHeight:ref.offsetHeight)');

// Sets or gets the visibility. Accepted values: 'visible', 'inherit', 'hidden'.
// There are two versions -- a setter only, and a commented out setter/getter..
LyrFn('vis','sty.visibility=p');
//LyrFn('vis','if (p) sty.visibility=p; ' +
// 'else { var v=sty.visibility; return (v=="show"?"visible": (v=="hide"?"hidden":v)) }');

// Set the element background -- pass an empty string '' to set it to transparent.
// Note that setting and removing both colours and images on one div has issues in Opera,
// and it also seems to have an intense dislike of transparency. Background images are
// not supported by Opera 5, but can be set in Opera 6.
LyrFn('bgColor','if (isNS4) sty.bgColor=p?p:null; ' +
 'else sty.background=p?p:"transparent"');
LyrFn('bgImage','if (isNS4) sty.background.src=p?p:null; ' +
 'else sty.background=p?"url("+p+")":"transparent"');
// Experimental, both rolled up into one.
//LyrFn('bg','var img=p.indexOf(".")>-1; if (isNS4) if (img) sty.background.src=p?p:null; ' +
// 'else sty.bgColor=p?p:null; else sty.background=p?(img?"url("+p+")":p):"transparent"');

// Sets the clipping area of the div -- pass x1,y1,x2,y2.
// Works in IE5+/Mac, IE4+/Win, NS4/6+ all platforms, Opera 5/6 is unsupported.
LyrFn('clip','if (isNS4) with(sty.clip){left=a[0];top=a[1];right=a[2];bottom=a[3]} ' +
 'else sty.clip="rect("+a[1]+"px "+a[2]+"px "+a[3]+"px "+a[0]+"px)" ');

// Replace the contents of the layer/div. Pass it a string. Doesn't work in Opera yet.
LyrFn('write','if (isNS4) with (ref.document){write(p);close()} else ref.innerHTML=p');

// Sets the opacity of the div. Cool, but don't overuse, a little buggy in Mozilla (NS6), and
// it doesn't work in NS4 or IE/Mac. Pass it a percentage (0 to 100) to set, or null to remove.
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');



// *** DYNAMIC LAYER CREATION ROUTINE ***
// Requires: Layer Object Constructor, 'vis','x' and 'y' Layer Functions.

// Create a new layer dynamically and returns a layer object like getLyr(). Syntax:
// var xyz = setLyr('visibility', width, parent);
// You must pass an initial visibility, one of: 'visible', 'inherit' or 'hidden'.
// Optionally also pass layer width in pixels, and a reference to a parent layer/frame
// within which this layer will be created. setLyr isn't supported by Opera 5 or 6.
function setLyr(v, dw, p)
{
 if (!setLyr.seq) setLyr.seq=0;
 if (!dw) dw=0;
 var o = !p ? isNS4?self:document.body : !isNS4&&p.navigator?p.document.body:p,
  IA='insertAdjacentHTML', AC='appendChild', id='_sl_'+setLyr.seq++;

 if (o[IA]) o[IA]('beforeEnd', '<div id="'+id+'" style="position:absolute"></div>');
 else if (o[AC])
 {
  var n=document.createElement('div');
  o[AC](n); n.id=id; n.style.position='absolute';
 }
 else if (isNS4)
 {
  var n=new Layer(dw, o);
  id=n.id;
 }

 var l=getLyr(id, p);
 with (l) if (ref) { vis(v); x(0); y(0); sty.width=dw+(isNS4?0:'px') }
 return l;
};



// *** PAGE OBJECT***
// Requires: Browser detection variables "isDOM, isIE, isNS4, isOp"

// Use this to determine current window dimensions. It automatically creates an
// object called 'page', usage:  var xyz = page.scrollY();
// You can set page.minW and page.minH as minimum values for the dimension functions.
// page.win can point to the window whose properties are returned, useful for framesets.

if (!self.page) var page = { win: self, minW: 0, minH: 0, MS: isIE&&!isOp,
 db: document.compatMode&&document.compatMode.indexOf('CSS')>-1?'documentElement':'body' };

// Get the current area of the visible window, check against minima.
page.winW=function()
 { with (this) return Math.max(minW, MS?win.document[db].clientWidth:win.innerWidth) };
page.winH=function()
 { with (this) return Math.max(minH, MS?win.document[db].clientHeight:win.innerHeight) };

// Get the scroll position of the window. No real minima needed here.
page.scrollX=function()
 { with (this) return MS?win.document[db].scrollLeft:win.pageXOffset };
page.scrollY=function()
 { with (this) return MS?win.document[db].scrollTop:win.pageYOffset };

// Get the current document size, check versus minimum dimensions.
page.docW=function()
 { with (this) return Math.max(minW, isNS4?win.document.width:win.document.offsetWidth) };
page.docH=function()
 { with (this) return Math.max(minH, isNS4?win.document.height:win.document.offsetHeight) };


// OK, this next one is just plain crazy. It detects the positions of anchors (NS4) or any
// element (in other browsers) from the page corner. Here's the available ways to call it:
//  elmPos('name_or_id_of_element'); which returns the position of the element in the page
//  elmPos('name', refToParent); if the anchor is nested inside another layer in NS4.
// You can also call those above methods with a reference to the element instead of its name.
// Finally, you can pass just the div parameter like so (from getRef() command is fine):
//  elmPos(null, referenceToDiv);
// to detect the page position of that div regardless of nesting. Told you it was crazy :).
// It return an object as you can see, so use page.elmPos('blah').x to get the relevant value.

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}}
 if (e && this.MS && navigator.platform.indexOf('Mac')>-1 && e.tagName=='A')
 {
  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};
};




// *** MULTIPLE EVENT MANAGER ***
// Requires: Browser Detection Variable "isNS4"

// This is another admittedly crazy function that allows you to run several functions on
// events like page load/click/etc. and can back up other scripts' events too. Parameters:
//  1) A reference to an object like window or document.
//  2) The name of the event in quotes without the 'on' prefix (e.g. 'mouseover').
//  3) A function reference that is called when the event fires.
//  4) true/false that enables capturing and routing this event in NS4 if true.
// Functions called are automatically passed the correct browser event object.
// It also manages return values, if any of the functions called explicitly returns false,
// then the whole event returns false and isn't routed in NS4.
// Finally, it compacts down to under 500 bytes!
// See the example in the demo script below if this doesn't make sense.


// I've used one-char var names here to save space: this is what they mean:
//  o = object to which we're attaching an event.
//  i = identifier of event, without the 'on' prefix, e.g. 'click' or 'mouseover'.
//  f = function reference, to run on the event.
//  c = whether to capture events in NS4 (true/false).
//  o.evts = array of arrays of functions to run for events of this object.
//  n = name of event, with 'on' prefix e.g. 'onclick' or 'onmouseover'.
//  b = backup of old function that runs on event call.
//  m = temporary match from a regex, to see if this event already has a controlling function.
//  u = unique index for this event in the 'object.evts[]' array (from 'm').
// There's a few more, but you can figure them out :).
function addEvent(o, i, f, c)
{
 var l = 'addEventListener', n = 'on'+i, b = o[n], a = o.evts = o.evts || [],
  m = / u=(\d+),r/.exec(b), u = m ? parseInt(m[1]) : a.length;
 if (o[l]) return o[l](i, f, false);
 a[u] = a[u] || [];
 if (!m)
 {
  if (b) a[u][0] = b;
  o[n] = new Function('e', 'e=e||self.event;var u=' + u + ',r=true,o=this,n="' + n +
   '",t=n+"TMP",a=o.evts[u];for(var f in a){o[t]=a[f];r=o[t](e)!=false&&r}return r&&isNS4&&' +
   c + '?o.routeEvent(e):r');
  if (isNS4 && c) o.captureEvents(Event[i.toUpperCase()]);
 }
 a[u][a[u].length] = f;
}

// I haven't coded up a 'removeEvent' function; that is left as an exercise to the reader :).
// You should just have to call object.removeEventListener() or find and remove the entry
// from the object.evts[] array (depending on the browser).

// Here's another version that drops NS4 support (and the fourth parameter, of course).
// It's smaller, and aimed at v5+ browsers.

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;
 }
};

