/**
 * @script: WebDDM
 * @file: WebDDM.js
 * @version: 3.2.0
 * @website: www.jportalhome.com
 * @email: josh@jportalhome.com
 * @copyright: (C) 2004 JPortal
 * @begin: Sun Jul 25 2004
 * @license: Free, Open Source (GPL)
 */

// domLib code from Dan Allen's dom Library - www.mojavelinux.com
// domLib version: 0.6
// -- Browsers --
var domLib_userAgent = navigator.userAgent.toLowerCase();
var domLib_isMac = navigator.appVersion.indexOf('Mac') != -1;
var domLib_isOpera = domLib_userAgent.indexOf('opera') != -1;
var domLib_isOpera7 = (domLib_userAgent.indexOf('opera/7') != -1 || domLib_userAgent.indexOf('opera 7') != -1);
var domLib_isSafari = domLib_userAgent.indexOf('safari') != -1;
var domLib_isKonq = domLib_userAgent.indexOf('konqueror') != -1;
// Both konqueror and safari use the khtml rendering engine
var domLib_isKHTML = (domLib_isKonq || domLib_isSafari);
var domLib_isIE = (!domLib_isKHTML && !domLib_isOpera && (domLib_userAgent.indexOf('msie 5') != -1 || domLib_userAgent.indexOf('msie 6') != -1));
var domLib_isIE5up = domLib_isIE;
var domLib_isIE50 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.0') != -1);
var domLib_isIE55 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.5') != -1);
var domLib_isIE5 = (domLib_isIE50 || domLib_isIE55);
// safari and konq may use string "khtml, like gecko", so check for destinctive /
var domLib_isGecko = domLib_userAgent.indexOf('gecko/') != -1;
var domLib_isMacIE = (domLib_isIE && domLib_isMac);
var domLib_isIE55up = domLib_isIE5up && !domLib_isIE50 && !domLib_isMacIE;
var domLib_isIE6up = domLib_isIE55up && !domLib_isIE55;

// -- Abilities --
var domLib_standardsMode = (document.compatMode && document.compatMode == 'CSS1Compat');
var domLib_useLibrary = (domLib_isOpera7 || domLib_isKonq || domLib_isIE5up || domLib_isGecko);
var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) == null));
var domLib_canFade = (domLib_isGecko || domLib_isIE || domLib_isSafari);
var domLib_canDrawOverSelect = (domLib_isGecko || domLib_isOpera || domLib_isMac);

// -- Event Variables --
var domLib_eventTarget = domLib_isIE ? 'srcElement' : 'currentTarget';
var domLib_eventButton = domLib_isIE ? 'button' : 'which';
var domLib_eventTo = domLib_isIE ? 'toElement' : 'relatedTarget';
var domLib_stylePointer = domLib_isIE ? 'hand' : 'pointer';
// NOTE: a bug exists in Opera that prevents maxWidth from being set to 'none', so we make it huge
var domLib_styleNoMaxWidth = domLib_isOpera ? '1000000px' : 'none';
var domLib_hidePosition = '-1000px';
var domLib_scrollbarWidth = 14;
var domLib_autoId = 1;
var domLib_zIndex = 100;

// -- Detection --
var domLib_selectElements;

// -- Timeouts --
var domLib_timeoutStateId = 0;
var domLib_timeoutStates = new Hash();

// -- WebDDM style constants --
var WebDDM_style_out = '';
var WebDDM_style_rollover = '_rollover';
var WebDDM_style_rolloverPressed = '_rollover_pressed';
var WebDDM_style_menuopenOut = '_menuopen';
var WebDDM_style_menuopenRollover = '_menuopen_rollover';
var WebDDM_style_menuopenPressed = '_menuopen_pressed';

/**
 * Initialize other public variables.
 */
// TRUE if global menu initilization has been done, FALSE otherwise
var WebDDM_globalInitDone = false;
// X,Y position of mouse on the screen.
var WebDDM_mousePosition = {x:0, y:0};
// Hash of WebDDM objects
var WebDDMObjects = new Hash();
// An array of "this" objects; for easy use of the "this"
// object in timeouts.
var thisObjs = new Hash();
// domTT fix for when domTT isn't included
var domTT_dragMouseDown = false;


/**
 * Hash library function. Creates a new Hash object, and uses passed arguments
 * to populate the Hash.
 * Examples:
 * new Hash('key1', 'value1', 'key2', 'value2', 3, 'value3', 4, 'value4', 5, 6);
 * new Hash({'key1': 'value1', 'key2': 'value2'});
 * new Hash(new Array('val1', 'val2', 'val3'));
 * new Hash(existing_hash_object);
 *
 * @return VOID
 *
 * @access PRIVATE
 */
function Hash()
{
	this.length = 0;
    this.numericLength = 0; 
	this.elementData = [];
	
	// <HACK>
	// Hack to easily recover if the first passed argument is a hash.
	if (arguments[0] && arguments[0].elementData) {
		this.length = arguments[0].length;
		this.numericLength = arguments[0].numericLength;
		this.elementData = arguments[0].elementData;
		return;
	}
	// </HACK>
	
	// <HACK>
	// Hack to easily convert existing arrays to hashes.
	// Load hash data from an existing array, if it was passed
	if (typeof(arguments[0]) == 'object') {
		this.elementData = arguments[0];
		return;
	}
	// </HACK>
	
	// For regular Hash operations - load hash object from passed parameters
	for (var i = 0; i < arguments.length; i += 2)
	{
		if (typeof(arguments[i + 1]) != 'undefined')
		{
			this.elementData[arguments[i]] = arguments[i + 1];
			this.length++;
            if (arguments[i] == parseInt(arguments[i])) 
            {
                this.numericLength++;
            }
		}
	}
} // End function Hash


/**
 * Hash prototype - get. Gets a Hash value that is paired with the passed in_key.
 *
 * @package Hash
 *
 * @param ANY in_key The key whose value will be returned.
 *
 * @return ANY Returns the value of the Hash element with the passed key.
 */
Hash.prototype.get = function(in_key)
{
    return this.elementData[in_key];
} // End Hash.prototype.get


/**
 * Hash prototype - set. Sets a Hash value that is paired with the passed in_key.
 *
 * @package Hash
 *
 * @param ANY in_key The key to set the value of.
 * @param ANY in_value The value to set in the Hash.
 *
 * @return ANY Returns in_value or FALSE if in_value is not set.
 */
Hash.prototype.set = function(in_key, in_value)
{
    if (typeof(in_value) != 'undefined')
    {
        if (typeof(this.elementData[in_key]) == 'undefined')
        {
            this.length++;
			if (parseInt(in_key) == in_key)
            {
                this.numericLength++;
            }
        }

        return this.elementData[in_key] = in_value;
    }

    return false;
} // End Hash.prototype.set


/**
 * Hash prototype - remove. Removes an element from the Hash with a key of the value passed.
 *
 * @package Hash
 *
 * @param ANY in_key The key to remove from the Hash.
 *
 * @return ANY Returns the value of the key removed or "undefined" if the key did not exist.
 */
Hash.prototype.remove = function(in_key)
{
    var tmp_value;
    if (typeof(this.elementData[in_key]) != 'undefined')
    {
        this.length--;
        if (in_key == parseInt(in_key)) 
        {
            this.numericLength--;
        }

        tmp_value = this.elementData[in_key];
        delete this.elementData[in_key];
    }

    return tmp_value;
} // End Hash.prototype.remove


/**
 * Hash prototype - size. Returns the number of elements in the Hash.
 *
 * @package Hash
 *
 * @return INTEGER Returns the number of elements in the Hash.
 */
Hash.prototype.size = function()
{
    return this.length;
} // End Hash.prototype.size


/**
 * Hash prototype - has. Checks to see if a key has a value in the Hash.
 *
 * @package Hash
 *
 * @param ANY in_key Key to check for a value.
 *
 * @return BOOLEAN Returns TRUE if the key's value is set or FALSE otherwise.
 */
Hash.prototype.has = function(in_key)
{
    return typeof(this.elementData[in_key]) != 'undefined';
} // End Hash.prototype.size


/**
 * Hash prototype - find. Searches the Hash for a key that corresponds to the passed
 * value. 
 *
 * @package Hash
 *
 * @param ANY in_obj Object or value to search for in the Hash.
 *
 * @return ANY Returns the key that corresponds to the in_obj or null if none is found.
 */
Hash.prototype.find = function(in_obj)
{
    for (var tmp_key in this.elementData) 
    {
        if (this.elementData[tmp_key] == in_obj) 
        {
            return tmp_key;
        }
    }
	return null;
} // End Hash.prototype.find


/**
 * Hash prototype - merge. Merges the passed Hash into the current Hash.
 *
 * @package Hash
 *
 * @param Hash in_hash Hash to merge
 *
 * @return VOID
 */
Hash.prototype.merge = function(in_hash)
{
    for (var tmp_key in in_hash.elementData) 
    {
        if (typeof(this.elementData[tmp_key]) == 'undefined') 
        {
            this.length++;
            if (tmp_key == parseInt(tmp_key)) 
            {
                this.numericLength++;
            }
        }

        this.elementData[tmp_key] = in_hash.elementData[tmp_key];
    }
} // End Hash.prototype.merge


/**
 * Hash prototype - compare. Compares the passed Hash with the current Hash.
 *
 * @package Hash
 *
 * @param Hash in_hash Hash to compare
 *
 * @return BOOLEAN Returns true if the hashes are identical or false.
 */
Hash.prototype.compare = function(in_hash)
{
    if (this.length != in_hash.length) 
    {
        return false;
    }

    for (var tmp_key in this.elementData) 
    {
        if (this.elementData[tmp_key] != in_hash.elementData[tmp_key]) 
        {
            return false;
        }
    }
    
    return true;
} // End Hash.prototype.compare


/**
 * "Hashify"s arrays.
 *
 * @param OBJECT/ARRAY Object or array to convert to Hash.
 *
 * @return Hash Returns a Hash of all converted data.
 *
 * @access PRIVATE
 */
function Hashify (in_array)
{
	// Loop through array and convert all array members to Hashes
	if (typeof(in_array) == 'object') {
		for (var i in in_array) {
			if (typeof(in_array[i]) == 'object') {
				in_array[i] = Hashify(in_array[i]);
			}
		}
		
		// Make the array a hash
		in_array = new Hash(in_array);
		
		// Return Hash'd array
		return in_array;
	}
	
	return in_array;
} // End function Hashify


/**
 * Returns a formatted unit, which is unit+'px' if the variable doesn't have a
 * unit included with it.
 *
 * @param STRING/INTEGER unit The unit to format
 *
 * @return STRING Returns a formatted string that can be used for positioning, dimensions,
 *   etc in CSS.
 *
 * @access PRIVATE
 */
function WebDDM_formatUnit (unit)
{
	var formatted;
	if (parseInt(unit) == unit) {
		formatted = unit + 'px';
	} else {
		formatted = unit;
	}
	return formatted;
} // End function WebDDM_formatUnit


/**
 * Specially escapes a string so that it can be put inside of another string to
 * be evaluated.
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING s String to "escape"
 * @param STRING quoteType Qupte character to be used in returned string; defaults to "
 *
 * @return STRING Returns the escaped version of the parameter string.
 *
 * @access PRIVATE
 */
function evalEscapeString (s, quoteType)
{
	// Set quote type, defaults to " if not already set
	var quoteType = quoteType ? quoteType : '"';
	// Escape and return string
	return 'unescape('+quoteType+escape(s)+quoteType+')';
} // End function evalEscapeString


/**
 * Builds one function object from the two functions passed. The function parameters of
 * the first function take precedence over the parameters of the second function.
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param FUNCTION function1 The first function
 * @param FUNCTION function2 The second function
 *
 * @return FUNCTION Returns the body of the first and second functions in a
 *   third "composite" function, complete with the parameters of the first
 *   and second functions.
 *
 * @access PRIVATE
 */
function WebDDM_buildFunction (function1, function2)
{
	// Initialize function body and parameters
	var function_body = '';
	var function_params = '';
	// Check that the first function is set; change the function
	// into a string; and parse the function.
	if (function1 && (data = WebDDM_parseFunction(function1 + ''))) {
		function_body += data[1];
		function_params += data[0];	
	}
	if (function2 && (data = WebDDM_parseFunction(function2 + ''))) {
		function_body += data[1];
		function_params += (function_params && data[0] ? ',' : '') + data[0];
	}
	// Create final function
	var retFunc;
	(function(){
		eval('retFunc = function ('+function_params+') {'+function_body+'};');
	})();
	// Return composite
	return retFunc;
} // End function WebDDM_buildFunction


/**
 * Parses a function and returns an array of [0]=function parameters, [1]=body
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING parseFunction Function to parse
 *
 * @return ARRAY [0] = function parameters, [1] = function body
 *
 * @access PRIVATE
 */
function WebDDM_parseFunction (parseFunction)
{
	var data = ['',''];
	if (parseFunction.match(/\s*function\s*\(([^\)]*)\)\s*\{([^$]*)\}\s*/)) {
		data[0] = RegExp.$1;
		data[1] = RegExp.$2;
	} else {
		data[1] = parseFunction;
	}
	return data;
} // End function WebDDM_parseFunction


