/*
 * Div: an API library for cross-browser dynamic HTML
 * $Id: div.js,v 1.1 2004/08/03 17:29:40 sstoianov Exp $
 * Copyright (C) 2001-2003 Scott Martin (scott@coffeeblack.org)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, it is available from the Free Software
 * Foundation, Inc. at http://www.gnu.org/copyleft/lesser.html or in writing at
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Constructs a Div object using the div's ID in the html document.
 * 
 * The div whose ID is supplied as the argument must be positioned-- it must
 * have a style class or ID defined for it which contains the attribute
 * 'position,' either 'absolute' or 'relative.'
 *
 * For Netscape 4.x browsers, whose document.layers object model
 * forms a hierarchical tree, the constructor traverses the object tree in
 * order to find the appropriate layer object.
 */
function Div(divId)
{	this.id = divId;
	this.alias = (document.all && !document.getElementById)
		? new String("document.all." + this.id)
			: (document.getElementById) ? document.getElementById(this.id).id
				: (document.layers) ? findLayerAlias(this.id) : null;
	this.styleAlias = (document.getElementById || document.all)
		? this.alias + ".style" : this.alias;
	this.documentObject = (document.getElementById)
		? document.getElementById(this.id) : eval(this.alias);
	this.styleObject = (document.getElementById)
		? this.documentObject.style : eval(this.styleAlias);

	// for Netscape 4.x: finds a nested layer object
	function findLayerAlias(name, docAlias)
	{	var i, layer, docLayers, layerAlias;
		if(!docAlias) docAlias = "document";
		docLayers = eval(docAlias + ".layers");
		for(i = 0; i < docLayers.length; i++) 
		{	layerAlias = docAlias + ".layers." + docLayers[i].name;
			layer = eval(layerAlias);
			if(layer.name == name) return layerAlias;
			if(layer.document.layers.length > 0) 
			{	layerAlias = findLayerAlias(name, layerAlias + ".document");
				if(layerAlias != null) return layerAlias;
			}
		}
		return null;
	}
}

/*
 * This flag indicates the presence of a bug in the way the Gecko engine
 * reported an element's position before version 20010802.
 */
Div.geckoPosBug = (navigator.product && navigator.product == "Gecko" 
	&& navigator.productSub && parseInt(navigator.productSub) < 20010802);
	

/*
 * Gets this div's ID, as a string. This will be the same as the argument
 * passed into the constructor.
 */
Div.prototype.getId = function()
{	return this.id;
}

/*
 * Gets this Div's alias, as a string. A Div's alias is the name of the
 * object representing this html div element in the document. For example,
 * a div named "myDiv" will have an alias "document.layers.myDiv" for NS 4
 * but an alias "document.all.myDiv" for IE 4.
 */
Div.prototype.getAlias = function()
{	return this.alias;
}

/*
 * Gets this Div's style alias, as a string. A Div's style alias is the name of
 * this html div element's style in the document. For example,
 * a div named "myDiv" will have an alias "document.layers.myDiv" for NS 4
 * but an alias "document.all.myDiv.style" for IE 4.
 */
Div.prototype.getStyleAlias = function()
{	return this.styleAlias;
}

/*
 * Gets the object in the html document which is referenced this Div's alias.
 */
Div.prototype.getDocumentObject = function()
{	return this.documentObject;
}

/*
 * Gets the object in the html document which is referenced this
 * Div's style alias.
 */
Div.prototype.getStyleObject = function()
{	return this.styleObject;
}

/*
 * If this div is nested inside another div, gets this div's parent as an
 * object of type Div. If this div is a top-level element (its parent is the
 * element body), returns null.
 */
Div.prototype.getParentDiv = function()
{	var pid = (document.getElementById) ? this.documentObject.parentNode.id 
		: (document.all) ? eval(this.alias + ".parentElement.id")
			: (document.layers) ? eval(this.alias + ".parentLayer.id")
				: null;

	return (pid && pid != null) ? new Div(pid) : null;
}

/*
 * Tests whether this div is visible.
 */
Div.prototype.isVisible = function()
{	with(this)
	{	if(styleObject.visibility)
		{	var vStr = styleObject.visibility;
			
			// default value for NS 4 is 'inherit', for DOM browsers, 
			// the default value is an empty string-- check parent's visibility
			if(vStr == "inherit" || vStr == null || vStr.length == 0) 
			{	var pd = getParentDiv();
				return (pd != null && pd.isVisible());
			}
			
			return (vStr == "visible" || vStr == "show");
		}

		return true;
	}
}

/*
 * Sets this div's visiblility to the value of the boolean argument.
 */
Div.prototype.setVisible = function(bool)
{	this.styleObject.visibility = (document.layers) ?
		((bool) ? "show" : "hide") : ((bool) ? "visible" : "hidden");
}

