/* TOP LEVEL DROP DOWN ---------------------------------------------
	object to turn the top level of a heirarchical nav list into a
	drop-down while leaving the child lists as a static list

	PARAMETERS

	- id = string; ID of the list to manipulate
	- cssPath = string; path to a CSS file that will hide the nav
	  until it is set up

	REQUIRED MARKUP

	- UL element with ONE child UL, which itself may have as many
	  child ULs as desired; parent UL should have an ID attribute
	  corresponding to the ID passed to this object as a startup
	  parameter
	- LI children of the UL
	- one A per LI with only a text node as a descendant
	- one LI with a class of 'current' to be the selected
	  item in the drop-down menu

	REQUIRED CSS
	external stylesheet with the following rules:

	- rule to hide the target UL and another to show the target UL
	  when the .live class is applied
	- styles to hide the LIs when the UL does not have a class
	  of 'active'
	- styles to show the LIs when the UL does have a class of
	  'active'
	- styles for the active & inactive hover & rest states of
	  the LIs; each LI as an A within, and each A has two
	  nested SPANs (i.e., one SPAN is the parent of the other)
	  for styling hooks
	- styles for a container DIV that wraps the LI; must include
	  a rule of position: relative to provide a positioning
	  context for the list so that if, for example, the 3rd item
	  is the current that item doesn't move from under the 
	  visitor's mouse when they click to activate the menu

	REQUIRED SCRIPTS

	- DOM LOAD HANDLER functions
	- addStyleSheet
	- addUnloadHandler
	- addLoadHandler (required by DOM LOAD HANDLER functions)
*/
function TopLevelDropDown(id,cssPath) {
	var ua = '';
//	explicitly exclude Mac IE 5 -- browser sniffing's cheeseball, but since we're actually trying to exclude exactly one browser it's appropriate
	if (!((ua = navigator.userAgent.toLowerCase()).indexOf('msie') != -1 && ua.indexOf('mac') != -1 && !window.opera)) {
//	choose & cache our name
		this.name = id + 'TopLevelDropDown';
		window[this.name] = this;
//	holder for timer used to delay the mouseover action to dodge sloppy mousing + the 'mouseout' when moving from one menu item to another
		this.timer = null;
//	add the stylesheet to hide the nav until its set up
		addStyleSheet(cssPath);
//	set our initialisation script to run when the DOM is loaded
//		setInit( new Function ('return ' + this.name + ' .init("' + id + '");' ), function() { var nav; return ((nav = document.getElementById('mainNav')) && ((document.readyState && ('complete' == nav.readyState)) || (!document.readyState && nav.firstChild)));});
		addLoadHandler( new Function ('return ' + this.name + '.init("' + id + '");' ));
		return true;
	}
	return false;
}
//	method for setup that needs to run after the DOM loads; takes the ID of the ul upon which we're to operate as a parameter
TopLevelDropDown.prototype.init = function (id) {
	var	i = 0,
			container,
			numKids,
			navList,
			navLink,
			linkText;
	if (	document.getElementById &&
			(this.brandList = document.getElementById(id)) &&
			this.brandList.removeChild &&
			this.brandList.replaceChild &&
			this.brandList.appendChild &&
			this.brandList.firstChild &&
			this.brandList.lastChild &&
			this.brandList.previousSibling &&
			this.brandList.parentNode &&
			this.brandList.previousSibling.nextSibling &&
			this.brandList.insertBefore )
			 { 
//	hunt through the brandList for LIs, and stop when we find the one with a UL child 'There can be only one' -- the Kurgan ;-)
	while (typeof this.brandList.childNodes[i] != 'undefined' && (this.brandList.childNodes[i].nodeName.toLowerCase() != 'li'|| !(navList = this.brandList.childNodes[i].getElementsByTagName('ul')[0]))) i++;
	if (!navList) return false;
//	pull out the child UL (the nav for the current brand)
	navList = this.brandList.childNodes[i].removeChild(navList);
//	replace the outer ('brand') list with the child list
	this.brandList = this.brandList.parentNode.replaceChild(navList,this.brandList);
	navList.parentNode.insertBefore(document.createElement('div'),navList);
	navList.previousSibling.className = 'dropDownNav';
	navList.previousSibling.appendChild(this.brandList);

//	set a class on the wrapper
	this.brandList.parentNode.className = 'dropDownNav';

//	cache a reference to the brandList LIs
	this.brands	= this.brandList.getElementsByTagName('li');
//	count the number of nodes so we can loop through them
	numKids = this.brands.length;
//	loop through the brand list links and add a SPAN around the link text (again, a CSS hook) and the click handler
	for (i = 0; i < numKids; i++) {
		navLink = this.brands[i].firstChild;
//		if it's the link for the current brand, make the click handler show the menu
		if (navLink.parentNode.className.indexOf('current') != -1) navLink.onclick = new Function('e','return ' + this.name + '.displayMenu(e);');
//		if it's not the link for the current, then a click means to navigate so switch the active link while the next page loads
		else navLink.onclick = new Function('return ' + this.name + '.changeActive(this);');
//		now insert two spans around the link text -- more CSS hooks
		linkText = navLink.removeChild(navLink.firstChild);
		navLink.appendChild(document.createElement('span'));
		navLink.firstChild.appendChild(document.createElement('span'));
		navLink.firstChild.firstChild.appendChild(linkText);
//		navLink.firstChild.onmouseover = new Function('e','return ' + this.name + '.displayMenu(e,"showMenu");');
	}
//	add the .live class to show the nav
	this.brandList.className += ' live';
//	set up hide/show handlers so when the visitor mouses off the menu it disappears after a short delay;
//	n.b.: the 'show' handler only clears a pending 'hide' action to mask the mouseout events generated
//	when the cursor moves over the children of the UL; the visitor must always click to show the menu
	this.brandList.onmouseover = new Function('e','return ' + this.name + '.displayMenu(e);');
	this.brandList.onmouseout = new Function('e','return ' + this.name + '.displayMenu(e);');
//	set an unload handler to clean up after ourselves
	addUnloadHandler(new Function ('return ' + this.name + '.cleanUp();'));
	return this;
} return false;}
//	method to change the active link; takes the link to become active as an argument
TopLevelDropDown.prototype.changeActive = function (element) {
	var	i = 0, 
		numKids = this.brands.length;
//	loop through all brand links and set them all to inactive
	for (i = 0; i < numKids; i++) {
		this.brands[i].className = '';
		this.brands[i].getElementsByTagName('a')[0].onclick = new Function('return ' + this.name + '.changeActive(this);');
	}
//	hide the menu
	this.hideMenu();
//	change the class of the supplied link to current, and change its onload to
//	the showMenu method
	element.parentNode.className = 'current';
	element.onclick = new Function('e','return ' + this.name + '.displayMenu(e);');
	return true;
}
//	'traffic cop' method to deal with hiding/showing the menu; accepts an event parameter from
//	Moz-type browsers and a 'which' parameter to day whether it should be shown or hidden;
//	that parameter is only used by the mouseover handler to determine whether to start a countdown
// 	to hide the menu or to clear a pending hide -- mouseovers never show a hidden menu; the click
//	handlers shouldn't supply the parameter at all, as the user is unlikely to make an inadvertent
//	click, so they take effect immediately
TopLevelDropDown.prototype.displayMenu = function(e) {
//	determine whether the menu is currently visible
	var	isShown = (this.brandList.parentNode.className.indexOf(' active') != -1),
//	set the which variable to toggle the menu's state (call hide if the menu is visible, show if it's not)
	which = (isShown)?'hideMenu':'showMenu';
//	normalise the event object for browsers that don't implement the W3C event model
	e = (e)?e:window.event;
//	if the event we're handling is a click, execute the appropriate handler immediately
	if ('click' == e.type) return this[which]();
//	if the menu is currently visible and the event was a mouseout, start a countdown to hiding it
	else if (isShown && 'mouseout' == e.type) this.timer = setTimeout(this.name + '.hideMenu();',250);
//	otherwise, it must be a mouseover so we can clear the timer to hide (if one exists)
	else clearTimeout(this.timer);
	return true;
}
//	method to show the menu
TopLevelDropDown.prototype.showMenu = function () {
	var	i = 0,
		numBrands = this.brands.length,
		count = 0,
		offset = 0,
		classFound = -1,
		afterCurrent = false;
//	loop through the brands
	for (i = 0; i < numBrands; i++) {
//		if we've reached the current brand, calculate the offset necessary to centre that one over the holder DIV
		if ('current' == this.brands[i].className) {
			offset =  -1 * count * this.brands[i].offsetHeight;
			break;
		} else 	count++;
	}
//	set the offset on the top to get the 'current' brand centred
	this.brandList.style.top = offset + 'px';
//	set the height on the DIV to compensate for the fact that it's not going to be held open by the UL
//	add one px to compensate for the lack of a border, too
	this.brandList.parentNode.style.height = (this.brands[i].offsetHeight + 2) + 'px';
	setOpacity(85,this.brandList);
//	make the menu active
	this.brandList.parentNode.className += ' active';

	return false;
}
//	method to hide the menu
TopLevelDropDown.prototype.hideMenu = function () {
//	set the container height to 'auto' so it will resize with the page text
	this.brandList.parentNode.style.height = 'auto';
//	set top back to nuttin' so the active item doesn't end up offscreen or some such
	this.brandList.style.top = '';
//	set the opacity of the menu back to 100 
	setOpacity(100,this.brandList);
//	remove the active class to 'hide' the menu
	this.brandList.parentNode.className = this.brandList.parentNode.className.replace(/ ?active\b/,'');
	return false;
}
//	method to clean up any circular references to avoid memory leaks in IE/Win
TopLevelDropDown.prototype.cleanUp = function () {
	var numBrands = this.brands.length,
		i = 0;
//	loop through the brand links and remove all onclick handlers
	for (i = 0; i < numBrands; i++) {
		this.brands[i].firstChild.onclick = null;
	}
//	null our references to on-page elements
	this.brands = null;
	this.brandList = null;
	return true;
}
new TopLevelDropDown('brandNav','/docs/css/topleveldropdown.css');