/**
 * Merge two arrays. Returns the resulting array. Elements in the second array will NOT
 * replace elements in the first array, unless the third argument is "false".
 *
 * @param ARRAY baseArray The first base array to work off of
 * @param ARRAY mergeArray The array to merge into the base array
 * @param BOOLEAN override Should keys in the merge array override
 *   keys in the base array?
 * @param BOOLEAN provideIncrementalKeys If the key in the merge array
 *   is numeric and the same key exists in the base array, if this is
 *   set to TRUE, then the array element will be added to the end of
 *   the baseArray. This parameter is optional.
 *
 * @return ARRAY Returns the merged array
 *
 * @access PRIVATE
 */
function WebDDM_mergeArrays (baseArray, mergeArray, override, provideIncrementalKeys)
{
	for (var i in mergeArray) {
		// Try to make the key into an integer
		if (parseInt(i) == i) {
			i = parseInt(i);
		}
		// If we can, put key from the merge array into
		// the base array.
		if (typeof(baseArray[i]) == 'undefined' || override) {
			baseArray[i] = mergeArray[i];
		} else if (provideIncrementalKeys && typeof(i) == 'number') {
			// The key exists in the base array and we cannot override it
			// The key is numeric, and provideIncrementalKeys is TRUE
			// So, push this element to the end of the base array.
			baseArray[baseArray.length] = mergeArray[i];
		}
	}
	return baseArray;
} // End function WebDDM_mergeArrays


/**
 * Set a timeout, compatible with all browsers (sometimes timeout functionality 
 * broken).
 *
 * @author Dan Allen <http://www.mojavelinux.com>
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param FUNCTION in_function Callback function that will be called after the timeout.
 * @param INTEGER in_timeout An integer that determines the number of seconds to sleep before
    calling the callback function.
 * @param ARRAY in_args An array of arguments to pass to the callback function (optional).
 *
 * @return INTEGER Returns timeout ID that can be passed to domLib_clearTimeout().
 * @return BOOLEAN Returns false if no timeout is set and the callback function is
    called immediately.
 * 
 * @access PRIVATE
 */
function domLib_setTimeout(in_function, in_timeout, in_args)
{
	if (typeof(in_args) == 'undefined')
	{
		in_args = [];
	}
	
	// Make sure in_function is a function
	if (typeof(in_function) == 'string') {
		eval('var in_function = function () { '+in_function+' }');
	}

    if (in_timeout <= 0)
	{
//		throw "domLib_setTimeout(): Illegal timeout value " + in_timeout;
//    }
//	else if (in_timeout == 0)
//	{
		in_function(in_args);
		return 0;
	}

	// must make a copy of the arguments so that we release the reference
	var args = WebDDM_mergeArrays({}, in_args);

	if (!domLib_hasBrokenTimeout)
	{
		return setTimeout(function() { in_function(args); }, in_timeout);
	}
	else
	{
		var id = domLib_timeoutStateId++;
		var data = new Hash();
		data.set('function', in_function);
		data.set('args', args);
		domLib_timeoutStates.set(id, data);

		data.set('timeoutId', setTimeout('domLib_timeoutStates.get(' + id + ').get(\'function\')(domLib_timeoutStates.get(' + id + ').get(\'args\')); domLib_timeoutStates.remove(' + id + ');', in_timeout));
		return id;
	}
} // End function domLib_setTimeout


/**
 * Clear a timeout that was set with domLib_setTimeout
 *
 * @author Dan Allen <http://www.mojavelinux.com>
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param INTEGER timeoutID Timeout ID returned from domLib_setTimeout
 *
 * @return VOID
 *
 * @acess PRIVATE
 */
function domLib_clearTimeout(in_id)
{
	if (!domLib_hasBrokenTimeout)
	{
		clearTimeout(in_id);
	}
	else
	{
		if (domLib_timeoutStates.has(in_id))
		{
			clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId'))
			domLib_timeoutStates.remove(in_id);
		}
	}
} // End function domLib_clearTimeout


/**
 * Inspect a DOM object.
 *
 * @author Dan Allen <http://www.mojavelinux.com>
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param OBJECT in_object A DOM object to be inspected.
 *
 * @return Hash Returns hash with the following keys:
 *   'left': Left offset of the object from the main document.
 *   'top': Top offset of the object from the main document.
 *   'right': Right offset of the object from the main document.
 *   'bottom': Bottom offset of the object from the main document.
 *   'width': Actual width (in pixels) of the object. Useful if you
 *      haven't explicitly set the width/don't know what it is.
 *   'height': Actual height (in pixels) of the object. Useful if you
 *      haven't explicitly set the height/don't know what it is.
 *   'centerLeft': Offset from the main document to the center of the object
 *     on the X axis.
 *   'centerTop': Offset from the main document to the center of the object
 *     on the Y axis.
 *   'radius': 'height' if 'height' is greater than 'width', and vice-versa.
 *
 * @access PRIVATE
 */
function domLib_getOffsets (in_object)
{
	var originalObject = in_object;
	var originalWidth = in_object.offsetWidth;
	var originalHeight = in_object.offsetHeight;
	var offsetLeft = 0;
	var offsetTop = 0;

	while (in_object)
	{
		offsetLeft += in_object.offsetLeft;
		offsetTop += in_object.offsetTop;
		in_object = in_object.offsetParent;
	}

    // MacIE misreports the offsets (even with margin: 0 in body{}), still not perfect
    if (domLib_isMacIE) {
        offsetLeft += 10;
        offsetTop += 10;
    }

	return new Hash(
		'left',			offsetLeft,
		'top',			offsetTop,
		'right',		offsetLeft + originalWidth,
		'bottom',		offsetTop + originalHeight,
		'leftCenter',	offsetLeft + originalWidth/2,
		'topCenter',	offsetTop + originalHeight/2,
		'radius',		Math.max(originalWidth, originalHeight) 
	);
} // End function domLib_getOffsets

/**
 * Check if the first argument is the child of the second
 * argument in the DOM. The first argument doesn't have to
 * be a direct child; it can be grand-child, great-grand-child,
 * etc.
 *
 * @author Dan Allen <http://www.mojavelinux.com>
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param OBJECT in_object The alleged "child" object.
 * @param OBJECT in_ancestor The alleged ancestor
 *   of the domObject.
 *
 * @return BOOLEAN Returns true if the domObject is
 *   a descendant of the domAncestorObject, or false if it is not.
 *
 * @access PRIVATE
 */
function domLib_isDescendantOf(in_object, in_ancestor)
{
	if (in_object == in_ancestor)
	{
		return true;
	}

	while (in_object != document.documentElement)
	{
		try
		{
			if ((tmp_object = in_object.offsetParent) && tmp_object == in_ancestor)
			{
				return true;
			}
			else if ((tmp_object = in_object.parentNode) == in_ancestor)
			{
				return true;
			}
			else
			{
				in_object = tmp_object;
			}
		}
		// in case we get some wierd error, just assume we haven't gone out yet
		catch(e)
		{
			return true;
		}
	}

	return false;
} // End function domLib_isDescendantOf


/**
 * Gets the X, Y coordinates of the mouse for a particular event object.
 * Base code from PPK's article at evolt.
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param OBJECT eventObject Event object to "probe" for mouse position properties.
 *
 * @return Hash Returns array with "x" and "y" keys for the X and Y positions
 *   of the mouse.
 *
 * @access PRIVATE
 */
function domLib_getEventPosition(in_eventObj)
{
	var eventPosition = new Hash('x', 0, 'y', 0);

	// Opera and Konq both offer event properties that give the
	// total offset from top of	document to current scroll offset
	if (domLib_isKonq)
	{
		eventPosition.set('x', in_eventObj.x);
		eventPosition.set('y', in_eventObj.y);
	}
	// IE varies depending on standard compliance mode
	else if (domLib_isIE)
	{
		if (domLib_standardsMode)
		{
			eventPosition.set('x', in_eventObj.clientX + document.documentElement.scrollLeft);
			eventPosition.set('y', in_eventObj.clientY + document.documentElement.scrollTop);
		}
		// NOTE: just in case we fire too early, go ahead and check for document.body
		else if (document.body)
		{
			eventPosition.set('x', in_eventObj.clientX + document.body.scrollLeft);
			eventPosition.set('y', in_eventObj.clientY + document.body.scrollTop);
		}
	}
	else
	{
		eventPosition.set('x', in_eventObj.pageX);
		eventPosition.set('y', in_eventObj.pageY);
	}

	return eventPosition;
} // End function domLib_getEventPosition


/**
 * Hides all SELECT objects that are overlapping the passed object.
 * Optionally shows the hidden select objects if the second argument
 * is true (boolean).
 *
 * @author Dan Allen <http://www.mojavelinux.com>
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param OBJECT in_object DOM object to check.
 * @param BOOLEAN in_recover Set to "true" if you want to unhide
 *   any hidden select objects.
 *
 * @return VOID
 *
 * @access PRIVATE
 */
function domLib_detectCollisions(in_object, in_recover)
{
	if (domLib_canDrawOverSelect)
	{
		return;
	}

	if (typeof(domLib_selectElements) == 'undefined')
	{
		domLib_selectElements = document.getElementsByTagName('select');
		domLib_selectElements = WebDDM_mergeArrays(domLib_selectElements,
			document.getElementsByTagName('applet'), false, true);
		domLib_selectElements = WebDDM_mergeArrays(domLib_selectElements,
			document.getElementsByTagName('object'), false, true);
	}

	// if we don't have a tip, then unhide selects
	if (in_recover)
	{
		for (var cnt = 0; cnt < domLib_selectElements.length; cnt++)
		{
			var thisSelect = domLib_selectElements[cnt];

			if (!thisSelect.hideList)
			{
				thisSelect.hideList = new Hash();
			}

			thisSelect.hideList.remove(in_object.id);
			if (!thisSelect.hideList.length)
			{
				domLib_selectElements[cnt].style.visibility = 'visible';
				if (domLib_isKonq) { domLib_selectElements[cnt].style.display = ''; }
			}
		}

		return;
	}

	// okay, we have a tip, so hunt and destroy
	var objectOffsets = domLib_getOffsets(in_object);

	for (var cnt = 0; cnt < domLib_selectElements.length; cnt++)
	{
		var thisSelect = domLib_selectElements[cnt];

		// if the select is in the tip, then skip it
		// :WARNING: is this too costly?
		if (domLib_isDescendantOf(thisSelect, in_object))
		{
			continue;
		}

		if (!thisSelect.hideList)
		{
			thisSelect.hideList = new Hash();
		}

		var selectOffsets = domLib_getOffsets(thisSelect); 
		var center2centerDistance = Math.sqrt(Math.pow(selectOffsets.get('leftCenter') - objectOffsets.get('leftCenter'), 2) + Math.pow(selectOffsets.get('topCenter') - objectOffsets.get('topCenter'), 2));
		var radiusSum = selectOffsets.get('radius') + objectOffsets.get('radius');
		// the encompassing circles are overlapping, get in for a closer look
		if (center2centerDistance < radiusSum)
		{
			// tip is left of select
			if ((objectOffsets.get('leftCenter') <= selectOffsets.get('leftCenter') && objectOffsets.get('right') < selectOffsets.get('left')) ||
			// tip is right of select
				(objectOffsets.get('leftCenter') > selectOffsets.get('leftCenter') && objectOffsets.get('left') > selectOffsets.get('right')) ||
			// tip is above select
				(objectOffsets.get('topCenter') <= selectOffsets.get('topCenter') && objectOffsets.get('bottom') < selectOffsets.get('top')) ||
			// tip is below select
				(objectOffsets.get('topCenter') > selectOffsets.get('topCenter') && objectOffsets.get('top') > selectOffsets.get('bottom')))
			{
				thisSelect.hideList.remove(in_object.id);
				if (!thisSelect.hideList.length)
				{
					thisSelect.style.visibility = 'visible';
					if (domLib_isKonq) { thisSelect.style.display = ''; }
				}
			}
			else
			{
				thisSelect.hideList.set(in_object.id, true);
				thisSelect.style.visibility = 'hidden';
				if (domLib_isKonq) { thisSelect.style.display = 'none'; }
			}
		}
	}
} // End function domLib_detectCollisions


/**
 * This returns a DOM object with the passed ID.
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING elementId ID of the element in question
 *
 * @return OBJECT Returns a DOM object.
 *
 * @access PRIVATE
 */
function WebDDM_getElement (elementId)
{
	return document.all
		? document.all[elementId]
		: document.getElementById(elementId);
} // End function WebDDM_getElement


/**
 * InterCaps a string. Removes dashes, and uppercases the letter
 * directly after. This is useful for CSS. Examples:
 * -khtml-opacity = KhtmlOpacity
 * -moz-opacity = MozOpacity
 * background-color = backgroundColor
 * opacity = opacity
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING str String to intercap.
 *
 * @return STRING Returns the intercapped string.
 *
 * @access PRIVATE
 */
function interCap (str)
{
	while (str.match(/\-(.)/)) {
		// Character directly after the dash
		var c = RegExp.$1;
		// Intercap it
		str = str.replace('-'+c, c.toUpperCase());
	}
	// Return string
	return str;
} // End function interCap


/**
 * Takes a CSS string and returns an array of key:value pairs.
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING cssString String of CSS attributes.
 *
 * @return ARRAY Returns array of parsed CSS attributes.
 *
 * @access PRIVATE
 */