/*
 * Gets the current pixel position of this div's left edge. If the div is
 * nested within another div, the number returned represents the distance
 * relative to the position of the div inside which it resides.
 */
Div.prototype.getLeft = function()
{	return (document.getElementById)
		? (document.all || !Div.geckoPosBug || !this.documentObject.parentNode)
			? this.documentObject.offsetLeft
				: (this.documentObject.offsetLeft -
					this.documentObject.parentNode.offsetLeft)
					: (document.all) ? this.styleObject.pixelLeft
						: this.styleObject.left;
}

/*
 * Sets the pixel position of this div's left edge.
 * The argument supplied must be a number.
 */
Div.prototype.setLeft = function(x)
{	if(!isNaN(x)) this.styleObject.left = (document.getElementById)
		? new String(x + "px") : x;
}

/*
 * Gets the current pixel position of this div's top edge. If the div is
 * nested within another div, the number returned represents the distance
 * relative to the position of the div inside which it resides.
 */
Div.prototype.getTop = function()
{	return (document.getElementById)
		? (document.all || !Div.geckoPosBug || !this.documentObject.parentNode)
			? this.documentObject.offsetTop
				: (this.documentObject.offsetTop -
					this.documentObject.parentNode.offsetTop)
					: (document.all) ? this.styleObject.pixelTop : this.styleObject.top;
}

/*
 * Sets the pixel position of this div's top edge.
 * The argument supplied must be a number.
 */
Div.prototype.setTop = function(y)
{	if(!isNaN(y)) this.styleObject.top = (document.getElementById)
		? new String(y + "px") : y;
}

/*
 * Gets the current pixel position of this div's right edge. If the div is
 * nested within another div, the number returned represents the distance
 * relative to the position of the div inside which it resides.
 */
Div.prototype.getRight = function()
{	with(this)	
	{	return (getLeft() + getWidth());
	}
}

/*
 * Sets the pixel position of this div's right edge.
 * The argument supplied must be a number.
 */
Div.prototype.setRight = function(x)
{	if(!isNaN(x)) 
	{	with(this)	
		{	setLeft(x - getWidth());
		}
	}
}

/*
 * Gets the current pixel position of this div's bottom edge. If the div is
 * nested within another div, the number returned represents the distance
 * relative to the position of the div inside which it resides.
 */
Div.prototype.getBottom = function()
{	with(this)
	{	return (getTop() + getHeight());
	}
}

/*
 * Sets the pixel position of this div's bottom edge.
 * The argument supplied must be a number.
 */
Div.prototype.setBottom = function(y)
{	if(!isNaN(y))
	{	with(this)
		{	setTop(y - getHeight());
		}
	}
}

/*
 * Gets the height of this div, in pixels.
 */
Div.prototype.getHeight = function()
{	return (document.getElementById) ? this.documentObject.offsetHeight
		: (document.all) ? (this.styleObject.pixelHeight)
			? this.styleObject.pixelHeight
				: eval(this.alias + ".clientHeight") : this.styleObject.clip 
					? (this.styleObject.clip.bottom - this.styleObject.clip.top)
						: (this.documentObject.height) ? this.documentObject.height : -1;
}

/*
 * Sets the pixel height of this div. 
 * The argument supplied must be a number.
 */
Div.prototype.setHeight = function(h)
{	if(!isNaN(h))
	{	if(document.getElementById)
			this.styleObject.height = new String(h + "px");
		else if(document.all)
			this.styleObject.pixelHeight = h;
		else this.styleObject.clip.bottom += (h - this.getHeight());
	}
}

/*
 * Gets the width of this div, in pixels.
 */
Div.prototype.getWidth = function()
{	return (document.getElementById) ? this.documentObject.offsetWidth
		: (document.all) ? (this.styleObject.pixelWidth)
			? this.styleObject.pixelWidth
				: eval(this.alias + ".clientWidth") : this.styleObject.clip 
					? (this.styleObject.clip.right - this.styleObject.clip.left)
						: (this.documentObject.width) ? this.documentObject.width : -1;
}

/*
 * Sets the pixel width of this div. 
 * The argument supplied must be a number.
 */
Div.prototype.setWidth = function(w)
{	if(!isNaN(w))
	{	if(document.getElementById)
			this.styleObject.width = new String(w + "px");
		else if(document.all)
			this.styleObject.pixelWidth = w;
		else this.styleObject.clip.right = w;
	}
}

/*
 * Gets this div's clipping region, as an object of type Clip.
 * If no clipping region has been defined for this div, the object returned
 * represents a clip with the div's width and height as its dimentions.
 */