/*	DRAWER ---------------------------------------------------------
	script to hide/show an extension; quite specific to the ITV
	Branding site ATM -- needs to be generalised
	
	REQUIRED MARKUP
	- block-level element w/and ID; must have a single block-level
	  child with auto height which wraps all content to be shown/
	  hidden

	REQUIRED CSS
	- drawer.css = styles for hide/show element
	
	REQUIRED SCRIPTS
	- addLoadHandler
	- addUnLoadHandler
	- addStyleSheet
	- getEasedStep
	- setOpacity
	- Animator instance in global variable 'animator'
	- getEmHeight

	PARAMETERS
	- id = string; id of element to show/hide
	- cssPath = string; path to drawer.css above
*/
function Drawer(id,cssPath) {
//	check browser support & whether our stylesheet gets added
	if (	document.getElementById &&
			document.getElementsByTagName &&
			document.createElement &&
			document.createTextNode &&
			addStyleSheet(cssPath,true)) {
//		set this widget's name
		window[(this.name = id + 'Drawer')] = this;
//		set ease value -- higher = slower accelleration
		this.ease = 4;
//		set cleanup to run onunload
		addUnloadHandler(new Function('return ' + this.name + '.cleanUp();'));
//		set our init to run onload
		addLoadHandler(new Function('return ' + this.name + '.init("' + id + '");'));
	}
	return false;
}
//	init function to run after the document is loaded
//	id = string; id of container element
Drawer.prototype.init = function (id) {
	if (this.container = document.getElementById(id)) {
		var junkDiv;
//		add junk markup clearing DIV so IE will respect the height of other columns after the drawer starts growing
//		get reference to the fieldset w/in the form, as IE won't set opacity on a form
		this.fieldset = this.container.getElementsByTagName('fieldset')[0];
//		append the actuator link w/in a paragraph
		this.actuator = this.container.parentNode.getElementsByTagName('form')[0].appendChild(document.createElement('p')).appendChild(document.createElement('a'));
		this.actuator.setAttribute('href','#' + id);
//		need to focus then blur or Safari doesn't display the text with appropriate link styles
		this.actuator.focus();
		this.actuator.blur();
		this.actuator.appendChild(document.createTextNode('Forgot your password?'));
//		set handler
		this.actuator.onclick = new Function('return ' + this.name + '.actuate();');
		return true;
	} else return false;
}
//	cleanup function
Drawer.prototype.cleanUp = function () {
//	remove link handler
	if (typeof this.actuator != 'undefined') this.actuator.onclick = null;
//	remove link
	this.actuator = null;
//	remove fieldset
	this.fieldset = null;
//	remove container
	this.container = null;
	return true;
}
//	function to extend/retract element
Drawer.prototype.actuate = function () {
	var height = 1,
		direction = 1,
		emHeight = getEmHeight(this.container);
//	block further clicks until the animation is done -- better to let the user click & abort extension/retraction, but this Q'n'D solution will do for now
	this.actuator.onclick = function () { return false; };
//	if container height is 2 or less (got to allow for FF including the border and IE not)
	if (2 >= (height = parseInt(this.container.offsetHeight))) {
//		set direction to 1 (extending)
		direction = 1;
//		set end & delta to container's contents height
		this.destination = this.delta = parseInt(this.container.firstChild.offsetHeight);
		this.ems = this.destination/emHeight;
//		set the current step to 5 to skip over the delay caused by lower-than-1 step values
		this.currStep = 5;
//	otherwise
	} else {
//		set direction to -1 (retracting)
		direction = -1;
//		end is 0
		this.destination = 1;
//		delta is the container height
		this.delta = parseInt(height);
//		set the current step to 1
		this.currStep = 1;
	}
//	divide delta by 5 and round to get our number of steps so we move 5px per step
	this.numSteps = Math.round(this.delta/5);
//	start animating and store the index to clear our animation
	this.animation = animator.addAnimation(new Function('return ' + this.name + '.animate(' + direction + ');'));
//	force brain-dead early Safari versions to update the display 
	this.container.parentNode.style.display = 'none';
	this.container.parentNode.style.display = 'block';
	return false;
}
//	function to animate the drawer
//	direction = integer; 1 (extending) or -1 (hiding)
Drawer.prototype.animate = function (direction) {
	var thisStep = getEasedStep(this.currStep,this.numSteps,this.delta,this.ease * direction);
//	if we're less than 1px away from the last step, finish up to avoid a stutter...
	if (this.delta - thisStep <= 1) {
//		clear the animation
		animator.removeAnimation(this.animation);
//		set the height of the container to 'auto'
		this.container.style.height = (0 >= direction)?'1px':this.ems + 'em';
//		this.container.style.height = (0 >= direction)?0:'auto';
//		this.container.style.height = this.destination + 'px';
//		set the opacity of the contents to 100% if we're extending, 0 if we're retracting
		setOpacity(100,this.fieldset);
		if (0 > direction) 	this.container.className = this.container.className.replace(/ ?extended/,'');
//		re-enable the actuator
		this.actuator.onclick = new Function('return ' + this.name + '.actuate();');
//	otherwise, if we're extending and we've reached the point where we're adding 1 pixel and haven't yet added the bottom border...
	} else if (Math.round(thisStep) == 1 && this.container.className.indexOf('extended') == -1) {
//		add the 'extended' class to create the bottom border
		this.container.className += ' extended';
		this.currStep++;
//	and if none of the previous cases are true
	} else if (0 > direction || 1 < thisStep) {
//		set the height to a minimum of 1; otherwise set it to whichever
		this.container.style.height = (0 < thisStep || 0 > direction)?((0 > direction)?this.delta - thisStep:thisStep) + 'px':'1px';
//		adjust the contents opacity by an eased step * direction
		thisStep = getEasedStep(this.currStep,this.numSteps,100,this.ease * direction)
		setOpacity(((0 > direction)?100 - thisStep:thisStep),this.fieldset);
//		increment the current step
		this.currStep++;
	} else {
		this.currStep++;
	}
	return true;
}