function parseCssString (cssString)
{
	var css = {};

	// Split string into chunks
	var chunks = cssString.split(/\s*\;\s*/);
	// Loop through chunks
	for (var k in chunks)
	{
		// v will be something like "border-color: red"
		var v = chunks[k];
		if (v.match(/^\s*([a-zA-Z0-9\-]*)\s*\:(.*?)$/)) {
			css[RegExp.$1] = RegExp.$2;
		}
	}
	return css;
} // End function parseCssString


/**
 * Cancels event bubbling of the passed event object.
 *
 * @param OBJECT inEvent The event object
 *
 * @return VOID
 *
 * @access PRIVATE
 */
function domLib_cancelBubble(in_event)
{
    var eventObj = in_event ? in_event : window.event;
    eventObj.cancelBubble = true;
} // End function domLib_cancelBubble


/**
 * The end-all, be-all WebDDM function/object/class/API.
 * w00t!
 * The generated WebDDM object can be retrieved by a call to
 * getWebDDMObject(containerId);
 *
 * @author Josh Gross <josh@jportalhome.com>
 *
 * @param STRING containerId ID of the menu's DIV tag.
 * @param ARRAY/Hash menuData Array or Hash of menu data.
 *
 * @return OBJECT Returns the "this" object to give you
 *   easy access to all of the menu's functions.
 *
 * @access PUBLIC
 */
function WebDDM (containerId, menuData)
{
	// Make this WebDDM object easily accessible
	WebDDMObjects.set(containerId, this);
	
	// Make sure we have a valid container
	var container = WebDDM_getElement(containerId);
	if (!container) {
		container = document.createElement('div');
		container.id = containerId;
		document.body.appendChild(container);
	}
	
	// Save the container ID
	this.containerId = containerId;
	// Save the menu data
	this.menuData = Hashify(menuData);
	// Initialize the open menu data hash
	this.openMenuData = new Hash();
	// Intialize the item hash evaluation path cache
	this.hashEvalPathCache = new Hash();
	// Initialize this menu's zIndex
	this.zIndex = (this.menuData.has('zIndex') ? this.menuData.get('zIndex') : 200);
	// Initialize last mouseover element
	this.lastMouseoverElement = '';
	// Initialize the mouseover status hash
	this.mouseoverStatuses = new Hash();
	// Visibility of SUBITEM elements populated by the toggleVisibility
	// method.
	this.menuOpenStatuses = new Hash();
	// Timeout object for the menu cleaning loop
	this.cleanMenusLoopTimeout = false;
	// Timeout object for the timeout call to check if all menus
	// can be hidden, when we're cleaning menus.
	this.hideAllTimeout = false;
	// Timeout object for the timeout call while cleaning menus; this
	// checks if individual menus can be hidden.
	this.cleanMenuTimeout = false;
	// Used for the above timeout; the menu ID that is being checked.
	this.cleanMenuId = false;
	// Hash of alphaAPI objects for the alphaTrans. Each item container
	// (that feeds) has its own alphaAPI object.
	this.alphaAPIObjects = new Hash();
	// Hash of clipAPI object for the clipTrans. Each item container
	// that slides has its own clipAPI object.
	this.clipperObjects = new Hash();
		
	// Set "loading" message
	container.innerHTML = '<div style="visibility: visible;" id="WebDDM_loading_' +
		container.id + '">A WebDDM menu is loading; please wait!</div>';
	// Set container styles
	var styles = ['position', 'top', 'left', 'width', 'height'];
	for (var i = 0; styles[i]; i++) {
		// Set CSS style
		// If the variable isn't set we may get an error?
		var style = styles[i];
		var value = this.menuData.get(style);
		if (i != 'position') {
			container.style[style] = WebDDM_formatUnit(value);
		} else {
			container.style[style] = value;
		}
	}
	// Set background color to transparent
	container.style.backgroundColor = 'transparent';
	// Set visibility and overflow to visible
	container.style.visibility = container.style.overflow = 'visible';
	// Set zIndex
	container.style.zIndex = this.zIndex++;
	
	// IS THIS A FLOATING MENU?
	// If it is, set it up to float
	// It's either floating menus or context menus; never both
	if (this.menuData.get('float')) {
		// Set float handler
		this.floatHandler = new FloatAPI(container.id, this.menuData.get('top'),
			this.menuData.get('left'), this.menuData.get('floatSpeed'), true);
	}
	
	// IS THIS A CONTEXT MENU?
	// If it is, set code that will open the submenu...
	else if (this.menuData.get('expand_menu').match(/context\(([a-zA-Z\-]*)\,([a-zA-Z0-9\-\_]*)\)/)) {
		// Get context menu options data
		// Get expand type
		var expandType = RegExp.$1;
		// Get context object
		var contextObjectId = RegExp.$2;
		
		// Now get the context object
		var contextObject;
		if (contextObjectId == 'document') {
			contextObject = document;
		} else {
			contextObject = WebDDM_getElement(contextObjectId);
		}

		// Now get "event" handler name
		var contextEvent;
		if (expandType == 'left-click') {
			// expandType: left-click --- onclick
			contextEvent = 'onclick';
 		} else if (expandType == 'right-click') {
			// expandType: right-click --- oncontextmenu
			contextEvent = 'oncontextmenu';
		} else {
			// expandType: anything else (probably "rollover") --- onmouseover
			contextEvent = 'onmouseover';
		}
		
		// Create the function code
		var evalCode = 'var contextFunctionCode = function (in_event) {' +
				// Get WebDDM object
				// The next line needs to be an eval for Opera :/
				'eval("var obj = getWebDDMObject(\\"' + this.containerId + '\\");");' +
				// Show submenu
//				'obj.showMenu();' +
				'obj.showMenu(obj.menuData, "");' +
				// Set menu at cursor...
				'obj.setMenuAtCursor();' +
				// Return false so the event doesn't bubble
				'return false;' +
			'}';
		eval(evalCode);

		// Now set the submenu opener code
		contextObject[contextEvent] = WebDDM_buildFunction(contextFunctionCode,
			contextObject[contextEvent]);
		// Set styles of top-level submenu
		this.menuData.set('position', 'absolute');
		container.style.position = 'absolute';
	}
	
	// If the global menu initializations haven't been done yet,
	// do them now.
	if (!WebDDM_globalInitDone) {
		// Set code to capture the mouse position onscreen
		var mousemoveCode = function (in_event) {
			// Make sure we have a valid event
			// IE sucks so we have to do this :)
			// It may not pass us the event object; instead it
			// sets the global variable "event".
			var in_event = in_event ? in_event : window.event;
			var eventPos = domLib_getEventPosition(in_event);
			WebDDM_mousePosition.x = eventPos.get('x');
			WebDDM_mousePosition.y = eventPos.get('y');

			// Update any tooltip dragging
			if (domTT_dragMouseDown) {
				domTT_dragUpdate(in_event);
			}
		};
		
		// Set document.onclick and onblur code so all submenus in all menus are
		// hidden
		var hideCode = function () { WebDDM_hideAllMenus(); };
		
		// Now assign actions to objects
		// We split it up to avoid the IE closure memory leak bug
		// Although we are not affected in THESE cases, the code may change sometime,
		// so it's just safer this way.
		(function(){
			document.onclick = WebDDM_buildFunction(document.onclick, hideCode);
			document.onblur = WebDDM_buildFunction(document.onblur, hideCode);
			document.onmousemove = WebDDM_buildFunction(document.onmousemove, mousemoveCode);
		})();
				
		// We've initialized WebDDM globally for all future menus and this
		// one; make sure it doesn't happen again
		WebDDM_globalInitDone = true;
	}
	
	// If we are supposed to, automatically show the base menu.
	if (this.menuData.get('expand_menu').match('auto')) {
		this.showMenu(this.menuData, "");
	}
	
	// Remove the loading message
	WebDDM_getElement('WebDDM_loading_' + container.id).style.visiblity = 'hidden';
	WebDDM_getElement('WebDDM_loading_' + container.id).innerHTML = '';
	
	// Return the new WebDDM object :)
	return this;
} // End function WebDDM


/**
 * Set the menu on the cursor.
 *
 * @package WebDDM
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.setMenuAtCursor = function ()
{
	// Get positions
	var topPos = WebDDM_mousePosition.y + parseInt(this.menuData.get('top'));
	var leftPos = WebDDM_mousePosition.x + parseInt(this.menuData.get('left'));
	// Set positions for object
	WebDDM_getElement(this.containerId).style.top = topPos + 'px';
	WebDDM_getElement(this.containerId).style.left = leftPos + 'px';
} // End WebDDM.prototype.setMenuAtCursor


/**
 * Build an item.
 *
 * @param Hash parentItemHash Item array to get build data from.
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.buildMenu = function (parentItemHash)
{
	// Get item ID
	var itemId = parentItemHash.get('itemId') || this.containerId;

	// Create the HTML container
	var HTML = '';
	
	// Initialize array of actions (aka onmouseover, onmouseout)
	// Each key will become a separate array, for each item.
	var actionsArray = [];
	
	// Get base z-index
	var zIndex = parentItemHash.zIndex || this.zIndex++;
	zIndex++;
	var originalZIndex = zIndex;

	// Loop through all the items in the hash of items
	for (var itemIndex = 1; parentItemHash.get('items').has(itemIndex); itemIndex++) {
		// Get item ID
		var thisItemId = itemId + '_' + itemIndex;
		// Get item hash
		var itemHash = parentItemHash.get('items').get(itemIndex);
		// Set item ID
		itemHash.set('itemId', thisItemId);
		// Set z-index
		if (!itemHash.has('zIndex')) {	
			itemHash.set('zIndex', zIndex++);
		}

		// Initialize actions for this item
		// These are just actions that will be needed for
		// WebDDM to function; if the user tries
		// to add custom actions it will work fine.
		var getWebDDMObjectVarCode = 'var WebDDM_obj = getWebDDMObject("'+this.containerId+'");';
		var actions = {
			'mouseover': getWebDDMObjectVarCode,
			'mouseout': getWebDDMObjectVarCode,
			'click': getWebDDMObjectVarCode,
			'mouseup': getWebDDMObjectVarCode,
			'mousemove': getWebDDMObjectVarCode,
			'mousedown': getWebDDMObjectVarCode,
			'selectstart': (domLib_isIE ? 'return false;' : '')
		};
		
		// Set defaults for this item hash
		itemHash.set('expand_menu', (itemHash.has('expand_menu') ? itemHash.get('expand_menu') : 'rollover'));
		itemHash.set('contract_menu', (itemHash.has('contract_menu') ? itemHash.get('contract_menu') : 'rollout'));
			
		// Set event handler actions
		actions.mouseover += 'WebDDM_obj.mouseAction(in_event, "mouseover", "'+thisItemId+'");';
		actions.mouseout += 'WebDDM_obj.mouseAction(in_event, "mouseout", "'+thisItemId+'");';
		actions.mousemove += 'WebDDM_obj.mouseAction(in_event, "mousemove", "'+thisItemId+'");';
		actions.mouseup += 'WebDDM_obj.mouseAction(in_event, "mouseup", "'+thisItemId+'");';
		actions.mousedown += 'WebDDM_obj.mouseAction(in_event, "mousedown", "'+thisItemId+'");';
		actions.click += 'WebDDM_obj.mouseAction(in_event, "click", "'+thisItemId+'");';
				
		/*************
		* Create item
		*************/
		
		// Get user-defined CSS
		var css = itemHash.has('css') ? itemHash.get('css') + ';' : '';
		
		// Get IE CSS filters
		var filter = itemHash.has('filter') && domLib_isIE
			? 'filter: ' + itemHash.get('filter') + ';'
			: '';

		// Get cursor and correct it
		// If it is hand or pointer, make sure that in IE it's 'hand' and in
		// gecko browsers it's 'pointer'
		var cursor = (itemHash.has('cursor')
			? (itemHash.get('cursor').match(/hand|pointer/)
				? domLib_stylePointer
				: itemHash.get('cursor'))
			: 'default');
		css += 'cursor: ' + cursor + ';';
		
		// Get item class
		var itemClass = (itemHash.get('class')
			? 'class="' + itemHash.get('class') + '"'
			: '');	
		HTML +=
			// Build item container
			'<div id="' + thisItemId + '_item_container" ' +
			'style="visibility:inherit;position:absolute;' +
				'top: ' + WebDDM_formatUnit(itemHash.get('top')) + ';' +
				'left: ' + WebDDM_formatUnit(itemHash.get('left')) + ';' +
				'margin: 0px; z-index: ' + itemHash.get('zIndex') + ';' +
				'width: ' + WebDDM_formatUnit(itemHash.get('width')) + ';' +
				'height: ' + WebDDM_formatUnit(itemHash.get('height')) + ';'
				+ filter + ';">' +
			// Build table
				'<table style="width:100%;height:100%;background-color:transparent;"' +
				' cellpadding="0" cellspacing="0"><tbody><tr>' + 
					// Build item
					'<td id="' + thisItemId + '_item" ' + itemClass + ' ' + 'style="' + css + '">' +
						// Item content
						itemHash.get('content') +
					// End item
					'</td>' +
				// End table
				'</tr></tbody></table>' +
			// End item container
			'</div>';
			
		// Set final actions in the actions array
		actionsArray[itemIndex] = actions;		
	} // End loop through all the items

	
	// Get the items hash
	var itemsHash = parentItemHash.get('items');
	// Create the item container
	var itemsContainer = WebDDM_getElement(this.containerId).
		appendChild(document.createElement('div'));
	// Set the ID of the subitem container
	itemsContainer.id = itemId + '_subitems';
	// Add HTML of all items to the subitem container
	itemsContainer.innerHTML = HTML;
	itemsContainer.style.position = 'absolute';
	itemsContainer.style.top = WebDDM_formatUnit(itemsHash.get('top') +
		(parentItemHash.get('parentId')
			? this.getItemHash(parentItemHash.get('parentId')).get('items').get('top')
			: 0));
	itemsContainer.style.left = WebDDM_formatUnit(itemsHash.get('left') +
		(parentItemHash.get('parentId')
			? this.getItemHash(parentItemHash.get('parentId')).get('items').get('left')
			: 0));
	itemsContainer.style.height = WebDDM_formatUnit(itemsHash.get('height'));
	itemsContainer.style.width = WebDDM_formatUnit(itemsHash.get('width'));
	itemsContainer.style.visibility = itemsContainer.style.overflow = 'hidden';
	itemsContainer.style.zIndex = originalZIndex;
	itemsContainer.style.borderStyle = 'none';

	// Set events - splitting it up like this fixes the IE closure memory leak bug
	eval('var onmousemoveCode = function () {' +
			'getWebDDMObject("'+this.containerId+'").' +
			'setMouseoverStatus("'+itemId+'subitems", true, true);' +
		'};');
	eval('var onmouseoutCode = function () {' +
			'getWebDDMObject("'+this.containerId+'").' +
			'setMouseoverStatus("'+itemId+'subitems", false, true);' +
	'};');
	(function(){
		itemsContainer.onmousemove = onmousemoveCode;
		itemsContainer.onmouseout = onmouseoutCode;
	})();

	
	if (domLib_isIE) {
		// This is a fake image. This is needed for IE to
		// execute the onmouseover/onmouseout code! (it doesn't execute the code
		// if the element has a transparent background; this fools IE into
		// thinking it's not transparent, but it actually is)
		itemsContainer.style.backgroundImage = 'url(-)';
		
		// Set item filter
		if (itemsHash.has('filter')) {
			itemsContainer.style.filter = itemsHash.get('filter'); 
		}
	}

	// Loop through actions array and set final actions for items
	for (var i in actionsArray) {
		var itemObject = WebDDM_getElement(itemId + '_' + i + '_item_container');
		for (actionName in actionsArray[i]) {
			// Assign anonymous function to a variable
			eval('var eventAction = function(in_event)' +
				'{' +
					'in_event = typeof(in_event) != "undefined" ? in_event : window.event; ' +
					actionsArray[i][actionName] + 
				'};');
				
			// Now assign event action code to the actual object
			// Splitting it up fixes the IE memory leak issue
			(function(){
				itemObject['on' + actionName] = eventAction;	
			})();
		}
	}
}; // End WebDDM.prototype.buildMenu