Div.prototype.getClip = function()
{	// clip could be defined but null in Mac IE 4.5
	if(this.styleObject.clip && this.styleObject.clip != null)
	{	if((document.getElementById || document.all)
			&& (this.styleObject.clip.length > 0))
			return Clip.parseClip(this.styleObject.clip);
		else
		{	with(this.styleObject.clip)
			{	return new Clip(top, right, bottom, left);
			}
		}
	}
	else 
	{	with(this)
		{	return new Clip(0, getWidth(), getHeight(), 0);
		}
	}
}

/*
 * Gets the value of the top edge of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipTop = function()
{	var cl = this.getClip();
	return cl.top;
}

/*
 * Gets the value of the right edge of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipRight = function()
{	var cl = this.getClip();
	return cl.right;
}

/*
 * Gets the value of the bottom edge of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipBottom = function()
{	cl = this.getClip();
	return cl.bottom;
}

/*
 * Gets the value of the left edge of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipLeft = function()
{	var cl = this.getClip();
	return cl.left;
}

/*
 * Gets the value of the height of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipHeight = function()
{	var cl = this.getClip();
	return (cl.bottom - cl.top);
}

/*
 * Gets the value of the width of this div's clipping region,
 * in number of pixels.
 */
Div.prototype.getClipWidth = function()
{	var cl = this.getClip();
	return (cl.right - cl.left);
}

/*
 * Sets this div's clipping region. The argument supplied can either be an
 * object of type Clip or a string from which a Clip object will be parsed.
 * If the argument is a string, it must be in the format required by CSS clip
 * attributes (e.g., "rect(0px, 22px, 104px, 0px)").
 */
Div.prototype.setClip = function(c)
{	var clipObj = (typeof c == "string") ? Clip.parseClip(c) : c;
	
	if(document.getElementById || document.all)
		this.styleObject.clip = clipObj.toCSSString();
	else
	{	with(this.documentObject.clip)
		{	top = clipObj.top;
			right = clipObj.right;
			bottom = clipObj.bottom;
			left = clipObj.left;
		}
	}
}

/*
 * Moves this div to the supplied x and y pixel coordinates.
 */
Div.prototype.moveTo = function(x, y)
{	with(this)
	{	setLeft(x);
		setTop(y);
	}
}

/*
 * Moves this div by the supplied change in x and y pixel coordinates.
 */
Div.prototype.moveBy = function(dx, dy)
{	with(this)
	{	moveTo(getLeft() + dx, getTop() + dy);
	}
}

/*
 * Gets the z-index of this div, if defined. If no z-index is defined,
 * returns -1.
 */
Div.prototype.getZIndex = function()
{	var zi = parseInt(this.styleObject.zIndex);
	return (isNaN(zi)) ? -1 : zi;
}

/*
 * Sets this div's z-index.
 * The argument supplied must be a number.
 */
Div.prototype.setZIndex = function(z)
{	if(!isNaN(z)) this.styleObject.zIndex = z;
}

/*
 * Tests whether this div is above (has a higher z-index than) another div.
 * The argument supplied must be an object of type Div. 
 */
Div.prototype.isAbove = function(aDiv)
{	return (aDiv && aDiv != null && this.getZIndex() > aDiv.getZIndex());
}

/*
 * Tests whether this div is below (has a lower z-index than) another div.
 * The argument supplied must be an object of type Div. 
 */
Div.prototype.isBelow = function(aDiv)
{	return (aDiv && aDiv != null && this.getZIndex() < aDiv.getZIndex());
}

/*
 * Moves this div above another div. Gets the z-index of the other div, then
 * sets this div's z-index to that value plus one.
 * The argument supplied must be an object of type Div.
 */
Div.prototype.moveAbove = function(aDiv)
{	if(aDiv && aDiv != null)
		this.setZIndex(aDiv.getZIndex() + 1);
}

/*
 * Moves this div below another div. Gets the z-index of the other div, then
 * sets this div's z-index to that value minus one.
 * The argument supplied must be an object of type Div.
 */
Div.prototype.moveBelow = function(aDiv)
{	if(aDiv && aDiv != null)
		this.setZIndex(aDiv.getZIndex() - 1);
}

/*
 * Gets the textual content of this div. For Netscape 4, returns an empty
 * string until something is written to the div using setContent().
 */
Div.prototype.getContent = function()
{	return (document.all) ? eval(this.alias + ".innerHTML") 
		: (this.documentObject.innerHTML) ? this.documentObject.innerHTML
			: new String("");
}

/*
 * Sets the textual content of this div. For Netscape 4, all formatting
 * information is lost. A workaround is to use <span style= tags around
 * the content.
 */
Div.prototype.setContent = function(str)
{	if(document.all)
		eval(this.alias + ".innerHTML = str;");
	else
	{	// even if NS 4, store the text as innerHTML for
		// later retrieval by getContent()	
		this.documentObject.innerHTML = str;

		if(document.layers)
		{	with(this.documentObject.document)
			{	open();
				write(this.getContent());
				close();
			}
		}
	}
}