new Drawer('retrievePassword','/docs/css/drawer.css');


/*	UTILITIES ------------------------------------------------------ */

/*	DOM LOAD HANDLER -----------------------------------------------
	Based on work by Dean Edwards:
	http://dean.edwards.name/weblog/2005/09/busted/
	others using the .readystate property or placing the script tag
	at the end of the BODY element aren't reliable; neither is an
	inline script with the DEFER attribute, nor setting the DEFER
	attribute via script

	script sets up an array of initialisation functions and loops
	through them on DOMContentLoaded in Gecko-based browsers,
	when an external script with the DEFER attribute set runs in
	IE/Win (which is by definition after the DOM loads), when a
	supplied test function returns true afte being repeatedly
	called by a setTimeout or, if all else fails, after the
	onLoad event fires

	PARAMETERS FOR init()
	- isOnload = boolean; true if the function is being called by
	  an onload event, false otherwise

	PARAMETERS FOR setInit()
	- handler = function; the funciton to be executed when the
	  DOM is ready; if this function isn't supplied, the script
	  won't be executed until the onload handler fires
	- test = function; optional test for non-Moz, non-IE browsers
	  to see if the required element(s) are loaded; should return
	  true if they are, false if not

	REQUIRED SCRIPTS
	- ADD LOAD HANLDER
	- ie_init.js within conditional comments in the page containing
	  only a call to the init() function
*/
function init (isOnload) {
//	see if we've already completed, return if we have
	if (arguments.callee.done) return true;
	var	num = inits.length,
		i = 0,
		allDone = true;
//	loop through the initialisation functions
	for (i=0;i<num;i++) {
//		if the initialisation function hasn't run and it either has no test or the test succeeds
		if (!inits[i].done && (isOnload || (typeof inits[i].test == 'function' && inits[i].test()))) {
//			mark it as having run
			inits[i].done = true;
//			run the initialisation function
			inits[i]();
//		if the initialisation function hasn't run yet, then its test must've failed
//		so set a flag indicating we stil have at least one to run
		} else if (!inits[i].done) allDone = false;
	}
//	if we stil have initialisation functions to run, set a timeout to call ourselves
	if (!allDone) setTimeout('init(false)',250);
//	otherwise, set a flag indicating we're all done
	else arguments.callee.done = true;
	return true;
}