/**
 * Sets the mouseover status of an element. The mouseover status can be
 * retrieved using the getMouseoverStatus method.
 *
 * @package WebDDM
 *
 * @param STRING elementId ID of the element to modify status
 * @param BOOLEAN status The status, over (true) or off (false)
 * @param BOOLEAN doNotSetAsLast If set to TRUE, it will ONLY set
 *   the status of this element and won't do anything else.
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.setMouseoverStatus = function (elementId, status, doNotSetAsLast)
{
	// Correct doNotSetAsLast since it's optional
	if (typeof(doNotSetAsLast) == 'undefined') {
		doNotSetAsLast = false;
	}
	
	// Set last mouseover to "false" since if we're over a different item,
	// we're not over the last one now. Also execute it's onmouseout code.
	if (!doNotSetAsLast)
	{
		if (this.lastMouseoverElement && this.lastMouseoverElement != elementId)
		{
			// Set mouseover status to false
			this.mouseoverStatuses.set(this.lastMouseoverElement, false);
			// Execute onmouseout code
			this.mouseAction(0, 'mouseout', this.lastMouseoverElement);
		}

		// Set last mouseover ID
		this.lastMouseoverElement = elementId;
	}

	// Set current mouseover status
	this.mouseoverStatuses.set(elementId, status);
}; // End WebDDM.prototype.setMouseoverStatus


/**
 * Returns the current mouseover status of the element with the passed
 * ID.
 *
 * @package WebDDM
 *
 * @param STRING elementId Element ID of the element to check
 *
 * @return BOOLEAN True if the mouse is over, else false
 *
 * @access PRIVATE
 */
WebDDM.prototype.getMouseoverStatus = function (elementId)
{
	return this.mouseoverStatuses.get(elementId)
		? true
		: false;
}; // End WebDDM.prototype.getMouseoverStatus


/**
 * Checks whether a menu can be automatically hidden or not. If the menu does not
 * have "rollout" in it's contract_menu data hash parameter, or if the
 * mouse is over the menu, then it returns false.
 *
 * @package WebDDM
 *
 * @param Hash itemHash Data of the menu's parent item
 *
 * @return BOOLEAN True if the menu can be hidden, false otherwise
 *
 * @access PRIVATE
 */
WebDDM.prototype.canHideMenu = function (itemHash)
{
	// If item hash or ID has not been initialized, quit
	if (!itemHash || (itemHash && !itemHash.has('itemId'))) {
		return true;
	}
	// Now get the item ID that we use
	var itemId = itemHash.get('itemId');
	// Can the menu be closed at all? If contract_menu
	// is not set or "rollout" is not in it, it can't be
	var contractMenu = itemHash.has('contract_menu') ? itemHash.get('contract_menu') : false;
	if (!contractMenu) {
		if (itemId == this.containerId || !itemId) {
			return false;
		} else {
			return true;
		}
	}
	if (contractMenu && (!contractMenu.match(/rollout/) || contractMenu.match(/none/))) {
		return false;
	}
	
	// Now we know that technically it is possible to close
	// the menu. SHOULD we?
	if (this.getMouseoverStatus(itemId)) {
		return false;
	}
	if (this.getMouseoverStatus(itemId + 'subitems')) {
		return false;
	}
	
	// Now THIS item can be hidden
	// Can subitems?
	if (itemHash.has('items')) {
		for (var i in itemHash.get('items').elementData) {
			if (parseInt(i) != i) {
				continue;
			}
			if (!this.canHideMenu(itemHash.get('items').get(i))) {
				return false;
			}
		}
	}
	
	// We can hide this menu
	return true;
}; // End WebDDM.prototype.canHideMenu


/**
 * Initialize an item's attributes, like itemId, parentItemId, etc.
 *
 * @package WebDDM
 *
 * @param Hash itemHash Item's array
 * @param STRING itemId Item ID
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.initializeItemAttributes = function (itemHash, itemId)
{
	// Remove container ID from item ID
	var bareItemId = itemId.replace(this.containerId + '_', '');
	// Get menu level
	itemHash.set('menuLevel', (bareItemId
		? bareItemId.split('_').length
		: 0));
	// Get parent ID
	itemHash.set('parentId', (itemId.match(/^(.*?)\_([0-9]+)$/)
		? RegExp.$1
		: ''));
	// Set item and container IDs in the item hash
	itemHash.set('itemId', itemId);
}; // End WebDDM.prototype.initializeItemAttributes


/**
 * Get an item hash corresponding to the passed item ID.
 * Item ID is in this format: "1_1_2_3"
 *
 * @package WebDDM
 *
 * @param STRING itemId Item ID of item hash to fetch.
 *
 * @return Hash Returns the item hash.
 *
 * @access PUBLIC
 */
WebDDM.prototype.getItemHash = function (itemId)
{
	// Get item ID
	var itemId = (this.containerId == itemId ? '' : itemId);

	// Remove container ID from item ID
	var bareItemId = itemId.replace(this.containerId + '_', '');

	// If this item hash evaluation path has not been cached,
	// create it.
	if (!this.hashEvalPathCache.has(bareItemId)) {
		var evalCode = 'var itemHash = this.menuData';
		if (itemId != '') {
			var chunks = bareItemId.split('_');
			for (var i = 0; i < chunks.length; i++) {
				evalCode += '.get("items").get('+chunks[i]+')';
			}
		}
		evalCode += ';';
		
		// Save (cache) eval code
		this.hashEvalPathCache.set(bareItemId, evalCode);
	}
	
	// Evaluate cached eval code
	eval(this.hashEvalPathCache.get(bareItemId));
	
	// Return item hash
	return itemHash;
}; // End WebDDM.prototype.getItemHash


/**
 * Checks whether a menu is open or not.
 *
 * @package WebDDM
 *
 * @param STRING itemId Item ID of the menu to check
 *
 * @return BOOLEAN True if the menu is open, false otherwise
 */
WebDDM.prototype.menuIsOpen = function (itemId)
{
	// Basically, we'll just look at the hash of 
	// visible elements populated by the toggleVisibility
	// method. We can't directly look at the CSS visibility
	// property because an element may be transitioning out,
	// in which case the menu is closed but still visible.
	return (this.menuOpenStatuses.get(itemId)
		? true
		: false);
}; // End WebDDM.prototype.menuIsOpen


