/*
 * Div: an API library for cross-browser dynamic HTML
 * $Id: div.js,v 1.44 2003/10/24 04:19:43 scott 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 = null;

	if(document.getElementById)
		this.alias = document.getElementById(this.id).id;
	else if(document.all) // IE 4.x only
		this.alias = new String("document.all." + this.id);
	else if(document.layers) // NS 4.x only
		this.alias = findLayerAlias(this.id);

	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);
	
/*
 * 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 = null;

	if(document.getElementById)
		pid = this.documentObject.parentNode.id;
	else 
	{	if(document.all)
			pid = eval(this.alias + ".parentElement.id");
		else if(document.layers)
			pid = eval(this.alias + ".parentLayer.id");
	}

	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(viz)
{	this.styleObject.visibility = (document.layers) ?
		((viz) ? "show" : "hide") : ((viz) ? "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()
{	if(document.getElementById)
	{	with(this.documentObject)
		{	if(Div.geckoPosBug && parentNode)
				return (offsetLeft - parentNode.offsetLeft);
			else
				return offsetLeft;
		}		
	}
	else if(document.all)
		return this.styleObject.pixelLeft;
	else if(document.layers)
		return this.styleObject.left;

	return -1;
}

/*
 * 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))
	{	if(document.getElementById)
			x = new String(x + "px");
	
		this.styleObject.left = 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()
{	if(document.getElementById)
	{	with(this.documentObject)
		{	if(Div.geckoPosBug && parentNode)
				return (offsetTop - parentNode.offsetTop);
			else
				return offsetTop;
		}		
	}
	else if(document.all)
		return this.styleObject.pixelTop;
	else if(document.layers)
		return this.styleObject.top;

	return -1;
}

/*
 * 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))
	{	if(document.getElementById)
			y = new String(y + "px");

		this.styleObject.top = 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 width of this div, in pixels.
 */
Div.prototype.getWidth = function()
{	if(document.getElementById)
		return this.documentObject.offsetWidth;
	else if(document.all)
	{	return (this.styleObject.pixelWidth)
			? this.styleObject.pixelWidth
				: eval(this.alias + ".clientWidth");
	}
	else if(document.layers)
	{	with(this.documentObject)
		{	if(width) return width;
		}
	}

	return -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 the height of this div, in pixels.
 */
Div.prototype.getHeight = function()
{	if(document.getElementById)
		return this.documentObject.offsetHeight;
	else if(document.all)
	{	return (this.styleObject.pixelHeight)
			? this.styleObject.pixelHeight
				: eval(this.alias + ".clientHeight");
	}
	else if(document.layers)
	{	with(this.documentObject)
		{	if(height) return height;
		}
	}
		
	return -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 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()
{	with(this.styleObject)
	{	// clip could be defined but null in Mac IE 4.5
		if(clip && clip != null)
		{	if(document.layers)
			{	return new Clip(clip.top, clip.right, clip.bottom, clip.left);		
			}
			else if(length > 0)
				return Clip.parseClip(clip);
		}
	}

	return null;
}

/*
 * 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 == null) ? -1 : (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 == null) ? -1 : (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)
{	if(typeof c == "string")
		c = Clip.parseClip(c);
	
	if(document.getElementById || document.all)
		this.styleObject.clip = c.toCSSString();
	else
	{	with(this.documentObject.clip)
		{	top = c.top;
			right = c.right;
			bottom = c.bottom;
			left = c.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()
{	if(document.all)
		return eval(this.alias + ".innerHTML");
	else
	{	with(this.documentObject)
		{	if(innerHTML) return innerHTML;
		}
	}

	return 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.layers) ?
		this.documentObject.document.forms : 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.layers)
		? this.documentObject.document.images : 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.layers)
		? this.documentObject.document.fgColor : this.styleObject.color;
}

/*
 * 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.layers)
			this.documentObject.document.fgColor = c;
		else
			this.styleObject.color = c;
	}
}

/*
 * Gets the background color of this div, if any is defined.
 */
Div.prototype.getBackgroundColor = function()
{	if(document.layers)
	{	with(this.documentObject)
		{	if(document.bgColor)
				return document.bgColor;
		}
	}
	else
		return this.styleObject.backgroundColor;
}

/*
 * Sets the background color of this div.
 */
Div.prototype.setBackgroundColor = function(c)
{	if(c && c != null)
	{	if(document.layers)
			this.documentObject.document.bgColor = c;
		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()
{	if(document.layers)
	{	with(this.documentObject)
		{	if(document.background)
				return document.background.src;
		}
	}
	else
		return this.styleObject.backgroundImage;
}

/*
 * 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.layers) 
		{	with(this.documentObject.document)
			{	if(!background || background == null)
					background = new Image();
				
				background.src = strImgName;
			}
		}
		else
			this.styleObject.backgroundImage = "url(" + 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 + ")";
}

/*
 * Gets a string representation of this clip.
 */
Clip.prototype.toString = function()
{	return "[object Clip]";
}