function setInit (handler, test) {
//	if the inits array doesn't exist yet, create it
	if (typeof inits == 'undefined') inits = [];
//	if the addEventListener method exists
	if (document.addEventListener) {
//		add a listener for the DOMContentLoaded event
		document.addEventListener("DOMContentLoaded", init, false);
	}
//	if the 2nd argument is a function, add it as the test property of the initialisation function we've been passed
	if (typeof test == 'function') handler.test = test;
//	add the initialisation functionto the initialisation array
	inits[inits.length] = handler;
//	if there's no timer set to run the initialisation functions, set one
	if (!init.timer) setTimeout('init(false)',250);
//	and add the initialisation function to the onload event handler, in case that happens to fire before the timer
	addLoadHandler(new Function('return init(true);'));
	return true;
}



/*	ADD LOAD HANDLER -----------------------------------------------
	allows adding more than one function to the onload event
	based on Simon Willison's discussion of closures at:
	http://simon.incutio.com/archive/2004/05/26/addLoadEvent
*/
function addLoadHandler(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

/*	ADD ONUNLOAD HANDLER -------------------------------------------
	allows adding more than one function to the window's onlunload
	event based on Simon Willison's closure demo
*/
function addUnloadHandler(func) {
	var oldonunload = window.onunload;
	if (typeof window.onunload != 'function') {
		window.onunload = function() {
			func();
			window.onload = null;
			window.onunload = null;
		}
	} else {
		window.onunload = function() {
			func();
			oldonunload();
		}
	}
}

/*	ADD STYLESHEET -------------------------------------------------
	adds a stylesheet prior to document load
	must be placed after any stylesheets it is to override
	
	PARAMETERS
	- path = string; path to CSS file, including filename
	- ie5Mac = boolean; if true, script uses an IE5Mac-compatible
	  document.write statement to add the stylesheet
*/
function addStyleSheet(path,ie5Mac) {
	var headElement,linkElement;
//		test that we have the necessary support by getting a
//		reference to the HEAD element, creating a new LINK element
//		and setting the REL attributes for a CSS stylesheet
	if (	!ie5Mac &&
			document.getElementsByTagName &&
			document.createElement &&
			(headElement  = document.getElementsByTagName('head')[0]) &&
			(linkElement = document.createElement('link')) &&
			(linkElement.setAttribute)) {
//		set TYPE & REL attributes for a CSS stylesheet
		linkElement.setAttribute('rel','stylesheet')
		linkElement.setAttribute('type','text/css');
//		set the HREF to the supplied cssPath + the stylesheet filename 'ticker.css'
		linkElement.setAttribute('href',path);
//		append the link to the head element, thereby adding the stylesheet to the document
		headElement.appendChild(linkElement);
//		note that the method above, while kosher in XHTML with an application/xml+xhtml MIME type,
//		does NOT work in IE5/Mac so the script gracefully degrades in that browser; if compatibility
//		with IE5/Mac is needed comment out all lines from getting the reference to the head down to
//		this commment and uncomment the line immediately following this one; note, though, that
//		document.write is verboten in XHTML sent as application/xml+xhtml
//		document.write('<link rel="stylesheet" type="text/css" href="' + path + '">');
		return true;
	} else if (ie5Mac) {
		document.write('<link rel="stylesheet" type="text/css" href="' + path + '">');
		return true;
	}
	return false;
}


/*	SET OPACITY ----------------------------------------------------
	sets the opacity of a supplied element; derived from couloir
	image fader
	
	PARAMETERS
	- opacity = float; percentage opacity to which the element will
	be set
	- el = HTML element; element whose opacity is to be set
	- class = class name to set on the element to avoid IE ClearType
	bug (see below)
*/
function setOpacity(opacity,el) {
//	TBH, I'm not sure why this is here, but it was in the original credited above
//	and I'm loathe to remove it without extensive testing
	opacity = (opacity >= 100)?99.999:opacity;
  
//	IE/Win
//	n.b.: in IE6 on WinXP with ClearType enabled, the headlines get all muddy looking
//	if you muck with the alpha UNLESS you add a background colour or image to the
//	container whose opacity is being adjusted, hence adjusting the className attribute
//	but if a background image/colour isn't an option comment this line out to remove
//	the alpha fade from IE win if you're concerned with headline text rendering in that
//	specific case; note that non-smoothed or 'standard smoothed' text doesn't display
//	the problem -- only ClearType
	el.style.filter = "alpha(opacity:"+opacity+")";
//	headline.className = 'tickerHeadline';
  
//	Safari<1.2, Konqueror
	el.style.KHTMLOpacity = opacity/100;

//	Older Mozilla and Firefox
	el.style.MozOpacity = opacity/100;

//	Safari 1.2, newer Firefox and Mozilla, CSS3
	el.style.opacity = opacity/100;
	return opacity;
}

/*	GET EASED STEP -------------------------------------------------
	gets an 'eased' step for easing animation; will do ease in 
	(positive ease value) or ease out (negative ease value)
	
	PARAMETERS
	- currStep = integer; which step (or 'frame') in the animation
	  we are currently on
	- numSteps = integer; total number of steps in the animation
	- delta = float; the total change in the value being animated
	  (e.g. if we're moving from position 0 to position 10, the
	   delta is 10)
	- easeValue = integer; factor for easing; higher ease factor
	  means a more gradual ease (slower acceleration)
*/
function getEasedStep(currStep,numSteps,delta,easeValue) {
	var base,easeFactor,destination;
//	if the ease value is positive...
	if (0 < easeValue) {
//		calculate the base; this is equal to the percentage complete, calculated by dividing
//		currentStep by numSteps
		base = currStep/numSteps;
//		calculate ease factor by raising the base to the easeValue-th power
		easeFactor = Math.pow(base,easeValue);
//		calculate the destination by multiplying the total change (delta) by the easeFactor
		destination = easeFactor * delta;
//		rerturn the rounded destination value
		return Math.round(destination)
//	otherwise, the ease value must be negative...
	} else {
//		calculate the base, then subtract from one (this is because we need to invert the
//		direction and subtracting 1 from it will render a negative number but with an
//		absolute value equal to the amount left rather than complete as with a positive
//		ease value
		base = currStep/numSteps-1;
//		calculate the ease factor by raising the base to the power equal to the absolute 
//		value of the ease value; we take the absolute value of this to eliminate the sign
//		difference between even and odd powers (base is negative due to subtracting above)
		easeFactor = Math.abs(Math.pow(base,Math.abs(easeValue)));
//		now subtract 1 to reverse the value back from percentage left to percentage complete
		easeFactor -= 1;
//		calculate the destination, reversing the sign to positive in the process
		destination = easeFactor * delta * -1;
//		rerturn the rounded destination value
		return Math.round(destination)
	}
} 


/*	GET EM HEIGHT --------------------------------------------------
	calculates the pixel height of an em within a given element

	PARAMETERS:
		- el = element object; object within which the em height
		  will be calculated
*/
function getEmHeight(el) {
	var emHeight = 0,
		testDiv = document.createElement('div');
//	need to set all styles using the style object, not the style attribute, for IE Win
	testDiv.style.padding = '0';
	testDiv.style.margin = '0';
	testDiv.style.border = '0 none';
	testDiv.style.lineHeight = '1em';
	testDiv.style.fontSize = '1em';
	testDiv.style.position = 'absolute';
	testDiv.style.visibility = 'hidden';
//	add the test DIV to the doc
	testDiv = el.appendChild(testDiv);
//	add a text node
	testDiv.appendChild(document.createTextNode('M'));
//	get the height
	emHeight = parseInt(testDiv.offsetHeight);
//	remove the test node and null the reference so we don't leak memory
	testDiv = el.removeChild(testDiv);
	testDiv = null;
	return emHeight;
}

/*	ANIMATOR -------------------------------------------------------
	an object that sets up a central timer to use for multiple
	animations; simply loops through an associative array of
	functions implemented as an object and calls each in turn each
	time through the interval

	REQUIRED SCRIPTS:
		- ADD LOAD HANDLER
		- ADD UNLOAD HANDLER

*/
function Animator(name,interval) {
//	cache our parameters
	this.name = name;
	this.interval = interval;

//	set a timer that calls our animation method every -interval- milliseconds
	this.timer = setInterval(this.name + '.animate();',this.interval);
//	nullify ourself on unload
	addUnloadHandler(new Function('return ' + this.name + '.unload();'));
	return this;
}
//	an array to hold the specific animation funcitons triggered by this timer
Animator.prototype.animations = [];
//	a method that loops through the animations array calling each function
Animator.prototype.animate = function() {
	var i,numAnimations;
	numAnimations = this.animations.length;
	for (i = 0; i < numAnimations; i++) {
		if (typeof this.animations[i] == 'function') this.animations[i]();
	}
}
//	a method for adding a function to the animations object; takes a function
//	to call each time through the interval and returns an index by which
//	the function can be referenced
Animator.prototype.addAnimation = function(func) {
	var numAnimations = this.animations.length
	this.animations[numAnimations] = func;
	return numAnimations;
}
//	method to delete a function from the animations object; takes a string
//	corresponding to the name of the method to be deleted
Animator.prototype.removeAnimation = function(index) {
	this.animations[index] = null;
	return true;
}
//	unload function to clean up this object to avoid memory leaks
//	in certain archaic browsers
Animator.prototype.unload = function() {
	var i,numAnimations;
	numAnimations = this.animations.length;
	for (i = 0; i < numAnimations; i++) {
		this.animations[i] = null;
	}
	clearInterval(this.timer);
	window[this.name] = null;
	return true;
}
addLoadHandler(new Function("window['animator'] = new Animator('animator',50);"));