/**
 * Hides menus on or above the level of the passed item hash.
 *
 * @package WebDDM
 *
 * @param Hash itemHash Item hash to probe for menu level data
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.hideMenusOnLevel = function (itemHash)
{
	var menuLevel = itemHash.get('menuLevel');
	var itemId = itemHash.get('itemId');
	// If the item hash level is above 0, continue
	if (itemHash.get('menuLevel') > 0) {
		// Loop through the open menus
		for (var tmp_key = (this.openMenuData.size()-1); tmp_key >= 0; tmp_key--) {
			// Get menu data
			var tmp_itemHash = this.openMenuData.get(tmp_key);
			var tmp_menuLevel = tmp_itemHash.get('menuLevel');
			var tmp_itemId = tmp_itemHash.get('itemId');
			// If the open menu meets certain criteria, hide it
			if (tmp_menuLevel >= menuLevel && !tmp_itemId.match(itemId)) {
				this.hideMenu(tmp_itemHash);
			} // End if menu_level above this menu_level
		} // End for array_keys
	} // End if menu_level above
}; // End WebDDM.prototype.hideMenusOnLevel

 
/**
 * Shows a menu's submenu. Builds the submenu if needed.
 *
 * @package WebDDM
 * 
 * @param STRING itemHash Item hash of the item to open
 * @param STRING itemId Item ID of the item to open
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.showMenu = function (itemHash, itemId)
{
	// If the max subitem ID has not been set for the item hash,
	// it means that other variables, too, have not been initialized.
	// Intialize all of them.
	if (!itemHash.has('parentId')) {
		this.initializeItemAttributes(itemHash, itemId);
	}
	
	// Close all menus on/above this level
	this.hideMenusOnLevel(itemHash);
	
	// If there are no subitems, stop now
	if (!itemHash.has('items')) {
		return;
	}
	
	//  Set menu open status
	this.menuOpenStatuses.set(itemId, true);
	
	// Get key for this Item in the hash of open menus
	var thisMenuOpen = false;
	var thisArrayId = this.openMenuData.find(itemHash) || -1;
	if (thisArrayId != -1) {
		thisMenuOpen = true;
	}
	
	// Get Item Or Container ID
	var itemOrContainerId = itemId || this.containerId;

	// Build menu
	if (!WebDDM_getElement(itemOrContainerId + '_subitems')) {
		this.buildMenu(itemHash);
	}
	
	// The menu has not been opened yet.
	// Assign it a new hash key.
	if (thisMenuOpen == false) {
		// Get new array ID
		thisArrayId = this.openMenuData.size();

		// Change style to open
		if (itemId) {
			this.setItemStyle(itemHash, WebDDM_style_menuopenOut);
		} else if (itemHash.get('contract_menu') &&
			itemHash.get('contract_menu').match('rollout'))
		{
			// There is no item_id, meaning that this is the base menu
			// Set the mouseover status to true
			// The menu closes on rollout, meaning that
			// (a) the menu is a context menu or (b) the user
			// is a crackhead. Assume A - the first item
			// will likely be placed directly under the cursor,
			// and the onmouseover code will not be executed
			// right away - so the mouseover status will be
			// FALSE, and the menu will be closed right away.
			this.setMouseoverStatus(this.containerId + '_1', true);
		}
	}

	// Put this item hash in the open menu data hash
	this.openMenuData.set(thisArrayId, itemHash);

	// Show menu
	this.setVisibility(itemHash.get('items'), itemOrContainerId + '_subitems', true);
	
	// If there are any subitems that have "expand_menu" set to "auto",
	// open them now.
	for (var i = 1; itemHash.get('items').has(i); i++) {
		// Check if the expand_menu of the subitem is set to auto
		var expandMenu = itemHash.get('items').get(i).get('expand_menu');
		if (expandMenu && expandMenu.match('auto')) {
			// Get subitem ID
			var subItemId = itemHash.get('items').get(i).get('itemId');
			// Show subitem's subitems
			this.showMenu(itemHash.get('items').get(i), subItemId);
			// Set mouseover status of subitem to true if 
			// the subitem's subitems close with rollout.
			var contractMenu = itemHash.get('items').get(i).get('contract_menu');
			if (contractMenu && contractMenu.match('rollout')) {
				this.setMouseoverStatus(subItemId, true);					
			}
		} // End if expand_menu contains 'auto'
	} // End for loop through itemHash.item
	// Set loop for cleaning menus
	this.cleanMenusLoop();
}; // End WebDDM.prototype.showMenu


/**
 * "Checks" a click menu when opening it; basically looks to see if there
 * are any click menus open on the same level, if there are, close them
 * and open this  menu.
 *
 * @package WebDDM
 *
 * @param STRING itemId ID of the item
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.checkClickMenu = function (itemId)
{
	// Get item hash
	var itemHash = this.getItemHash(itemId);
	
	// Get bare item ID
	var bareItemId = itemId.replace(this.containerId + '_', '');
	
	// Make sure menu level variable is preset in this hash
	if (!itemHash.has('menuLevel')) {
		// Set menu level...
		itemHash.set('menuLevel', (bareItemId
			? bareItemId.split('_').length
			: 0));
	}
	
	// Is there a menu open on this level?
	if (itemHash.get('menuLevel') > 0) {
		for (var i = (this.openMenuData.size() - 1); i > 0; i--) {
			var data = this.openMenuData.get(i);
			if (data && data.get('menuLevel') == itemHash.get('menuLevel') &&
				data.get('itemId') != itemId)
			{
				// There is a menu open on this level. Does it
				// expand with onclick?
				if (data.get('expand_menu').match('click')) {
					if (data.get('contract_menu').match('click')) {
						// Show the menu and break the loop
						this.showMenu(itemHash, itemId);
						break;
					} else if (data.get('contract_menu').match('rollout')) {
						// The current menu on this level closes on rollout.
						// In this case, it should NOT be open now since we're
						// not over the menu! So just close it (and all
						// children) here
						this.hideMenusOnLevel(itemHash);
					}
				}
			}
		}
	}
}; // End WebDDM.prototype.checkClickMenu


/**
 * Start the menu cleaning loop; calls cleanMenus every X milliseconds.
 *
 * @package WebDDM
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.cleanMenusLoop = function ()
{
	// Clear timeout
	domLib_clearTimeout(this.cleanMenusLoopTimeout);
	// Get first closable ID of the menu
	var data = this.openMenuData.get(0);
	if (data && data.has('contract_menu') && data.get('contract_menu').match(/rollout/)) {
		var firstId = 0;
	} else {
		var firstId = 1;
	}
	// Set another timeout
	this.cleanMenusLoopTimeout = domLib_setTimeout('getWebDDMObject("' +
		this.containerId + '").cleanMenusLoop();',
		((this.openMenuData.size()-1) >= firstId) ? 10 : 1000);
	// Clean menus
	this.cleanMenus(firstId);
}; // End WebDDM.prototype.cleanMenusLoop

	
/**
 * Clean menus - closes menus if possible.
 *
 * @package WebDDM
 *
 * @param INTEGER firstId The first closable ID of the menu
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.cleanMenus = function (firstId)
{
	// Get last opened menu's ID
	this.cleanMenuId = (this.openMenuData.size() - 1);
	// Try to hide the last menu opened
	// If the menu can be hidden in 1 millisecond, hide it
	// We do a timeout just to make sure that the menu didn't
	// show up _this_ millisecond
	// Hey, it happens :)
	var data = this.openMenuData.get(this.cleanMenuId);
	if (data && !this.cleanMenuTimeout && this.canHideMenu(data)) {
		// Set timeout
		this.cleanMenuTimeout =
			domLib_setTimeout('var obj = getWebDDMObject("' + this.containerId + '");' +
				'obj.cleanMenuTimeout = false;' +
				'var data = obj.openMenuData.get(obj.cleanMenuId);' +
				'if (data && obj.canHideMenu(data)) {' +
					'obj.hideMenu(data);' +
				'}', data.has('closeDelay') ? data.get('closeDelay') : 1);
	}
		
	// If the first menu opened can be hidden,
	// hide it and all the menus above.
	// First, get the "first element"
	var firstEl = this.openMenuData.get(firstId);
	// Now see if the first menu can be closed...
	if (firstEl && this.canHideMenu(firstEl)) {
		// Do a timeout and if the element can still be hidden,
		// hide everything
		if (this.hideAllTimeout == false) {
			this.hideAllTimeout = domLib_setTimeout('var obj = getWebDDMObject("'
				+ this.containerId + '");' +
				'obj.hideAllTimeout = false;' +
				'var firstEl = obj.openMenuData.get(' + firstId + ');' +
				'if (firstEl && obj.canHideMenu(firstEl)) {' +
					'obj.hideAllSubmenus(' + firstId + ');' +
				'}', data.has('closeDelay') ? data.get('closeDelay') : 100);
		}
	} else {
		// Since the first menu should not be hidden,
		// make sure that ABSOLUTELY NO timeouts are running
		// to close it.
		domLib_clearTimeout(this.hideAllTimeout);
		this.hideAllTimeout = false;
	}
}; // End WebDDM.prototype.cleanMenus


/**
 * Hides a submenu with the specified parent item hash.
 * Note that the passed hash must point to the LAST OPEN SUBMENU in the
 * openMenuData hash; if it doesn't, then nothing will happen.
 *
 * @package WebDDM
 *
 * @param STRING itemHash Item hash of the submenu's parent item
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.hideMenu = function (itemHash, setMouseoverStatus)
{
	// Make sure the item hash is valid
	if (!itemHash) {
		return;
	}

	// Make sure setMouseoverStatus is valid 
	if (typeof(setMouseoverStatus) == 'undefined') {
		setMouseoverStatus = true;
	}
	
	// Make sure passed item_id matches the item ID in the item hash
	if (itemHash != this.openMenuData.get(this.openMenuData.size() - 1)) {
		return;
	}

	// Get item ID
	var itemId = itemHash.get('itemId');
	
	if (setMouseoverStatus) {
		// Set menuover status
		this.setMouseoverStatus(itemId, false);
	}

	// Change styles
	if (itemHash.get('itemId')) {
		this.setItemStyle(itemHash,
			(this.getMouseoverStatus(itemHash.get('itemId'))
				? WebDDM_style_rollover
				: WebDDM_style_out));
	}

	// Hide all the items
	this.setVisibility(itemHash.get('items'),
		(itemHash.get('itemId') || this.containerId) + '_subitems', false);

	this.openMenuData.remove(this.openMenuData.size() - 1);

	//  Set menu open status
	this.menuOpenStatuses.set(itemHash.get('itemId'), false);
}; // End WebDDM.prototype.hideMenu


/**
 * Hides all menus. If the firstId variable is set, only menus with
 * openMenuData keys on or above the firstId will be hidden.
 *
 * @package WebDDM
 *
 * @param INTEGER firstId The first openMenuData key which value points to the first
 *   menu that can possibly be closed. Automatically detected if not passed.
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.hideAllSubmenus = function (firstId)
{
	// If firstId is not set, get it now
	if (typeof(firstId) == 'undefined') {
		// Get first closable ID of the menu
		var data = this.openMenuData.get(0);
		if (data && data.has('contract_menu') && data.get('contract_menu').match(/rollout/)) {
			var firstId = 0;
		} else {
			var firstId = 1;
		}
	}
	
	// Prevent errors from occuring...
	if (!this.openMenuData.has(firstId)) {
		return;
	}
	
	// Hide all item's submenus
	for (var i = (this.openMenuData.size() - 1); i >= firstId; i--) {
		// Set mouseover status of the item to false, since we know
		// we're over the document and not the item. Also, there are
		// some cases when WebDDM sets the mouseover status to true
		// automatically when the mouse is not over the item. This
		// fixes any bugs.
		this.setMouseoverStatus(this.openMenuData.get(i).get('itemId'), false);
		
		// Hide menu
		this.hideMenu(this.openMenuData.get(i));
	}
}; // End WebDDM.prototype.hideAllSubmenus


/**
 * Sets the item's styles to '' (out), '_rollover', '_menuopen',
 * '_menuopen_rollover', '_rollover_pressed', or '_menuopen_pressed'
 *
 * @package WebDDM
 *
 * @param STRING itemHash Item hash of the item to style
 * @param STRING new_style Style type
 * @param BOOLEAN forceStyle An OPTIONAL parameter; styles will be updated ALWAYS if this is true
 *
 * @return VOID
 *
 * @access	PRIVATE
 */
//WebDDM.prototype.setItemStyle = function (itemId, new_style, forceStyle)
WebDDM.prototype.setItemStyle = function (itemHash, new_style, forceStyle)
{
	// Get fallback style
	new_style.match(/^([\_a-zA-Z]*)\_([a-zA-Z]*)$/);
	var fallback_style = RegExp.$1;

	// Get element
	var element = WebDDM_getElement(itemHash.get('itemId') + '_item');		

	// Change class 
	var style = itemHash.has('class' + new_style) ? new_style : fallback_style; 
	if (itemHash.has('class' + style) && !(itemHash.get('currentStyleClass') == style && !forceStyle)) {
		element.className = itemHash.get('class' + style);
		itemHash.set('currentStyleClass', style);
	}

	// Change CSS
	var style = itemHash.has('css' + new_style) ? new_style : fallback_style; 
	if (itemHash.has('css' + style) && !(itemHash.get('currentStyleCSS') == style && !forceStyle)) {
		// Get array of CSS attribute:value pairs from CSS string
		var css = parseCssString(itemHash.get('css' + style));
		for (var k in css) {
			try {
				// Set particular style
				element.style[interCap(k)] = css[k];
			} catch (e) { }
		}
		itemHash.set('currentStyleCSS', style);
	}

	// Change content
	var style = itemHash.has('content' + new_style) ? new_style : fallback_style; 
	if (itemHash.has('content' + style) && !(itemHash.get('currentStyleContent') == style && !forceStyle)) {
		element.innerHTML = itemHash.get('content' + style);
		itemHash.set('currentStyleContent', style);
	} 
}; // End function WebDDM.prototype.setItemStyle