/*
 * Gets an object of type Form (identified by frmName) that resides inside
 * this div in the document hierarchy.
 */
Div.prototype.getForm = function(frmName)
{	var frmArray = (document.getElementById || document.all)
		? document.forms : this.documentObject.document.forms;
	return (frmArray != null && frmArray.length > 0) ? frmArray[frmName] : null;
}

/*
 * Gets an object of type Image (identified by imgName) that resides inside
 * this div in the document hierarchy.
 */
Div.prototype.getImage = function(imgName)
{	var imgArray = (document.getElementById || document.all)
		? document.images : this.documentObject.document.images;
	return (imgArray != null && imgArray.length > 0) ? imgArray[imgName] : null;
}

/*
 * Gets the color of this div, if any is defined. This value represents
 * the foreground text color of any textual content inside this div.
 */
Div.prototype.getColor = function()
{	return (document.getElementById || document.all)
		? this.styleObject.color : this.documentObject.document.fgColor;
}

/*
 * Sets the color of this div. This value represents the foreground text color
 * of any textual content inside this div.
 */
Div.prototype.setColor = function(c)
{	if(c && c != null)
	{	if(document.getElementById || document.all)
			this.styleObject.color = c;
		else this.documentObject.document.fgColor = c;
	}
}

/*
 * Gets the background color of this div, if any is defined.
 */
Div.prototype.getBackgroundColor = function()
{	return (document.getElementById || document.all)
		? this.styleObject.backgroundColor 
			: (this.documentObject.document.bgColor)
				? this.documentObject.document.bgColor
					: new String("");
}

/*
 * Sets the background color of this div.
 */
Div.prototype.setBackgroundColor = function(c)
{	if(c && c != null)
	{	if(document.layers)
			this.documentObject.document.bgColor = 'red';
		else
		{	// fix for weird bug in IE where toString() has to be called first
			if(document.getElementById && document.all)
				c = c.toString();

			this.styleObject.backgroundColor = c;
		}
	}
}

/*
 * Gets this div's background image, if any is defined.
 */
Div.prototype.getBackgroundImage = function()
{	return (document.getElementById || document.all)
		? this.styleObject.backgroundImage
			: this.documentObject.document.background
				? this.documentObject.document.background.src
					: new String("");
}

/*
 * Sets this div's background image. The argument supplied can be either an
 * object of type Image or a string representing an image's source.
 */
Div.prototype.setBackgroundImage = function(img)
{	if(img && img != null)	
	{	strImgName = (img.constructor == Image) ? img.src : img;
	
		if(document.getElementById || document.all)
			this.styleObject.backgroundImage = "url(" + strImgName + ")";
		else 
		{	if(!this.documentObject.document.background
				|| this.documentObject.document.background == null)
			{	this.documentObject.document.background = new Image();
			}
			
			this.documentObject.document.background.src = strImgName;
		}
	}
}

/*
 * Gets a string representation of this Div.
 */
Div.prototype.toString = function()
{	return "[object Div]";
}

/*
 * Constructs a Clip object from 4 pixel distance values representing 
 * its top, right, bottom, and left edges.
 */
function Clip(top, right, bottom, left)
{	// calling parseInt() strips off the trailing "px", if any
	this.top = parseInt(top);
	this.right = parseInt(right);
	this.bottom = parseInt(bottom);
	this.left = parseInt(left);
}

/*
 * Parses a clip object from a CSS clip string.
 * Both commas and spaces are supported as separator tokens for numeric clip
 * values, so that
 * 	rect(0 50 100 0)
 * and
 * 	rect(0, 50, 100, 0)
 * are both treated the same. Also, this method functions the same whether or
 * not the trailing "px" is present after the clip values.
 */
Clip.parseClip = function(strClip)
{	if(strClip && strClip != null)
	{	var spi = strClip.indexOf("("), epi = strClip.indexOf(")");
		if(spi != -1 && epi != -1 && spi < epi)
		{	// store variable for compatability with Mac NS 4.06 object bug
			var spl = new String(strClip.substring(spi + 1, epi));
			var tok = new String((spl.indexOf(",") == -1) ? " " : ",");
			var vals = spl.split(tok);
			if(vals.length == 4)
				return new Clip(vals[0], vals[1], vals[2], vals[3]);
		}
	}

	return null;
}

/*
 * Gets a CSS clip string representation of this Clip. For all DOM2 and
 * Netscape 4.x browsers, the pixel values ill have trailing "px" strings,
 * for IE4 they will just have numeric values.
 */
Clip.prototype.toCSSString = function()
{	strPx = (document.getElementById || document.layers) ? "px" : "";
	return "rect(" + this.top + strPx + ", " + this.right + strPx + ", " +
		this.bottom + strPx + ", " + this.left + strPx + ")";
}

Clip.prototype.toString = function()
{	return "[object Clip]";
}