/**
 * Toggles the visibility of the passed element.
 * Uses the passed hash for data and passed element ID
 * as the element that we change the visibility status of.
 * The third argument is a boolean describing whether we
 * are showing or hiding the item.
 * This is another revolutionary WebDDM invention :)
 * Transitions:
 * * Opacity (Mozilla browsers, IE, Safari)
 * * Clipping (all browsers supporting the CSS clip property; IE4+, NS?, Opera, Safari, Konq....)
 * * More coming!
 *
 * @package WebDDM
 *
 * @param Hash itemsData An 'items' hash
 * @param STRING itemsId The DOM ID of the 'items' element
 * @param BOOLEAN visibility True or false
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.setVisibility = function (itemsData, itemsId, visibility)
{
	// If we are making the items visible, detect
	// collisions with select lists, applets, objects, etc
	if (visibility) {
		domLib_detectCollisions(WebDDM_getElement(itemsId), false);
	}
	
	// Initially set "transitionsUsed" to false
	var transitionsUsed = false;
	
	// Check if we are supposed to do an opacity transition
	if (itemsData.has('alphaTrans') && domLib_canFade) {
		this.alphaTrans(itemsData.get('alphaTrans'), itemsId, visibility);
		transitionsUsed = true;
	}
	// Check if we are supposed to do a cliptrans transition
	if (itemsData.has('clipTrans')) {
		this.clipTrans(itemsData.get('clipTrans'), itemsId, visibility);
		transitionsUsed = true;
	}
	
	// Don't do anything special, just show or hide the element
	var element = WebDDM_getElement(itemsId);
	if (!transitionsUsed) {
		element.style.visibility = visibility
			? 'visible'
			: 'hidden';
		// Call the transitionCompleted method to clean up after everything.
		// Doesn't matter that we didn't transition at all; the transitionCompleted
		// method just needs to be called after the visibility is done changing.
		this.transitionCompleted(element, visibility);
	} else if (visibility) {
		element.style.visibility = 'visible';
	}
}; // End WebDDM.prototype.setVisibility


/**
 * The alpha transition system.
 *
 * @package
 *
 * @param Hash transitionData Alpha transition configuration (items.alphaTrans)
 * @param STRING itemsId ID of the item container in question.
 * @param BOOLEAN visibility True to make the item container visible, else false
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.alphaTrans = function (transitionData, itemsId, visibility)
{
	// Get alphaAPI object
	var fader;

	// Initialize the fader if needed
	if (this.alphaAPIObjects.has(itemsId)) {
		// Get alphaAPI object
		fader = this.alphaAPIObjects.get(itemsId);
		// Pause any potential fading
		fader.pause();
	} else {
		// Create new fader
		fader = new alphaAPI(WebDDM_getElement(itemsId), this);
		// Save fader
		this.alphaAPIObjects.set(itemsId, fader);
		// Set initial alpha
		fader.setAlpha(0);
	}
	
	// Set max opacity if it isn't set
	if (!transitionData.has('maxOpacity')) {
		transitionData.set('maxOpacity', 100);
	}

	// If we can't fade, just stop now
	if (!domLib_canFade ||
		(transitionData.get('direction') == 'in' && !visibility) ||
		(transitionData.get('direction') == 'out' && visibility))
	{
		fader.setAlpha(visibility ? transitionData.get('maxOpacity') : 0);
		WebDDM_getElement(itemsId).style.visibility = visibility
			? 'visible'
			: 'hidden';
	}

	// Set inDelay and outDelay if delay is set
	if (transitionData.has('delay')) {
		transitionData.set('inDelay', transitionData.get('delay'));
		transitionData.set('outDelay', transitionData.get('delay'));
	}

	// Set fader configuration
	fader.fadeInDelay = transitionData.get('inDelay');
	fader.fadeOutDelay = transitionData.get('outDelay');
	fader.offsetTime = 0;
	fader.deltaAlpha = transitionData.get('delta');
	
	// Get current alpha
	var currentAlpha = fader.getAlpha();
	// Set start alpha and stop alpha
	if (visibility) {
		fader.startAlpha = transitionData.get('maxOpacity');
		// Start fading
		fader.fadeIn();
	} else {
		fader.stopAlpha = 0;
		// Start fading
		fader.fadeOut();
	}
}; // End WebDDM.prototype.alphaTrans


/**
 * The clip transition system. Uses clip:rect(...,...,...,...) to create
 * the effect of sliding submenus.
 *
 * @package WebDDM
 *
 * @param Hash transitionData ClipTrans transition configuration (items.clipTrans)
 * @param STRING itemsId ID of the item container in question.
 * @param BOOLEAN visibility True to make the item container visible, else false
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.clipTrans = function (transitionData, itemsId, visibility)
{
	var clipper;

	// Initialize the clipper if needed
	if (!this.clipperObjects.has(itemsId)) {
		// Create new clipper
		clipper = new clipAPI(itemsId, this);
		// Save clipper
		this.clipperObjects.set(itemsId, clipper);
	} else {
		// Get clipper
		clipper = this.clipperObjects.get(itemsId);
	}

	// Set clipper configuration
	clipper.duration = transitionData.get('duration');
	clipper.frames = transitionData.get('frames');

	// Set start alpha and stop alpha
	if (visibility) {
		// Get new transition
		var transition;
		if (transitionData.has('transitionIn')) {
			transition = transitionData.get('transitionIn');
		} else if (transitionData.has('transition')) {
			transition = transitionData.get('transition');
		} else {
			transition = 'none';
		}
		// Set new transition
		clipper.transition = transition;
		// Set direction
		clipper.setDirection('in');
	} else {
		// Get new transition
		var transition;
		if (transitionData.has('transitionOut')) {
			transition = transitionData.get('transitionOut');
		} else if (transitionData.has('transition')) {
			transition = transitionData.get('transition');
		} else {
			transition = 'none';
		}
		// Set new transition
		clipper.transition = transition;
		// Set direction
		clipper.setDirection('out');
	}
	// Start fading
	clipper.start();
}; // End WebDDM.prototype.clipTrans


/** 
 * Perform actions when a transition is done.
 *
 * @package WebDDM
 *
 * @param OBJECT domObj DOM object that was transitioned visible/hidden
 * @param BOOLEAN visibilityStatus True for visible, false for hidden
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.transitionCompleted = function (domObj, visibilityStatus)
{
	// Did we make the element invisible?
	if (!visibilityStatus) {
		// Hide element
		domObj.style.visibility = 'hidden';
		// Unhide selects, applets, etc
		domLib_detectCollisions(WebDDM_getElement(domObj.id), true);
	} else {
		// Hide selects, applets, etc
		domLib_detectCollisions(WebDDM_getElement(domObj.id), false);
	}
}; // End WebDDM.prototype.transitionCompleted


/**
 * Reload an item. Should be used right after an item attribute is changed.
 *
 * @package WebDDM
 *
 * @param Hash itemHash Item Hash of the item to reload
 *
 * @return VOID
 *
 * @access PRIVATE
 */
WebDDM.prototype.reloadItem = function (itemHash)
{
	// If there is not a valid item ID, just return right away
	// If there is an item ID, it means that the item HAS been
	// built so it's possible that it's visible. However, if 
	// there is no item ID it's completely impossible that the
	// item is visible.
	if (!itemHash || !itemHash.has('itemId')) {
		return;
	}
	// Now update the current style of the item - remember,
	// the current style is held in the item Hash.
	this.setItemStyle(itemHash, itemHash.get('currentStyle') || '', true);
}; // End WebDDM.prototype.reloadItem


/**
 * Modify attributes, and then reload the item.
 *
 * @package WebDDM
 *
 * @param STRING hashPath Path to the hash that the new attribute's value will go in
 * @param ANY newValue New value of the attribute
 *
 * @return VOID
 *
 * @access PUBLIC
 */
WebDDM.prototype.modifyMenuData = function (hashPath, newValue)
{
	// Evaluation path to hash
	var evalHashPath = 'this.menuData';
	var itemHashPath = 'this.menuData';
	// Attribute name to modify
	var attributeName = '';
	
	// Parse hashPath
	var chunks = hashPath.split('-');
	for (var i = 0; i < chunks.length; i++) {
		var chunk = chunks[i];
		if (typeof(chunks[i+1]) == 'undefined') {
			attributeName = chunk;
		} else {
			evalHashPath += '.get("'+chunk+'")';
			if (chunk == 'items' || !isNaN(chunk)) {
				itemHashPath += '.get("'+chunk+'")';
			}
		}
	}
	
	// Get hash to modify
	eval('var attributeHash = ' + evalHashPath);
	
	// Get item hash
	eval('var itemHash = ' + itemHashPath);
	
	// Modify attribute
	attributeHash.set(attributeName, newValue);
	
	// Reload item
	this.reloadItem(itemHash);
}; // End WebDDM.prototype.modifyMenuData


/**
 * Return value of requested attribute.
 *
 * @package WebDDM
 *
 * @param STRING hashPath Path to the hash that the new attribute's value will go in
 *
 * @return ANY Returns value of passed hashPath value
 *
 * @access PUBLIC
 */
WebDDM.prototype.getMenuData = function (hashPath)
{
	// Evaluation path to hash
	var evalAttributePath = 'this.menuData';
	// Attribute name to modify
	var attributeName = '';
	
	// Parse hashPath
	var chunks = hashPath.split('-');
	for (var i in chunks) {
		evalAttributePath += '.get("'+chunks[i]+'")';
	}
	
	// Get hash to modify
	eval('var attribute = ' + evalAttributePath);
	return attribute;
}; // End WebDDM.prototype.getMenuData


/**
 * Performs actions needed by a WebDDM element.
 *
 * @param OBJECT in_event The event object
 * @param STRING action The action to perform - "mouseover", "click", "mousedown", "mouseup", etc
 * @param STRING itemId The item's ID
 */
WebDDM.prototype.mouseAction = function (in_event, action, itemId)
{
	// Make sure we have a valid in_event (for IE)
	// Sometimes we CAN'T have a valid event, in that event just set it to "false"
	var in_event = (in_event
		? in_event
		: (in_event == 0
			? false
			: (window.event
				? window.event
				: false)));
	
	// Get item hash
	var itemHash = this.getItemHash(itemId);
	
	// Make sure the item hash is valid
	if (!itemHash) {
		return;
	}
	
	// Prevent the event from bubbling
	if (in_event) {
		domLib_cancelBubble(in_event);
	}
	
	// Switch to get the action
	switch (action) {
		default:
			break;
		//
		// Mouseover code
		//
		case 'mouseover':
			// Do not execute mouseover code if we are in the element still
			if (this.mouseoutTimeout == itemId && !this.getMouseoverStatus(itemId)) {
				this.setMouseoverStatus(itemId, true);
				return;
			}
			// -- Submenu opening/closing --
			if (itemHash.has('items')) {
				// Are we opening the menu on onclick, and is this the click event?
				if (itemHash.get('expand_menu').match('click')) {
					// When the mouse rolls over the menu,
					// check if the currently opened menu opens with a click.
					// If it does, close that one and open this one.
					this.checkClickMenu(itemId);
				}
		
				// Are we opening the menu on rollover?
				if (itemHash.get('expand_menu').match('rollover')) {
					// Expand submenu
					var instance = this;
					domLib_setTimeout (function () {
						instance.showMenu(itemHash, itemId);
					}, itemHash.has('openDelay') ? itemHash.get('openDelay') : 0);
				}
			} else if (!itemHash.has('isContainerItem')) {
				// Expand submenu - although technically it doesn't exist. The effect that we
				// want is achieved, though - all the submenus on/above this level are closed.
				this.showMenu(itemHash, itemId);
			}
			// Perform needed tooltip actions
			if (itemHash.has('tooltip')) {		
				var ttcode = '';
				for (var key in itemHash.get('tooltip').elementData) {
					var value = itemHash.get('tooltip').get(key);
					value = (typeof(value) == 'string'
						? evalEscapeString(value)
						: value);
					ttcode += ', "' + key + '", ' + value;
				}
				eval('domTT_activate(WebDDM_getElement("'+itemId+'_item_container"), in_event' + ttcode + ');');
			}

			// Basic mouseover code
			if (this.menuIsOpen(itemId)) {
				this.setItemStyle(itemHash, WebDDM_style_menuopenRollover);
			} else {
				this.setItemStyle(itemHash, WebDDM_style_rollover);
			}
	
			// Open parent menu (basically re-showing all the items, INCLUDING this one -
			// useful if the menu is fading or sliding out and the mouse moves over.)
			// Nice effect, eh?		
			if (itemId != this.containerId && itemHash.get('parentId')) {
				this.showMenu(this.getItemHash(itemHash.get('parentId')), itemHash.get('parentId'));
			}
			
			// Set mouseover status to true
			this.setMouseoverStatus(itemId, true);		
			break;
		// -- End onmouseover --
		//
		// Click/Mouseup code
		//
		case 'click':
			// -- Submenu opening/closing --
			if (itemHash.has('items')) {
				// Are we opening the menu on onclick?
				if (itemHash.get('expand_menu').match('click')) {
					// Is the menu opened or closed?
					if (this.menuIsOpen(itemId) && itemHash.get('contract_menu').match('click')) {
						// Contract submenu
						this.hideMenu(itemHash, false);
					} else {			
						// Expand submenu
						domLib_setTimeout (function (args) {
							args[0].showMenu(itemHash, itemId);
						}, itemHash.has('openDelay') ? itemHash.get('openDelay') : 0, [this]);
					}
				} else if (itemHash.get('contract_menu').match('click')) {
					// The menu does not open on onclick, but it does
					// close on onclick
					this.hideMenu(itemHash, false);
				}
			}
		case 'mouseup':
			// If we are supposed to go to an URL, do that now.
			if (itemHash.has('url')) {
				eval('window.open(' + evalEscapeString(
						itemHash.get('url').replace(/^javascript\:/, 'javascript:void ')) +
						', "' + (itemHash.has('target') ? itemHash.get('target') : '_self') + '"' +
					')');
			}
			// Reset style of the item on onclick
			if (this.menuIsOpen(itemId)) {
				this.setItemStyle(itemHash, WebDDM_style_menuopenRollover);			
			} else {
				this.setItemStyle(itemHash, WebDDM_style_rollover);
			}
			// Don't let Mozilla users select item text
			if (window.getSelection) {
				window.getSelection().removeAllRanges();
			}
			break;
		// -- End onclick/onmouseup --
		//
		// Mousemove code
		//
		case 'mousemove':
			// Don't let Mozilla users select item text
			if (window.getSelection) {
				window.getSelection().removeAllRanges();
			}
			// Perform needed tooltip actions
			if (itemHash.has('tooltip')) {		
				domTT_mousemove(WebDDM_getElement(itemId + '_item_container'), in_event);
			}
			break;
		// -- End mousemove --
		//
		// Mouseout code
		//
		case 'mouseout':
			// Set mouseover status to false
			this.setMouseoverStatus(itemId, false);
			
			if (in_event) {
				var instance = this; 
				this.mouseoutTimeout = itemId;
				domLib_setTimeout(function () {
					instance.mouseoutTimeout = false;
					if (!instance.getMouseoverStatus(itemId)) {
						if (instance.menuIsOpen(itemId)) {
							instance.setItemStyle(itemHash, WebDDM_style_menuopenOut);						
						} else {
							instance.setItemStyle(itemHash, WebDDM_style_out);
						}
						if (itemHash.has('tooltip')) {
							domTT_mouseout(WebDDM_getElement(itemId + '_item_container'), in_event);
						}
						if (itemHash.has('actions')) {
							if (itemHash.get('actions').has(action)) {
								eval(itemHash.get('actions').get(action));
							} else if (itemHash.get('actions').has('on' + action)) {
								eval(itemHash.get('actions').get('on' + action));
							}
						}
					}
				}, 10);
			} else {
				this.mouseoutTimeout = false;
				if (this.menuIsOpen(itemId)) {
					this.setItemStyle(itemHash, WebDDM_style_menuopenOut);
				} else {
					this.setItemStyle(itemHash, WebDDM_style_out);
				}
				if (itemHash.has('tooltip')) {
					domTT_mouseout(WebDDM_getElement(itemId + '_item_container'), in_event);
				}
				if (itemHash.has('actions')) {
					if (itemHash.get('actions').has(action)) {
						eval(itemHash.get('actions').get(action));
					} else if (itemHash.get('actions').has('on' + action)) {
						eval(itemHash.get('actions').get('on' + action));
					}
				}
			}
			break;
		// -- End mouseout --
		//
		// Mousedown code
		//
		case 'mousedown':
			if (action.match(/mousedown/)) {
				if (this.menuIsOpen(itemId)) {
					this.setItemStyle(itemHash, WebDDM_style_menuopenPressed);
				} else {
					this.setItemStyle(itemHash, WebDDM_style_rolloverPressed);
				}
			}
			break;
		// -- End mousedown --
	}

	// Perform custom actions
	if (itemHash.has('actions') && !action.match('mouseout')) {
		if (itemHash.get('actions').has(action)) {
			eval(itemHash.get('actions').get(action));
		} else if (itemHash.get('actions').has('on' + action)) {
			eval(itemHash.get('actions').get('on' + action));
		}
	}
}; // End WebDDM.prototype.mouseAction


/**
 * Get a WebDDM object.
 *
 * @param STRING containerId Container ID of the WebDDM menu we're trying to get.
 *
 * @return OBJECT Returns a WebDDM object.
 *
 * @access PUBLIC
 */
function getWebDDMObject (containerId)
{
	return WebDDMObjects.has(containerId)
		? WebDDMObjects.get(containerId)
		: false;	
} // End function getWebDDMObject


/**
 * Hide all submenus in all menus.
 *
 * @return VOID
 *
 * @access PUBLIC
 */
function WebDDM_hideAllMenus ()
{
	// Loop through all menu objects
	for (var i in WebDDMObjects.elementData)
	{
		// Hide all submenus...
		getWebDDMObject(i).hideAllSubmenus();
	}
} // End function WebDDM_hideAllMenus


/**
 * Preloads images. Assumes ALL command-line arguments are image path names.
 *
 * @return VOID
 *
 * @access PUBLIC
 */
function WebDDM_preloadImages ()
{
	var imgs = [];
	for (var i = 0; i < arguments.length; i++) {
		imgs[i] = document.createElement('img');
		imgs[i].src = arguments[i];
	}
} // End function WebDDM_preloadImages 


/**
 * The float object that controls menu floating.
 *
 * @param STRING domId ID of the DOM object
 * @param INTEGER offsetX Offset on the X axis from the left of the screen
 * @param INTEGER offsetY Offset on the Y axis from the top of the screen
 * @param INTEGER floatSpeed Speed at which floating occurs
 * @param BOOLEAN checkScrolling If set to TRUE, FloatAPI will automatically float the
 *  element to its position when the window is scrolled.
 *
 * @return OBJECT Returns the FloatAPI object
 *
 * @access PRIVATE
 */
function FloatAPI (domId, offsetX, offsetY, floatSpeed, checkScrolling)
{
	// Save the DOM ID for future use.
	this.domId = domId;
	// Save offsets
	this.offsetX = offsetX;
	this.offsetY = offsetY;
	// Save speed
	this.speed = floatSpeed;
	// Special ID for timeouts
	this.timeoutId = 'FloatAPI' + Math.random() + this.domId;
	// Delay (in millisecond) between scroll checks
	this.scrollCheckDelay = 100;
	// Last scroll positions
	this.lastPageXOffset = false;
	this.lastPageYOffset = false;
	
	// Now, set up a timeout to check screen scrolling
	thisObjs.set(this.timeoutId, this);
	if (checkScrolling) {
		domLib_setTimeout('thisObjs.get("'+this.timeoutId+'").checkScrolling();',
			this.scrollCheckDelay);
	}
			
	return this;
} // End function FloatAPI


/**
 * Method that checks if the document has scrolled at
 * all. If it has, float the element to its new position.
 *
 * @package FloatAPI
 *
 * @return VOID
 *
 * @access PRIVATE
 */
FloatAPI.prototype.checkScrolling = function ()
{
	// Get scroll positions
	if (typeof(document.body.scrollTop) != 'undefined') {
		pageYOffsetNow = document.body.scrollTop;
		pageXOffsetNow = document.body.scrollLeft;
	} else {
		pageYOffsetNow = window.pageYOffset;
		pageXOffsetNow = window.pageXOffset;
	}

	// Check - has the document been scrolled since the last
	// check?
	if ((this.lastPageXOffset != pageXOffsetNow) ||
		(this.lastPageYOffset != pageYOffsetNow))
	{
		// Document has scrolled; float element to new position
		if (!this.floating) {
			this.floatElement();
		}
	}

	// Update last positions
	this.lastPageXOffset = pageXOffsetNow;
	this.lastPageYOffset = pageYOffsetNow;
	
	// Timeout to recursively call this method again...
	domLib_setTimeout('thisObjs.get("'+this.timeoutId+'").checkScrolling();',
		this.scrollCheckDelay);
}; // End FloatAPI.prototype.checkScrolling


/**
 * Floats an element to its new position.
 *
 * @package FloatAPI
 *
 * @return VOID
 *
 * @access PRIVATE
 */
FloatAPI.prototype.floatElement = function ()
{
	// Get element
	domEl = WebDDM_getElement(this.domId);
	// Get scroll positions
	if (typeof(document.body.scrollTop) != 'undefined') {
		pageYOffsetNow = document.body.scrollTop;
		pageXOffsetNow = document.body.scrollLeft;
	} else {
		pageYOffsetNow = window.pageYOffset;
		pageXOffsetNow = window.pageXOffset;
	}
	
	// Get position that the element should be at when
	// we're done floating it.
	shouldBePosX = pageXOffsetNow + this.offsetX;
	shouldBePosY = pageYOffsetNow + this.offsetY;
	
	// Get current positions
	currentPosX = parseInt(domEl.style.left);
	currentPosY = parseInt(domEl.style.top);
	
	// Get new positions
	if (currentPosX > shouldBePosX) {
		floatGapX = currentPosX - shouldBePosX;
		newPosX = currentPosX - (floatGapX / (85 - this.speed));
	} else {
		floatGapX = shouldBePosX - currentPosX;
		newPosX = currentPosX + (floatGapX / (85 - this.speed));
	}
	if (currentPosY > shouldBePosY) {
		floatGapY = currentPosY - shouldBePosY;
		newPosY = currentPosY - (floatGapY / (85 - this.speed));
	} else {
		floatGapY = shouldBePosY - currentPosY;
		newPosY = currentPosY + (floatGapY / (85 - this.speed));
	}
	
	if (floatGapX > 0 || floatGapY > 0) {
		// Update position of element if the float gap is still wide
		this.floating = true;
		domEl.style.top = newPosY + 'px';
		domEl.style.left = newPosX + 'px';
		// Set new timeout
		domLib_setTimeout('thisObjs.get("'+this.timeoutId+'").floatElement();',
			(100 - this.speed) / (floatGapY > 25 ? 1 : 3));
	} else {
		// Float gap is narrow; stop floating
		domEl.style.top = shouldBePosY + 'px';
		domEl.style.left = shouldBePosX + 'px';
		this.floating = false;
	}
}; // End FloatAPI.prototype.floatElement


/**
 * clipAPI - Made for WebDDM for sliding/clipping submenus
 *
 * @param STRING elId Element ID to slide
 * @param OBJECT parent The parent WebDDM object
 *
 * @return OBJECT Returns the "this" object
 *
 * @access PRIVATE
 */
function clipAPI (elId, parent)
{
	// Get the element that the element ID corresponds to
	this.element = WebDDM_getElement(elId);
	// Save "parent" WebDDM object
	this.parent = parent;

	// Set default properties
	// Duration (in milliseconds) of effect. Must be set before start() is called.
	this.duration = 500;
	// Number of frames total; must be set before start() is called
	this.frames = 10;
	// Current frame #
	this.currentFrame = false;
	// Transition name.
	this.transition = 'none';
	// Direction of transition, in or out. Must be set before start() is called.
	this.direction = '';
	// Variable that holds the domLib_setTimeout result
	this.clipTimer = false;
	// Starting clip objcet
	this.startClip = false;
	// Object of what the clip WILL be after the current transition is done
	this.targetClip = false;
	// Object of current clip coordinates
	this.currentClip = false;
	// Starting position object
	this.startPos = false;
	// Object of what the position WILL be after the current transition is done
	this.targetPos = false;
	// Object of the current position coordinates
	this.currentPos = false;
	// Last transition (transitionName+direction) completed
	this.lastTransCompleted = '';
	// Original x/y position of the element (used for sliding)
	this.originalPos = {
		'left': parseInt(this.element.offsetLeft),
		'top': parseInt(this.element.offsetTop)
	};
	// Special ID for timeouts
	this.timeoutId = 'clipAPI' + Math.random() + elId;
	// Set the "this" object
	thisObjs.set(this.timeoutId, this);
	
	
	// Return the clipAPI object
	return this;
} // End function clipAPI


/**
 * Starts the clip sliding effect.
 *
 * @package clipAPI
 *
 * @return VOID
 *
 * @access PUBLIC
 */
clipAPI.prototype.start = function ()
{
	// Kill any running timers
	domLib_clearTimeout(this.clipTimer);

	// If transition is set to none or the last transition
	// completed is the same transition we're doing now,
	// just make the element visible/hidden
	if (this.transition == 'none' ||
		this.lastTransCompleted == this.direction + this.transition)
	{
		this.element.style.visibility = (this.direction == 'in'
			? 'visible'
			: 'hidden');
		return;
	}
	
	// Get element height/width
	var elHeight = parseInt(this.element.offsetHeight);
	var elWidth = parseInt(this.element.offsetWidth);
	
	// Get direction
	if (this.direction == 'in') {
		// We are sliding in
		// Initialize clip if needed (if we're doing completely new slide
		// or we haven't before)
		if (!this.startClip || (this.startClip == this.targetClip) || !this.clipTimer) {
			// Are we sliding?
			var sliding = this.transition.match('slide') ? true : false;
			// Update starting clip positions and positions
			var topClipPos, bottomClipPos, leftClipPos, rightClipPos;
			var topPos = this.originalPos.top, leftPos = this.originalPos.left;
			if (this.transition.match(/north(.*?)\-to\-south(.*?)/)) {
				topClipPos = 0;
				bottomClipPos = 0;
				if (sliding) {
					topPos = this.originalPos.top + elHeight;
				}
			} else if (this.transition.match(/south(.*?)\-to\-north(.*?)/)) {
				topClipPos = elHeight;
				bottomClipPos = elHeight;
				if (sliding) {
					topPos = this.originalPos.top - elHeight;
				}
			} else if (this.transition.match('center-to-edges')) {
				topClipPos = elHeight / 2;
				bottomClipPos = elHeight / 2;
			} else if (this.transition.match('center-to-horizontal-edges')) {
				topClipPos = elHeight / 2;
				bottomClipPos = elHeight / 2;
			} else {
				topClipPos = 0;
				bottomClipPos = elHeight;
			}
			if (this.transition.match(/(.*?)west\-to\-(.*?)east/)) {
				leftClipPos = 0;
				rightClipPos = 0;
				if (sliding) {
					leftPos = this.originalPos.left + elWidth;
				}
			} else if (this.transition.match(/(.*?)east\-to\-(.*?)west/)) {
				leftClipPos = elWidth;
				rightClipPos = elWidth;
				if (sliding) {
					leftPos = this.originalPos.left - elWidth;
				}
			} else if (this.transition.match('center-to-edges')) {
				leftClipPos = elWidth / 2;
				rightClipPos = elWidth / 2;
			} else if (this.transition.match('center-to-vertical-edges')) {
				leftClipPos = elWidth / 2;
				rightClipPos = elWidth / 2;
			} else {
				leftClipPos = 0;
				rightClipPos = elWidth;
			}
			// Set starting clip
			this.startClip = {
				'top': topClipPos,
				'right': rightClipPos,
				'bottom': bottomClipPos,
				'left': leftClipPos
			};
			// Set starting position
			this.startPos = {
				'top': topPos,
				'left': leftPos
			};
		}
		// Set the target clip to full
		this.targetClip = {
			'top': 0,
			'right': elWidth,
			'bottom': elHeight,
			'left': 0
		};
		// Set the target position to the original position
		this.targetPos = this.originalPos;
		// Make sure element is visible
		this.element.style.visibility = 'visible';
	} else {
		// We are clipping out
		// Are we sliding?
		var sliding = this.transition.match('slide') ? true : false;
		// Get target clip positions and positions
		var topClipPos, bottomClipPos, leftClipPos, rightClipPos;
		var topPos = this.startPos.top;
		var leftPos = this.startPos.left;
		if (this.transition.match(/north(.*?)\-to\-south(.*?)/)) {
			topClipPos = this.startClip.bottom;
			bottomClipPos = this.startClip.bottom;
			if (sliding) {
				topPos = this.originalPos.top - elHeight;
			}
		} else if (this.transition.match(/south(.*?)\-to\-north(.*?)/)) {
			topClipPos = this.startClip.top;
			bottomClipPos = this.startClip.top;
			if (sliding) {
				topPos = this.originalPos.top + elHeight;
			}
		} else if (this.transition.match('center-to-edges')) {
			topClipPos = elHeight / 2;
			bottomClipPos = elHeight / 2;
		} else if (this.transition.match('center-to-horizontal-edges')) {
			topClipPos = elHeight / 2;
			bottomClipPos = elHeight / 2;
		} else {
			topClipPos = this.startClip.top;
			bottomClipPos = this.startClip.bottom;
		}
		if (this.transition.match(/(.*?)west\-to\-(.*?)east/)) {
			leftClipPos = this.startClip.right;
			rightClipPos = this.startClip.right;
			if (sliding) {
				leftPos = this.originalPos.left - elWidth;
			}
		} else if (this.transition.match(/(.*?)east\-to\-(.*?)west/)) {
			leftClipPos = this.startClip.left;
			rightClipPos = this.startClip.left;
			if (sliding) {
				leftPos = this.originalPos.left + elWidth;
			}
		} else if (this.transition.match('center-to-edges')) {
			leftClipPos = elWidth / 2;
			rightClipPos = elWidth / 2;
		} else if (this.transition.match('center-to-vertical-edges')) {
			leftClipPos = elWidth / 2;
			rightClipPos = elWidth / 2;
		} else {
			leftClipPos = this.startClip.left;
			rightClipPos = this.startClip.right;
		}
		// Set the target clip
		this.targetClip = {
			'top': topClipPos,
			'right': rightClipPos,
			'bottom': bottomClipPos,
			'left': leftClipPos
		};
		// Set the target positions
		this.targetPos = {
			'top': topPos,
			'left': leftPos
		};
	}
	// Slide
	this.slide();
}; // End clipAPI.prototype.start


/**
 * Slide method - slides element a little and then recurses.
 *
 * @package clipAPI
 *
 * @return VOID
 *
 * @acess PRIVATE
 */
clipAPI.prototype.slide = function ()
{
	// Reset the timeout ID
	this.clipTimer = false;
	// Get timeout time
	var timeoutTime = this.duration / this.frames;

	// Update clip properties
	this.currentClip = {};
	var positions = ['top', 'right', 'bottom', 'left'];
	for (var i = 0; i < positions.length; i++) {
		var pos = positions[i];
		if (this.startClip[pos] > this.targetClip[pos]) {
			var delta = (this.startClip[pos] - this.targetClip[pos]) / this.frames;
			this.currentClip[pos] = this.startClip[pos] - (delta * this.currentFrame);
			
			// Correct clip pos
			if (this.currentClip[pos] <= this.targetClip[pos]) {
				this.currentClip[pos] = this.targetClip[pos];
			}
		} else if (this.targetClip[pos] > this.startClip[pos]) {
			var delta = (this.targetClip[pos] - this.startClip[pos]) / this.frames;
			this.currentClip[pos] = this.startClip[pos] + (delta * this.currentFrame);
			
			// Correct clip pos
			if (this.currentClip[pos] >= this.targetClip[pos]) {
				this.currentClip[pos] = this.targetClip[pos];
			}
		} else {
			this.currentClip[pos] = this.startClip[pos];
		}
	}
	
	// Update position properties
	this.currentPos = {};
	var positions = ['top', 'left'];
	for (var i in positions) {
		// Get position name
		var pos = positions[i];
		// Is the current position above the target position?
		var position;
		if (this.startPos[pos] > this.targetPos[pos]) {
			// Slide more
			var delta = (this.startPos[pos] - this.targetPos[pos]) / this.frames;
			this.currentPos[pos] = this.startPos[pos] - (delta * this.currentFrame);

			// <HACK>
			// Fix position.
			position = this.currentPos[pos];
			if (this.direction == 'out' && pos == 'top') {
				position += 4;
			}
			// </HACK>

			// Should we jump to target?
			if (position < this.targetPos[pos]) {
				// Jump to target
				position = this.currentPos[pos] = this.targetPos[pos];
			}
			// Fix position otherwise
			if (position > this.startPos[pos]) {
				position = this.currentPos[pos] = (position - (position - this.startPos[pos]));
			}
		}
		// Is the target position above the current position?
		else if (this.targetPos[pos] > this.startPos[pos]) {
			// Slide more
			var delta = (this.targetPos[pos] - this.startPos[pos]) / this.frames;
			this.currentPos[pos] = this.startPos[pos] + (delta * this.currentFrame);
			
			// <HACK>
			// Fix position.
			position = this.currentPos[pos];
			if (pos == 'top' && (this.currentFrame > 0 || this.direction == 'in')) {
				position += 4;
			}
			// </HACK>

			// Should we jump to target?
			if (position > this.targetPos[pos]) {
				// Jump to target
				position = this.currentPos[pos] = this.targetPos[pos];
			}
			// Fix position otherwise
			if (position < this.startPos[pos]) {
				position = this.currentPos[pos] = (position + (this.startPos[pos] - position));
			}
		}
		// Start and target position are the same
		else {
			this.currentPos[pos] = position = this.startPos[pos];
		}

		// Set element's style position
		this.element.style[pos] = position + 'px';
	}

	// Set clip
	this.setClip(this.currentClip.top, this.currentClip.right,
		this.currentClip.bottom, this.currentClip.left);

	// Recurse
	if (this.currentFrame <= this.frames) {
		// Do a timeout
		this.clipTimer = domLib_setTimeout('thisObjs.get("'+this.timeoutId+'").slide()',
			timeoutTime);
		// Increment frame
		this.currentFrame++;
		// Reset last transition completed (since nothing is complete now!)
		this.lastTransCompleted = '';
	} else {
		// Set the last transition completed
		this.lastTransCompleted = this.direction + this.transition;
		// Send transition done message to parent WebDDM object
		this.parent.transitionCompleted(this.element, this.direction == 'out' ? false : true);
	}
}; // End clipAPI.prototype.slide


/**
 * Set clip property of element.
 *
 * @package clipAPI
 *
 * @param INTEGER top Top position of clipping
 * @param INTEGER right Right position of clipping
 * @param INTEGER bottom Bottom position of clipping
 * @param INTEGER left Left position of clipping
 */
clipAPI.prototype.setClip = function (top, right, bottom, left)
{
	// Set clip
	this.element.style.clip = 'rect('+top+'px,'+right+'px,'+bottom+'px,'+left+'px)';
}; // End clipAPI.prototype.setClip


/**
 * Sets direction of sliding. It's good to use this so sliding is
 * smooth and frames don't jump.
 *
 * @package clipAPI
 *
 * @param STRING dir Direction to change to: "in" or "out"
 *
 * @return VOID
 *
 * @access PUBLIC
 */
clipAPI.prototype.setDirection = function (dir)
{
	// Kill any running timers
	domLib_clearTimeout(this.clipTimer);

	// Reset the starting position
	this.startPos = this.targetPos = this.currentPos;
	// Reset the starting clip
	this.startClip = this.currentClip;
	// Reset the frame count
	this.currentFrame = 0;
	// Set direction
	this.direction = dir;
}; // End clipAPI.prototype.changeDirection


/** $Id: alphaAPI.js 1588 2004-11-13 22:46:35Z dallen $ */
/**
 * alphaAPI
 * Original Author: chrisken
 * Original Url: http://www.cs.utexas.edu/users/chrisken/alphaapi.html
 *
 * Modified by dallen and Josh
 */
function alphaAPI(element, fadeInDelay, fadeOutDelay, startAlpha, stopAlpha, offsetTime, deltaAlpha)
{
	this.element = typeof(element) == 'object' ? element : document.getElementById(element);
	this.fadeInDelay = fadeInDelay || 40;
	this.fadeOutDelay = fadeOutDelay || this.fadeInDelay;
	this.startAlpha = startAlpha;
	this.stopAlpha = stopAlpha;
	// make sure a filter exists so an error is not thrown
	if (typeof(this.element.filters) == 'object')
	{
		if (typeof(this.element.filters.alpha) == 'undefined')
		{
			this.element.style.filter += 'alpha(opacity=100)';
		}
	}

	this.offsetTime = (offsetTime || 0) * 1000;
	this.deltaAlpha = deltaAlpha || 10;
	this.timer = null;
	this.paused = false;
	this.started = false;
	this.cycle = false;
	this.command = function() {};
    return this;
} // End function alphaAPI


/**
 * Sets "cycle" property.
 */
alphaAPI.prototype.repeat = function(repeat)
{
    this.cycle = repeat ? true : false;
} // End function alphaAPI.prototype.repeat()


/**
 * Adds passed deltaAlpha to current alpha.
 */
alphaAPI.prototype.setAlphaBy = function(deltaAlpha)
{
    this.setAlpha(this.getAlpha() + deltaAlpha);
} // End function alphaAPI.prototype.setAlphaBy()


/**
 * Switch between stopped/paused/unpaused statuses.
 */
alphaAPI.prototype.toggle = function()
{
    if (!this.started)
    {
        this.start();
    }
    else if (this.paused)
    {
        this.unpause();
    }
    else
    {
        this.pause();
    }
} // End function alphaAPI.prototype.toggle()


/**
 * Sets a timeout.
 */
alphaAPI.prototype.timeout = function(command, delay)
{
    this.command = command;
    this.timer = setTimeout(command, delay);
} // End function alphaAPI.prototype.timeout()


/**
 * Set the alpha of an element.
 */
alphaAPI.prototype.setAlpha = function(opacity)
{
    if (typeof(this.element.filters) == 'object')
    {
        this.element.filters.alpha.opacity = opacity;
		return;
    }
    else if (this.element.style.setProperty)
    {
    	// css3
        this.element.style.setProperty('opacity', opacity / 100, '');
		// handle the case of mozilla < 1.7
        this.element.style.setProperty('-moz-opacity', opacity / 100, '');
		// handle the case of old kthml
        this.element.style.setProperty('-khtml-opacity', opacity / 100, '');
    }
} // End function alphaAPI.prototype.setAlpha()


/**
 * Returns current alpha status of element.
 */
alphaAPI.prototype.getAlpha = function()
{
    if (typeof(this.element.filters) == 'object')
    {
        return this.element.filters.alpha.opacity;
    }
    else if (this.element.style.getPropertyValue)
    {
		var opacityValue = this.element.style.getPropertyValue('opacity');
		// handle the case of mozilla < 1.7
		if (opacityValue == '')
		{
			opacityValue = this.element.style.getPropertyValue('-moz-opacity');
		}

		// handle the case of old khtml
		if (opacityValue == '')
		{
			opacityValue = this.element.style.getPropertyValue('-khtml-opacity');
		}

        return opacityValue * 100;
    }

    return 100;
} // End function alphaAPI.prototype.getAlpha()


/**
 * Starts fading.
 */
alphaAPI.prototype.start = function()
{
    this.started = true;
    this.setAlpha(this.startAlpha);
    // determine direction
    if (this.startAlpha > this.stopAlpha)
    {
        var instance = this;
        this.timeout(function() { instance.fadeOut(); }, this.offsetTime);
    }
    else
    {
        var instance = this;
        this.timeout(function() { instance.fadeIn(); }, this.offsetTime);
    }
} // End function alphaAPI.prototype.start()


/**
 * Stops fading.
 */
alphaAPI.prototype.stop = function()
{
    this.started = false;
    this.setAlpha(this.stopAlpha);
    this.stopTimer();
    this.command = function() {};
} // End function alphaAPI.prototype.stop()


/**
 * Resets the fader to its original settings.
 */
alphaAPI.prototype.reset = function()
{
    this.started = false;
    this.setAlpha(this.startAlpha);
    this.stopTimer();
    this.command = function() {};
} // End function alphaAPI.prototype.reset()


/**
 * Freeze fading.
 */
alphaAPI.prototype.pause = function()
{
    this.paused = true;
    this.stopTimer();
} // End function alphaAPI.prototype.pause()


/**
 * Unpauses fader and commences to fade in/out element.
 */
alphaAPI.prototype.unpause = function()
{
    this.paused = false;
    if (!this.started)
    { 
        this.start();
    }
    else
    {
        this.command(); 
    }
} // End function alphaAPI.prototype.unpause()


/**
 * Stops fading in/out.
 */
alphaAPI.prototype.stopTimer = function()
{
    clearTimeout(this.timer);
    this.timer = null;
} // End function alphaAPI.prototype.stopTimer()


/**
 * alphaAPI object to fade out elements. 
 */
alphaAPI.prototype.fadeOut = function()
{
    this.stopTimer();
    if (this.getAlpha() > this.stopAlpha)
    {
        this.setAlphaBy(-1 * this.deltaAlpha);
        var instance = this;
        this.timeout(function() { instance.fadeOut(); }, this.fadeOutDelay);
    }
    else
    {
        if (this.cycle)
        {
            var instance = this;
            this.timeout(function() { instance.fadeIn(); }, this.fadeInDelay);
        }
        else
        {
			this.element.style.visibility = 'hidden';
            this.started = false;
        }
    }
} // End function alphaAPI.prototype.fadeOut()


/**
 * alphaAPI object to fade in elements. 
 */
alphaAPI.prototype.fadeIn = function()
{
    this.stopTimer();
    if (this.getAlpha() < this.startAlpha)
    {
        this.setAlphaBy(this.deltaAlpha);
        var instance = this;
        this.timeout(function() { instance.fadeIn(); }, this.fadeInDelay);
    }
    else
    {
        if (this.cycle)
        {
            var instance = this;
            this.timeout(function() { instance.fadeOut(); }, this.fadeOutDelay);
        }
        else
        {
            this.started = false;
        }
    }
} // End function alphaAPI.prototype.fadeIn()
