
//------------------------------------------------------------------------
// Package: Structures
// The various structures used by the system.
// 
// Topic: Dependencies
// - <DOM Library>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class: Legato_Structure_Color
// Handles a single color.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Color()
// Class constructor.
//
// Parameters:
//     RGB - A six character string of Red, Green and Blue values, eg: "FF2345".
//     
//     OR
//
//     R - Red value, 0 - 255.
//     G - Green value, 0 - 255.
//     B - Blue value, 0 - 255.
//------------------------------------------------------------------------
function Legato_Structure_Color()
{

	// What type of argument was passed in?
	if ( arguments.length == 1 && (typeof arguments[0] == "string" || arguments[0] instanceof String) )
	{

		this.R = parseInt( arguments[0].substring( 0, 2 ), 16 );
		this.G = parseInt( arguments[0].substring( 2, 4 ), 16 );
		this.B = parseInt( arguments[0].substring( 4, 6 ), 16 );

	}  // End if hex string.
	else if ( arguments.length == 3 )
	{

		this.R = arguments[0];
		this.G = arguments[1];
		this.B = arguments[2];

	}  // End if numbers passed in.
	else
	{

		this.R = null;
		this.G = null;
		this.B = null;

	}  // End if null passed in.

}


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Function: toHexString()
// Returns the color as a string of hexidecimal values.
//------------------------------------------------------------------------
Legato_Structure_Color.prototype.toHexString = function()
{

	var R = (this.R < 16) ? "0" + this.R.toString( 16 ) : this.R.toString( 16 );
	var G = (this.G < 16) ? "0" + this.G.toString( 16 ) : this.G.toString( 16 );
	var B = (this.B < 16) ? "0" + this.B.toString( 16 ) : this.B.toString( 16 );

	return R + G + B;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Dimensions
// Handles a single set of dimensions.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Dimensions()
// Class constructor.
//
// Parameters:
//     width - The width to store.
//     height - The height to store.
//
//     OR
//
//     element - A DOM element, in which case it will store the dimensions
//               of that element at the time this structure was created.
//------------------------------------------------------------------------
function Legato_Structure_Dimensions()
{
	
	// What type of arguments passed in?
	if ( arguments.length == 2 )
	{

		// Store the passed in parameters.
		this.width   = arguments[0];
		this.height  = arguments[1];
		
	}  // End if a width and height.
	else if ( arguments.length == 1 )
	{
		
		var element = $( arguments[0] );

		// Get the element's dimensions.
		var dimensions = element.dimensions();
		this.width = dimensions[0];
		this.height = dimensions[1];
		
	}  // End if element passed in.

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Position
// Handles a single position. For positioning objects relative to its
// containing element.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Position()
// Class constructor.
//
// Parameters:
//     top - The value from the top of the containing element.
//     right - The value from the right of the containing element.
//     bottom - The value from the bottom of the containing element.
//     left - The value from the left of the containing element.
//------------------------------------------------------------------------
function Legato_Structure_Position( top, right, bottom, left )
{

	// Store the passed in parameters.
	this.top     = top;
	this.right   = right;
	this.bottom  = bottom;
	this.left    = left;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Point
// Handles a single point.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Point()
// Class constructor.
//
// Parameters:
//     X - The X value of the point.
//     Y - The Y value of the point.
//------------------------------------------------------------------------
function Legato_Structure_Point( X, Y )
{

	// Store the passed in parameters.
	this.X = X;
	this.Y = Y;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Region
// Handles a single region.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Region()
// Class constructor.
//
// Parameters:
//     min_point - The <Legato_Structure_Point> of the top left corner.
//     max_point - The <Legato_Structure_Point> of the bottom right corner.
//
//     OR
//
//     element - A DOM element, in which case the region will be the
//               containing region of the element.
//------------------------------------------------------------------------
function Legato_Structure_Region()
{

	// What was passed in?
	if ( arguments.length == 2 )
	{

		// Store the passed in parameters.
	  this.min_point  = arguments[0];
	  this.max_point  = arguments[1];

	}  // End if points passed in.
	else if ( arguments.length == 1 )
	{

		var element = $( arguments[0] );

		// Get the element's position for the region's min point.
		this.min_point = this.element.position();
		this.min_point = new Legato_Structure_Point( this.min_point[0], this.min_point[1] );

		// Get the max point for the region.
		var dimensions = element.dimensions();
		this.max_point = new Legato_Structure_Point( (this.min_point.X + dimensions[0]), (this.min_point.Y + dimensions[1]) );

	}  // End if HTML element passed in.

}


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Function: intersectRegion()
// Used to test if two <Legato_Structure_Region> objects are intersecting
// each other.
//
// Parameters:
//     region - The <Legato_Structure_Region> that you'd like to test for
//              intersection against this object.
//     in_test - (Optional) If this is set to true, the region passed in
//               must be fully contained by this object.
//     epsilon - (Optional) This is the allowable mistake that can happen
//               between the tests, eg: if this is 1, then if the region
//               is 1 pixel away from intersecting, this function will
//               still return true.
//------------------------------------------------------------------------
Legato_Structure_Region.prototype.intersectRegion = function( region, in_test, epsilon )
{

	// Get the epsilon.
	epsilon = (epsilon) ? epsilon : 0;

	// Only do this if we are testing if it's completely contained.
	if ( in_test )
	{

	  var contained = true;

		// Is the region completely contained in this region?
		if ( region.min_point.X < (this.min_point.X + epsilon) || region.min_point.X > (this.max_point.X + epsilon) ) contained = false;
		else if ( region.min_point.Y < (this.min_point.Y + epsilon) || region.min_point.Y > (this.max_point.Y + epsilon) ) contained = false;
		else if ( region.max_point.X < (this.min_point.X + epsilon) || region.max_point.X > (this.max_point.X + epsilon) ) contained = false;
		else if ( region.max_point.Y < (this.min_point.Y + epsilon) || region.max_point.Y > (this.max_point.Y + epsilon) ) contained = false;

		// Return.
		return contained;

	}  // End if completely contained test.
	else
	{

		// Perform the intersection test.
		return ((this.min_point.X + epsilon) <= region.max_point.X) &&
						((this.min_point.Y + epsilon) <= region.max_point.Y) &&
						((this.max_point.X + epsilon) >= region.min_point.X) &&
						((this.max_point.Y + epsilon) >= region.min_point.Y);

	}  // End normal touching test.

}


//------------------------------------------------------------------------
// Function: containsPoint()
// Used to test if there is an <Legato_Structure_Point> object within the
// bounds of this region.
//
// Parameters:
//     point - The <Legato_Structure_Point> object.
//------------------------------------------------------------------------
Legato_Structure_Region.prototype.containsPoint = function( point )
{

	var contained = true;

	// Is the point completely contained in this region?
	if ( this.min_point.X > point.X || this.max_point.X < point.X ) contained = false;
	if ( this.min_point.Y > point.Y || this.max_point.Y < point.Y ) contained = false;

	// Return.
	return contained;

}


// Developed by Robert Nyman/DOMAssistant team
// code/licensing: http://code.google.com/p/domassistant/ 
// documentation: http://www.domassistant.com/documentation
// version 2.7.1.1
var DOMAssistant = function () {
	var HTMLArray = function () {
		// Constructor
	};
	var isIE = /*@cc_on!@*/false;
	var ie5 = isIE && parseFloat(navigator.appVersion) < 6;
	var tagCache = {}, lastCache = {}, useCache = true;
	var camel = {
		"accesskey": "accessKey",
		"class": "className",
		"colspan": "colSpan",
		"for": "htmlFor",
		"maxlength": "maxLength",
		"readonly": "readOnly",
		"rowspan": "rowSpan",
		"tabindex": "tabIndex",
		"valign": "vAlign",
		"cellspacing": "cellSpacing",
		"cellpadding": "cellPadding"
	};
	var regex = {
		rules: /\s*(,)\s*/g,
		selector: /^(\w+)?(#[\w\u00C0-\uFFFF\-\_]+|(\*))?((\.[\w\u00C0-\uFFFF\-_]+)*)?((\[\w+\s*(\^|\$|\*|\||~)?(=\s*([\w\u00C0-\uFFFF\s\-\_\.]+|"[^"]*"|'[^']*'))?\]+)*)?(((:\w+[\w\-]*)(\((odd|even|\-?\d*n?((\+|\-)\d+)?|[\w\u00C0-\uFFFF\-_\.]+|"[^"]*"|'[^']*'|((\w*\.[\w\u00C0-\uFFFF\-_]+)*)?|(\[#?\w+(\^|\$|\*|\||~)?=?[\w\u00C0-\uFFFF\s\-\_\.\'\"]+\]+)|(:\w+[\w\-]*))\))?)*)?(>|\+|~)?/,
		id: /^#([\w\u00C0-\uFFFF\-\_]+)$/,
		tag: /^(\w+)/,
		relation: /^(>|\+|~)$/,
		pseudo: /^:(\w[\w\-]*)(\((.+)\))?$/,
		pseudos: /:(\w[\w\-]*)(\(([^\)]+)\))?/g,
		attribs: /\[(\w+)\s*(\^|\$|\*|\||~)?=?\s*([\w\u00C0-\uFFFF\s\-_\.]+|"[^"]*"|'[^']*')?\]/g,
		classes: /\.([\w\u00C0-\uFFFF\-_]+)/g,
		quoted: /^["'](.*)["']$/,
		nth: /^((odd|even)|([1-9]\d*)|((([1-9]\d*)?)n([\+\-]\d+)?)|(\-(([1-9]\d*)?)n\+(\d+)))$/
	};
	var pushAll = function (set1, set2) {
		set1.push.apply(set1, [].slice.apply(set2));
		return set1;
	};
	if (isIE) {
		pushAll = function (set1, set2) {
			if (set2.slice) {
				return set1.concat(set2);
			}
			var i=0, item;
			while ((item = set2[i++])) {
				set1[set1.length] = item;
			}
			return set1;
		};
	}
	var contains = function (array, value) {
		if (array.indexOf) { return array.indexOf(value) >= 0; }
		for (var i=0, iL=array.length; i<iL; i++) {
			if (array[i] === value) { return true; }
		}
		return false;
	};
	var isDescendant = function (node, ancestor) {
		var parent = node.parentNode;
		return ancestor === document || parent === ancestor || (parent !== document && isDescendant(parent, ancestor));
	};
	return {
		isIE : isIE,
		camel : camel,
		allMethods : [],
		publicMethods : [
			"cssSelect",
			"elmsByClass",
			"elmsByAttribute",
			"elmsByTag"
		],
		
		initCore : function () {
			this.applyMethod.call(window, "$", this.$);
			this.applyMethod.call(window, "$$", this.$$);
			window.DOMAssistant = this;
			if (isIE) {
				HTMLArray = Array;
			}
			HTMLArray.prototype = [];
			HTMLArray.prototype.each = function (functionCall) {
				for (var i=0, il=this.length; i<il; i++) {
					functionCall.call(this[i]);
				}
				return this;
			};
			HTMLArray.prototype.first = function () {
				return (typeof this[0] !== "undefined")? DOMAssistant.addMethodsToElm(this[0]) : null;
			};
			HTMLArray.prototype.end = function () {
				return this.previousSet;
			};
			this.attach(this);
		},
		
		addMethods : function (name, method) {
			if (typeof this.allMethods[name] === "undefined") {
				this.allMethods[name] = method;
				this.addHTMLArrayPrototype(name, method);
			}
		},
		
		addMethodsToElm : function (elm) {
			for (var method in this.allMethods) {
				if (typeof this.allMethods[method] !== "undefined") {
					this.applyMethod.call(elm, method, this.allMethods[method]);
				}
			}
			return elm;
		},
		
		applyMethod : function (method, func) {
			if (typeof this[method] !== "function") {
				this[method] = func;
			}
		},
		
		attach : function (plugin) {
			var publicMethods = plugin.publicMethods;
			if (typeof publicMethods === "undefined") {
				for (var method in plugin) {
					if (method !== "init" && typeof plugin[method] !== "undefined") {
						this.addMethods(method, plugin[method]);
					}
				}
			}
			else if (publicMethods.constructor === Array) {
				for (var i=0, current; (current=publicMethods[i]); i++) {
					this.addMethods(current, plugin[current]);
				}
			}
			if (typeof plugin.init === "function") {
				plugin.init();
			}
		},
		
		addHTMLArrayPrototype : function (name, method) {
			HTMLArray.prototype[name] = function () {
				var elmsToReturn = new HTMLArray();
				elmsToReturn.previousSet = this;
				var elms;
				for (var i=0, il=this.length; i<il; i++) {
					elms = method.apply(this[i], arguments);
					if (!!elms && elms.constructor === Array) {
						elmsToReturn = pushAll(elmsToReturn, elms);
					}
					else {
						elmsToReturn.push(elms);
					}
				}
				return elmsToReturn;
			};
		},
		
		clearHandlers : function () {
			var children = this.all || this.getElementsByTagName("*");
			for (var i=0, child, attr; (child=children[i++]);) {
				if ((attr = child.attributes)) {
					for (var j=0, jl=attr.length, att; j<jl; j++) {
						att = attr[j].nodeName.toLowerCase();
						if (typeof child[att] === "function") {
							child[att] = null;
						}
					}
				}
			}
		},
		
		setCache : function (cache) {
			useCache = cache;
		},
		
		$ : function () {
			var obj = arguments[0];
			if (arguments.length === 1 && (typeof obj === "object" || (typeof obj === "function" && !!obj.nodeName))) {
				return DOMAssistant.$$(obj);
			}
			var elm = new HTMLArray();
			for (var i=0, arg, idMatch; (arg=arguments[i]); i++) {
				if (typeof arg === "string") {
					arg = arg.replace(/^[^#]*(#)/, "$1");
					if (regex.id.test(arg)) {
						if ((idMatch = DOMAssistant.$$(arg.substr(1), false))) {
							elm.push(idMatch);
						}
					}
					else {
						var doc = (document.all || document.getElementsByTagName("*")).length;
						elm = (!document.querySelectorAll && useCache && lastCache.rule && lastCache.rule === arg && lastCache.doc === doc)? lastCache.elms : pushAll(elm, DOMAssistant.cssSelection.call(document, arg));
						lastCache = { rule: arg, elms: elm, doc: doc };
					}
				}
			}
			return elm;
		},
		
		$$ : function (id, addMethods) {
			var elm = (typeof id === "object" || (typeof id === "function" && !!id.nodeName))? id : document.getElementById(id);
			var applyMethods = addMethods || true;
			if (typeof id === "string" && elm && elm.id !== id) {
				elm = null;
				for (var i=0, item; (item=document.all[i]); i++) {
					if (item.id === id) {
						elm = item;
						break;
					}
				}
			}
			if (elm && applyMethods) {
				DOMAssistant.addMethodsToElm(elm);
			}
			return elm;
		},
		
		getSequence : function (expression) {
			var start, add = 2, max = -1, modVal = -1,
				pseudoValue = regex.nth.exec(expression.replace(/^0n\+/, "").replace(/^2n$/, "even").replace(/^2n+1$/, "odd"));
			if (!pseudoValue) {
				return null;
			}
			if (pseudoValue[2]) {	// odd or even
				start = (pseudoValue[2] === "odd")? 1 : 2;
				modVal = (start === 1)? 1 : 0;
			}
			else if (pseudoValue[3]) {	// single digit
				start = parseInt(pseudoValue[3], 10);
				add = 0;
				max = start;
			}
			else if (pseudoValue[4]) {	// an+b
				add = pseudoValue[6]? parseInt(pseudoValue[6], 10) : 1;
				start = pseudoValue[7]? parseInt(pseudoValue[7], 10) : 0;
				while (start < 1) {
					start += add;
				}
				modVal = (start > add)? (start - add) % add : ((start === add)? 0 : start);
			}
			else if (pseudoValue[8]) {	// -an+b
				add = pseudoValue[10]? parseInt(pseudoValue[10], 10) : 1;
				start = max = parseInt(pseudoValue[11], 10);
				while (start > add) {
					start -= add;
				}
				modVal = (max > add)? (max - add) % add : ((max === add)? 0 : max);
			}
			return { start: start, add: add, max: max, modVal: modVal };
		},
		
		cssByDOM : function (cssRule) {
			var cssRules = cssRule.replace(regex.rules, "$1").split(",");
			var elm = new HTMLArray(), prevElm = [], matchingElms = [];
			var selectorSplitRegExp, prevParents, currentRule, cssSelectors, childOrSiblingRef, nextTag, nextRegExp, current, previous, prevParent, notElm, addElm, iteratorNext, childElm, sequence;
			try {
				selectorSplitRegExp = new RegExp("(?:\\[[^\\[]*\\]|\\(.*\\)|[^\\s\\+>~\\[\\(])+|[\\+>~]", "g");
			}
			catch (e) {
				selectorSplitRegExp = /[^\s]+/g;
			}
			function clearAdded (elm) {
				elm = elm || prevElm;
				for (var n=0, nl=elm.length; n<nl; n++) {
					elm[n].added = null;
				}
			}
			function clearChildElms () {
				for (var n=0, nl=prevParents.length; n<nl; n++) {
					prevParents[n].childElms = null;
				}
			}
			function subtractArray (arr1, arr2) {
				for (var i=0, src1; (src1=arr1[i]); i++) {
					var found = false;
					for (var j=0, src2; (src2=arr2[j]); j++) {
						if (src2 === src1) {
							found = true;
							arr2.splice(j, 1);
							break;
						}
					}
					if (found) {
						arr1.splice(i--, 1);
					}
				}
				return arr1;
			}
			function getAttr (elm, attr) {
				return isIE? elm[camel[attr.toLowerCase()] || attr] : elm.getAttribute(attr, 2);
			}
			function attrToRegExp (attrVal, substrOperator) {
				attrVal = attrVal? attrVal.replace(regex.quoted, "$1").replace(/\./g, "\\.") : null;
				switch (substrOperator) {
					case "^": return "^" + attrVal;
					case "$": return attrVal + "$";
					case "*": return attrVal;
					case "|": return "^" + attrVal + "(\\-\\w+)*$";
					case "~": return "\\b" + attrVal + "\\b";
					default: return attrVal? "^" + attrVal + "$" : null;
				}
			}
			function getTags (tag, context) {
				return ie5? ((tag === "*")? context.all : context.all.tags(tag)) : context.getElementsByTagName(tag);
			}
			function getElementsByTagName (tag, parent) {
				tag = tag || "*";
				parent = parent || document;
				return (parent === document || parent.lastModified)? tagCache[tag] || (tagCache[tag] = getTags(tag, document)) : getTags(tag, parent);
			}
			function getElementsByPseudo (previousMatch, pseudoClass, pseudoValue) {
				prevParents = [];
				var pseudo = pseudoClass.split("-"), matchingElms = [], idx = 0, checkNodeName, recur;
				var prop = (checkNodeName = /\-of\-type$/.test(pseudoClass))? "nodeName" : "nodeType";
				function getPrevElm(elm) {
					var val = checkNodeName? elm.nodeName : 1;
					while ((elm = elm.previousSibling) && elm[prop] !== val) {}
					return elm;
				}
				function getNextElm(elm) {
					var val = checkNodeName? elm.nodeName : 1;
					while ((elm = elm.nextSibling) && elm[prop] !== val) {}
					return elm;
				}
				var match = {
					first: function(el) { return !getPrevElm(el); },
					last: function(el) { return !getNextElm(el); },
					empty: function(el) { return !el.childNodes.length; },
					enabled: function(el) { return !previous.disabled && previous.type !== "hidden"; },
					disabled: function(el) { return previous.disabled; },
					checked: function(el) { return previous.checked; },
					contains: function(el) { return (previous.innerText || previous.textContent || "").indexOf(pseudoValue.replace(regex.quoted, "$1")) > -1; },
					other: function(el) { return getAttr(previous, pseudoClass) === pseudoValue; }
				};
				function basicMatch(key) {
					while ((previous=previousMatch[idx++])) {
						if (match[key](previous)) {
							matchingElms[matchingElms.length] = previous;
						}
					}
					return matchingElms;
				}
				var word = pseudo[0] || null;
				if (word && match[word]) {
					return basicMatch(word);
				}
				switch (word) {
					case "only":
						var kParent;
						while ((previous=previousMatch[idx++])) {
							prevParent = previous.parentNode;
							if (prevParent !== kParent) {
								if (!getPrevElm(previous) && !getNextElm(previous)) {
									matchingElms[matchingElms.length] = previous;
								}
								kParent = prevParent;
							}
						}
						break;
					case "nth":
						if (/^n$/.test(pseudoValue)) {
							matchingElms = previousMatch;
						}
						else {
							var direction = (pseudo[1] === "last")? ["lastChild", "previousSibling"] : ["firstChild", "nextSibling"];
							sequence = DOMAssistant.getSequence.call(this, pseudoValue);
							if (sequence) {
								while ((previous=previousMatch[idx++])) {
									prevParent = previous.parentNode;
									if (!prevParent.childElms) {
										var childCount = 0, p = previous.nodeName;
										iteratorNext = sequence.start;
										childElm = prevParent[direction[0]];
										while (childElm && (sequence.max < 0 || iteratorNext <= sequence.max)) {
											var c = childElm.nodeName;
											if ((checkNodeName && c === p) || (!checkNodeName && childElm.nodeType === 1)) {
												if (++childCount === iteratorNext) {
													if (c === p) { matchingElms[matchingElms.length] = childElm; }
													iteratorNext += sequence.add;
												}
											}
											childElm = childElm[direction[1]];
										}
										prevParent.childElms = true;
										prevParents[prevParents.length] = prevParent;
									}
								}
								clearChildElms();
							}
						}
						break;
					case "target":
						var hash = document.location.hash.slice(1);
						if (hash) {
							while ((previous=previousMatch[idx++])) {
								if (getAttr(previous, "name") === hash || getAttr(previous, "id") === hash) {
									matchingElms[matchingElms.length] = previous;
									break;
								}
							}
						}
						break;
					case "not":
						if ((recur = regex.pseudo.exec(pseudoValue))) {
							matchingElms = subtractArray(previousMatch, getElementsByPseudo(previousMatch, recur[1]? recur[1].toLowerCase() : null, recur[3] || null));
						}
						else {
							for (var re in regex) {
								if (regex[re].lastIndex) {
									regex[re].lastIndex = 0;
								}
							}
							pseudoValue = pseudoValue.replace(regex.id, "[id=$1]");
							var notTag = regex.tag.exec(pseudoValue);
							var notClass = regex.classes.exec(pseudoValue);
							var notAttr = regex.attribs.exec(pseudoValue);
							var notRegExp = new RegExp(notAttr? attrToRegExp(notAttr[3], notAttr[2]) : "(^|\\s)" + (notTag? notTag[1] : notClass? notClass[1] : "") + "(\\s|$)", "i");
							while ((notElm=previousMatch[idx++])) {
								addElm = null;
								if (notTag && !notRegExp.test(notElm.nodeName)) {
									addElm = notElm;
								}
								else if (notClass && !notRegExp.test(notElm.className)) {
									addElm = notElm;
								}
								else if (notAttr) {
									var att = getAttr(notElm, notAttr[1]);
									if (!att || !notRegExp.test(att)) {
										addElm = notElm;
									}
								}
								if (addElm && !addElm.added) {
									addElm.added = true;
									matchingElms[matchingElms.length] = addElm;
								}
							}
						}
						break;
					default: return basicMatch("other");
				}
				return matchingElms;
			}
			for (var a=0; (currentRule=cssRules[a]); a++) {
				if (a && contains(cssRules.slice(0, a), currentRule)) { continue; }
				prevElm = [this];
				cssSelectors = currentRule.match(selectorSplitRegExp);
				for (var i=0, rule; (rule=cssSelectors[i]); i++) {
					matchingElms = [];
					if (i > 0 && regex.relation.test(rule)) {
						if ((childOrSiblingRef = regex.relation.exec(rule))) {
							var idElm = null, nextWord = cssSelectors[i+1];
							if ((nextTag = regex.tag.exec(nextWord))) {
								nextTag = nextTag[1];
								nextRegExp = new RegExp("(^|\\s)" + nextTag + "(\\s|$)", "i");
							}
							else if (regex.id.test(nextWord)) {
								idElm = DOMAssistant.$(nextWord) || null;
							}
							for (var j=0, prevRef; (prevRef=prevElm[j]); j++) {
								switch (childOrSiblingRef[0]) {
									case ">":
										var children = idElm || getElementsByTagName(nextTag, prevRef);
										for (var k=0, child; (child=children[k]); k++) {
											if (child.parentNode === prevRef) {
												matchingElms[matchingElms.length] = child;
											}
										}
										break;
									case "+":
										while ((prevRef = prevRef.nextSibling) && prevRef.nodeType !== 1) {}
										if (prevRef) {
											if ((idElm && idElm[0] === prevRef) || (!idElm && (!nextTag || nextRegExp.test(prevRef.nodeName)))) {
												matchingElms[matchingElms.length] = prevRef;
											}
										}
										break;
									case "~":
										while ((prevRef = prevRef.nextSibling) && !prevRef.added) {
											if ((idElm && idElm[0] === prevRef) || (!idElm && (!nextTag || nextRegExp.test(prevRef.nodeName)))) {
												prevRef.added = true;
												matchingElms[matchingElms.length] = prevRef;
											}
										}
										break;
								}
							}
							prevElm = matchingElms;
							clearAdded();
							rule = cssSelectors[++i];
							if (/^\w+$/.test(rule) || regex.id.test(rule)) {
								continue;
							}
							prevElm.skipTag = true;
						}
					}
					var cssSelector = regex.selector.exec(rule);
					var splitRule = {
						tag : (!cssSelector[1] || cssSelector[3] === "*")? "*" : cssSelector[1],
						id : (cssSelector[3] !== "*")? cssSelector[2] : null,
						allClasses : cssSelector[4],
						allAttr : cssSelector[6],
						allPseudos : cssSelector[11]
					};
					if (splitRule.id) {
						var u = 0, DOMElm = document.getElementById(splitRule.id.replace(/#/, ""));
						if (DOMElm) {
							while (prevElm[u] && !isDescendant(DOMElm, prevElm[u])) { u++; }
							matchingElms = (u < prevElm.length)? [DOMElm] : [];
						}
						prevElm = matchingElms;
					}
					else if (splitRule.tag && !prevElm.skipTag) {
						if (i===0 && !matchingElms.length && prevElm.length === 1) {
							prevElm = matchingElms = pushAll([], getElementsByTagName(splitRule.tag, prevElm[0]));
						}
						else {
							for (var l=0, ll=prevElm.length, tagCollectionMatches, tagMatch; l<ll; l++) {
								tagCollectionMatches = getElementsByTagName(splitRule.tag, prevElm[l]);
								for (var m=0; (tagMatch=tagCollectionMatches[m]); m++) {
									if (!tagMatch.added) {
										tagMatch.added = true;
										matchingElms[matchingElms.length] = tagMatch;
									}
								}
							}
							prevElm = matchingElms;
							clearAdded();
						}
					}
					if (!matchingElms.length) {
						break;
					}
					prevElm.skipTag = false;
					if (splitRule.allClasses) {
						var n = 0, matchingClassElms = [], allClasses = splitRule.allClasses.split(".").slice(1);
						while ((current = prevElm[n++])) {
							var matchCls = true, elmClass = current.className;
							if (elmClass && elmClass.length) {
								elmClass = elmClass.split(" ");
								for (var o=0, ol=allClasses.length; o<ol; o++) {
									if (!contains(elmClass, allClasses[o])) {
										matchCls = false;
										break;
									}
								}
								if (matchCls) {
									matchingClassElms[matchingClassElms.length] = current;
								}
							}
						}
						prevElm = matchingElms = matchingClassElms;
					}
					if (splitRule.allAttr) {
						var r = 0, regExpAttributes = [], matchingAttributeElms = [], allAttr = splitRule.allAttr.match(/\[[^\]]+\]/g);
						for (var q=0, ql=allAttr.length, attributeMatch, attrVal; q<ql; q++) {
							regex.attribs.lastIndex = 0;
							attributeMatch = regex.attribs.exec(allAttr[q]);
							attrVal = attrToRegExp(attributeMatch[3], attributeMatch[2] || null);
							regExpAttributes[q] = [(attrVal? new RegExp(attrVal) : null), attributeMatch[1]];
						}
						while ((current = matchingElms[r++])) {
							for (var s=0, sl=regExpAttributes.length; s<sl; s++) {
								var matchAttr = true, attributeRegExp = regExpAttributes[s][0], currentAttr = getAttr(current, regExpAttributes[s][1]);
								if (!attributeRegExp && currentAttr === true) { continue; }
								if ((!attributeRegExp && (!currentAttr || typeof currentAttr !== "string" || !currentAttr.length)) || (!!attributeRegExp && !attributeRegExp.test(currentAttr))) {
									matchAttr = false;
									break;
								}
							}
							if (matchAttr) {
								matchingAttributeElms[matchingAttributeElms.length] = current;
							}
						}
						prevElm = matchingElms = matchingAttributeElms;
					}
					if (splitRule.allPseudos) {
						var allPseudos = splitRule.allPseudos.match(regex.pseudos);
						for (var t=0, tl=allPseudos.length; t<tl; t++) {
							regex.pseudos.lastIndex = 0;
							var pseudo = regex.pseudos.exec(allPseudos[t]);
							var pseudoClass = pseudo[1]? pseudo[1].toLowerCase() : null;
							var pseudoValue = pseudo[3] || null;
							matchingElms = getElementsByPseudo(matchingElms, pseudoClass, pseudoValue);
							clearAdded(matchingElms);
						}
						prevElm = matchingElms;
					}
				}
				elm = pushAll(elm, prevElm);
			}
			return elm;
		},
		
		cssByXpath : function (cssRule) {
			var ns = { xhtml: "http://www.w3.org/1999/xhtml" };
			var prefix = (document.documentElement.namespaceURI === ns.xhtml)? "xhtml:" : "";
			var nsResolver = function lookupNamespaceURI (prefix) {
				return ns[prefix] || null;
			};
			DOMAssistant.cssByXpath = function (cssRule) {
				if (/:checked/.test(cssRule)) {
					return DOMAssistant.cssByDOM.call(this, cssRule);
				}
				var cssRules = cssRule.replace(regex.rules, "$1").split(",");
				var elm = new HTMLArray();
				var currentRule, cssSelectors, xPathExpression, cssSelector, splitRule, sequence;
				var selectorSplitRegExp = new RegExp("(?:\\[[^\\[]*\\]|\\(.*\\)|[^\\s\\+>~\\[\\(])+|[\\+>~]", "g");
				function attrToXPath (match, p1, p2, p3) {
					p3 = p3? p3.replace(regex.quoted, "$1") : p3;
					switch (p2) {
						case "^": return "starts-with(@" + p1 + ", \"" + p3 + "\")";
						case "$": return "substring(@" + p1 + ", (string-length(@" + p1 + ") - " + (p3.length - 1) + "), " + p3.length + ") = \"" + p3 + "\"";
						case "*": return "contains(concat(\" \", @" + p1 + ", \" \"), \"" + p3 + "\")";
						case "|": return "(@" + p1 + "=\"" + p3 + "\" or starts-with(@" + p1 + ", \"" + p3 + "-\"))";
						case "~": return "contains(concat(\" \", @" + p1 + ", \" \"), \" " + p3 + " \")";
						default: return "@" + p1 + (p3? "=\"" + p3 + "\"" : "");
					}
				}
				function attrToXPathB (match, p1, p2, p3) {
					return "[" + attrToXPath(match, p1, p2, p3) + "]";
				}
				function pseudoToXPath (tag, pseudoClass, pseudoValue) {
					tag = /\-child$/.test(pseudoClass)? "*" : tag;
					var xpath = "", pseudo = pseudoClass.split("-"), recur;
					switch (pseudo[0]) {
						case "nth":
							if (!/^n$/.test(pseudoValue)) {
								var position = ((pseudo[1] === "last")? "(count(following-sibling::" : "(count(preceding-sibling::") + tag + ") + 1)";
								if ((sequence = DOMAssistant.getSequence.call(this, pseudoValue))) {
									xpath = (sequence.start === sequence.max)?
										position + " = " + sequence.start :
										position + " mod " + sequence.add + " = " + sequence.modVal + ((sequence.start > 1)? " and " + position + " >= " + sequence.start : "") + ((sequence.max > 0)? " and " + position + " <= " + sequence.max: "");
								}
							}
							break;
						case "not":
							var notSelector = (recur = regex.pseudo.exec(pseudoValue))?
								pseudoToXPath(tag, recur[1]? recur[1].toLowerCase() : null, recur[3] || null) :
								pseudoValue.replace(regex.id, "[id=$1]")
									.replace(regex.tag, "self::$1")
									.replace(regex.classes, "contains(concat(\" \", @class, \" \"), \" $1 \")")
									.replace(regex.attribs, attrToXPath);
							xpath = "not(" + notSelector + ")";
							break;
						case "first": return "not(preceding-sibling::" + tag + ")";
						case "last": return "not(following-sibling::" + tag + ")";
						case "only": return "not(preceding-sibling::" + tag + " or following-sibling::" + tag + ")";
						case "empty": return "count(child::*) = 0 and string-length(text()) = 0";
						case "contains": return "contains(., \"" + pseudoValue.replace(regex.quoted, "$1") + "\")";
						case "enabled": return "not(@disabled) and not(@type=\"hidden\")";
						case "disabled": return "@disabled";
						case "target": var hash = document.location.hash.slice(1); return "@name=\"" + hash + "\" or @id=\"" + hash + "\"";
						default: return "@" + pseudoClass + "=\"" + pseudoValue + "\"";
					}
					return xpath;
				}
				for (var i=0; (currentRule=cssRules[i]); i++) {
					if (i && contains(cssRules.slice(0, i), currentRule)) { continue; }
					cssSelectors = currentRule.match(selectorSplitRegExp);
					xPathExpression = ".";
					for (var j=0, jl=cssSelectors.length; j<jl; j++) {
						cssSelector = regex.selector.exec(cssSelectors[j]);
						splitRule = {
							tag : prefix + ((!cssSelector[1] || cssSelector[3] === "*")? "*" : cssSelector[1]),
							id : (cssSelector[3] !== "*")? cssSelector[2] : null,
							allClasses : cssSelector[4],
							allAttr : cssSelector[6],
							allPseudos : cssSelector[11],
							tagRelation : cssSelector[23]
						};
						if (splitRule.tagRelation) {
							var mapping = { ">": "/child::", "+": "/following-sibling::*[1]/self::", "~": "/following-sibling::" };
							xPathExpression += mapping[splitRule.tagRelation] || "";
						}
						else {
							xPathExpression += (j > 0 && regex.relation.test(cssSelectors[j-1]))? splitRule.tag : ("/descendant::" + splitRule.tag);
						}
						if (splitRule.id) {
							xPathExpression += "[@id = \"" + splitRule.id.replace(/^#/, "") + "\"]";
						}
						if (splitRule.allClasses) {
							xPathExpression += splitRule.allClasses.replace(regex.classes, "[contains(concat(\" \", @class, \" \"), \" $1 \")]");
						}
						if (splitRule.allAttr) {
							xPathExpression += splitRule.allAttr.replace(regex.attribs, attrToXPathB);
						}
						if (splitRule.allPseudos) {
							var allPseudos = splitRule.allPseudos.match(regex.pseudos);
							for (var k=0, kl=allPseudos.length; k<kl; k++) {
								regex.pseudos.lastIndex = 0;
								var pseudo = regex.pseudos.exec(allPseudos[k]);
								var pseudoClass = pseudo[1]? pseudo[1].toLowerCase() : null;
								var pseudoValue = pseudo[3] || null;
								var xpath = pseudoToXPath(splitRule.tag, pseudoClass, pseudoValue);
								if (xpath.length) {
									xPathExpression += "[" + xpath + "]";
								}
							}
						}
					}
					var xPathNodes = document.evaluate(xPathExpression, this, nsResolver, 0, null), node;
					while ((node = xPathNodes.iterateNext())) {
						elm.push(node);
					}
				}
				return elm;
			};
			return DOMAssistant.cssByXpath.call(this, cssRule);
		},
		
		cssSelection : function (cssRule) {
			DOMAssistant.cssSelection = document.evaluate? DOMAssistant.cssByXpath : DOMAssistant.cssByDOM;
			if (document.querySelectorAll) {
				var cssSelectionBackup = DOMAssistant.cssSelection;
				DOMAssistant.cssSelection = function (cssRule) {
					try {
						var elm = new HTMLArray();
						return pushAll(elm, this.querySelectorAll(cssRule));
					}
					catch (e) {
						return cssSelectionBackup.call(this, cssRule);
					}
				};
			}
			return DOMAssistant.cssSelection.call(this, cssRule);
		},
		
		cssSelect : function (cssRule) {
			return DOMAssistant.cssSelection.call(this, cssRule);
		},
		
		elmsByClass : function (className, tag) {
			var cssRule = (tag || "") + "." + className;
			return DOMAssistant.cssSelection.call(this, cssRule);
		},
		
		elmsByAttribute : function (attr, attrVal, tag, substrMatchSelector) {
			var cssRule = (tag || "") + "[" + attr + ((attrVal && attrVal !== "*")? ((substrMatchSelector || "") + "=" + attrVal + "]") : "]");
			return DOMAssistant.cssSelection.call(this, cssRule);
		},
		
		elmsByTag : function (tag) {
			return DOMAssistant.cssSelection.call(this, tag);
		}
	};
}();
DOMAssistant.initCore();


DOMAssistant.CSS = function () {
	return {
		addClass : function (className) {
			if (!DOMAssistant.CSS.hasClass.call(this, className)) {
				var currentClass = this.className;
				this.className = currentClass + (currentClass.length? " " : "") + className;
			}
			return this;
		},

		removeClass : function (className) {
			return DOMAssistant.CSS.replaceClass.call(this, className);
		},

		replaceClass : function (className, newClass) {
			var classToRemove = new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i");
			this.className = this.className.replace(classToRemove, function (match, p1, p2) {
				var retVal = newClass? (p1 + newClass + p2) : "";
				if (/^\s+.*\s+$/.test(match)) {
					retVal = match.replace(/(\s+).+/, "$1");
				}
				return retVal;
			}).replace(/^\s+|\s+$/g, "");
			return this;
		},

		hasClass : function (className) {
			return new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i").test(this.className);
		},

		setStyle : function (style, value) {
			if (this.filters && (typeof style === "string"? /opacity/i.test(style) : style.opacity)) {
				this.style.filter = "alpha(opacity=" + (value || style.opacity || 1) * 100 + ")";
			}
			if (typeof this.style.cssText !== "undefined") {
				var styleToSet = this.style.cssText;
				if (typeof style === "object") {
					for (var i in style) {
						if (typeof i === "string") {
							styleToSet += ";" + i + ":" + style[i];
						}
					}
				}
				else {
					styleToSet += ";" + style + ":" + value;
				}
				this.style.cssText = styleToSet;
			}
			return this;
		},

		getStyle : function (cssRule) {
			var val = "";
			cssRule = cssRule.toLowerCase();
			if (document.defaultView && document.defaultView.getComputedStyle) {
				val = document.defaultView.getComputedStyle(this, "").getPropertyValue(cssRule);
			}
			else if (this.currentStyle) {
				if (this.filters && /^opacity$/.test(cssRule)) {
					var alpha = this.filters["DXImageTransform.Microsoft.Alpha"] || this.filters.alpha || {};
					val = (alpha.opacity || 100) / 100;
				}
				else {
					cssRule = cssRule.replace(/^float$/, "styleFloat").replace(/\-(\w)/g, function (match, p1) {
						return p1.toUpperCase();
					});
					val = this.currentStyle[cssRule];
				}
				if (val === "auto" && /^(width|height)$/.test(cssRule) && this.currentStyle.display !== "none") {
					val = this["offset" + cssRule.charAt(0).toUpperCase() + cssRule.substr(1)] + "px";
				}
			}
			return val;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.CSS);


DOMAssistant.Content = function () {
	var $ = DOMAssistant.$;
	return {
		init : function () {
			DOMAssistant.setCache(false);
		},

		prev : function () {
			var prevSib = this;
			while ((prevSib = prevSib.previousSibling) && prevSib.nodeType !== 1) {}
			return $(prevSib);
		},

		next : function () {
			var nextSib = this;
			while ((nextSib = nextSib.nextSibling) && nextSib.nodeType !== 1) {}
			return $(nextSib);
		},

		create : function (name, attr, append, content) {
			var elm = $(document.createElement(name));
			if (attr) {
				elm = elm.setAttributes(attr);
			}
			if (typeof content !== "undefined") {
				elm.addContent(content);
			}
			if (append) {
				DOMAssistant.Content.addContent.call(this, elm);
			}
			return elm;
		},

		setAttributes : function (attr) {
			if (DOMAssistant.isIE) {
				var setAttr = function (elm, att, val) {
					var attLower = att.toLowerCase();
					switch (attLower) {
						case "name":
						case "type":
							return document.createElement(elm.outerHTML.replace(new RegExp(attLower + "=[a-zA-Z]+"), " ").replace(">", " " + attLower + "=" + val + ">"));
						case "style":
							elm.style.cssText = val;
							return elm;
						default:
							elm[DOMAssistant.camel[attLower] || att] = val;
							return elm;
					}
				};
				DOMAssistant.Content.setAttributes = function (attr) {
					var elem = this;
					var parent = this.parentNode;
					for (var i in attr) {
						if (typeof attr[i] === "string" || typeof attr[i] === "number") {
							var newElem = setAttr(elem, i, attr[i]);
							if (parent && /(name|type)/i.test(i)) {
								if (elem.innerHTML) {
									newElem.innerHTML = elem.innerHTML;
								}
								parent.replaceChild(newElem, elem);
							}
							elem = newElem;
						}
					}
					return $(elem);
				};
			}
			else {
				DOMAssistant.Content.setAttributes = function (attr) {
					for (var i in attr) {
						if (/class/i.test(i)) {
							this.className = attr[i];
						}
						else {
							this.setAttribute(i, attr[i]);
						}
					}
					return this;
				};
			}
			return DOMAssistant.Content.setAttributes.call(this, attr); 
		},

		addContent : function (content) {
			var type = typeof content;
			if (type === "string" || type === "number") {
				this.innerHTML += content;
			}
			else if (type === "object" || (type === "function" && !!content.nodeName)) {
				this.appendChild(content);
			}
			return this;
		},

		replaceContent : function (content) {
			DOMAssistant.clearHandlers.apply(this);
			this.innerHTML = "";
			return DOMAssistant.Content.addContent.call(this, content);
		},

		replace : function (content, returnNew) {
			var type = typeof content;
			if (type === "string" || type === "number") {
				var parent = this.parentNode;
				var tmp = $(parent).create("div", null, false, content);
				for (var i=tmp.childNodes.length-1; i>=0; i--) {
					parent.insertBefore(tmp.childNodes[i], this.nextSibling);
				}
				content = this.nextSibling;
				parent.removeChild(this);
			}
			else if (type === "object" || (type === "function" && !!content.nodeName)) {
				this.parentNode.replaceChild(content, this);
			}
			return returnNew? content : this;
		},

		remove : function () {
			this.parentNode.removeChild(this);
			return null;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.Content);


/*
	Class: Legato_DOM_Library
	Provides a plugin to DOMAssistant to allow extra features for working with the DOM.
*/
Legato_DOM_Library = {};

Legato_DOM_Library.DOMAssistantPlugIn = function () 
{
	
	return {
				
		/*
			Function: dimensions()
			Sets/gets the dimension's of the element.
			If no dimensions passed in, will return the element's dimensions.
			
			Syntax:
				*Getting Dimensions*
				
				array dimensions()
				
				*Setting Dimensions*
				
				object dimensions( int width, int height )
				
			Parameters:				
				*Setting Dimensions*
				
				int width - The new width you'd like the element to have. Pass in null if you would like the width to stay the same.
				int height - The new height you'd like the element to have. Pass in null if you would like the height to stay the same.
				
			Returns:
				*Getting Dimensions*
				
				Returns an array of the dimensions, with the first item being the width and the second item being the height.
				
				*Setting Dimensions*
				
				Returns the element the dimensions were set on.
								
			Examples:
			(begin code)
				var dimensions = $$( 'container' ).dimensions();
				alert( dimensions[0] )  // Show the width of the container.
			(end)
			
			(begin code)
				// Set the height of the container to 300 pixels.
				$$( 'container' ).dimensions( null, 300 );
			(end)
		*/
		dimensions: function()
		{
			
			if ( this.window == window )
			{
				
				var width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
		        var height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
		        
		        return [ width, height ];
		        
			}
			else if ( arguments.length == 0 )
			{
				
				return [ this.offsetWidth, this.offsetHeight ];
				
			}	
			else
			{
				
				if ( arguments[0] !== null ) this.setStyle( 'width', arguments[0] + 'px' );
				if ( arguments[1] !== null ) this.setStyle( 'height', arguments[1] + 'px' );
				return this;
				
			}
			
		},
		
		
		/*
			Function: position()
			Sets/gets the position of an element.
			If no position passed in, will return the current position of the element.
			
			Syntax:
				*Getting Position*
				
				array position()
				
				*Setting Position*
				
				object position( int X, int Y )
				
			Parameters:
				*Setting Position*
				
				int X - The new X value that you'd like the element to have. Pass in null if you would like the X position to stay the same.
				int Y - The new Y value that you'd like the element to have. Pass in null if you would like the Y position to stay the same.
				
			Returns:
				*Getting Position*
				
				Returns an array of the position, with the first item being the X value and the second item being the Y value.
				
				*Setting Position*
				
				Returns the element the position was set on.
				
			Notes:
				This function works off of the page grid and not the containing element. So, setting an X value of 50 would put the element
				50 pixels from the top of the page.
								
			Examples:
			(begin code)
				// Show the position of the container element.
				var pos = $$( 'container' ).position();
				alert( pos[0] + ' | ' + pos[1] );
				
				// Set the Y position to 50 pixels.
				$$( 'container' ).position( null, 50 );
			(end)
		*/
		position: function()
		{
			
			if ( arguments.length == 0 )
			{
				
				var offsetLeft = offsetTop = 0;
				var elem = this;
				
				while ( elem !== null )
				{
					offsetLeft += elem.offsetLeft;
					offsetTop += elem.offsetTop;
					elem = elem.offsetParent;
				}
				
				return [ offsetLeft, offsetTop ];
				
			}
			else
			{
				
				// Get the positioning of this element.
				var positioning = this.getStyle( 'position' );
				
				// If it's statically positioned, we change it to relative positioning.
				// If it's absolute, we leave it.
                if ( positioning == 'static' ) 
				{
					positioning = 'relative';
                    this.setStyle( 'position', 'relative' );
                }
                
                // Try to get the offset value.
                var offset = 
				[
                    parseInt( this.getStyle( 'left' ), 10 ),
                    parseInt( this.getStyle( 'top' ), 10 )
                ];
            
            	// If auto was returned, retrieve the correct offset.
                if ( isNaN( offset[0] ) )
                    offset[0] = (positioning == 'relative') ? 0 : this.offsetLeft;
                    
                // If auto was returned, retrieve the correct offset.
                if ( isNaN( offset[1] ) )
                    offset[1] = (positioning == 'relative') ? 0 : this.offsetTop;
                    
                // Get the page XY position of the element.
                var posXY = this.position();
                
                // If a new X or Y value was passed in, set it.
                if ( arguments[0] !== null ) this.setStyle( 'left', arguments[0] - posXY[0] + offset[0] + 'px' );
                if ( arguments[1] !== null ) this.setStyle( 'top', arguments[1] - posXY[1] + offset[1] + 'px' );
                
                return this;
				
			}
			
		},
		
		
		/*
			Function: scrollOffset()
			Sets/gets the scroll offset of an element.
			If no offset passed in, will return the current offset of the element.
			
			Syntax:
				*Getting Offset*
				
				array scrollOffset()
				
				*Setting Offset*
				
				object scrollOffset( int X, int Y )
				
			Parameters:
				*Setting Offset*
				
				int X - The new X value that you'd like the element's scroll offset to be. Pass in null if you would like the X offset to stay the same.
				int Y - The new Y value that you'd like the element's scroll offset to be. Pass in null if you would like the Y offset to stay the same.
				
			Returns:
				*Getting Offset*
				
				Returns an array of the scroll offset, with the first item being the X offset and the second item being the Y offset.
				
				*Setting Offset*
				
				Returns the element the scroll offset was set on.
								
			Examples:
			(begin code)
				// Show the Y offset of the container element.
				alert( $$( 'container' ).position() );
				
				// Set the X offset to 75 pixels.
				$$( 'container' ).scrollOffset( 75, null );
			(end)
		*/
		scrollOffset: function()
		{
			
			if ( this.window == window || this == document.body )
			{
				
				var X = Y = 0;
				
				if( typeof( window.pageXOffset ) == 'number' ) 
				{					
					X = window.pageXOffset;
					Y = window.pageYOffset;					
				}  // Netscape.
				else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) 
				{					
					X = document.body.scrollLeft;
					Y = document.body.scrollTop;					
				}  // Standards compliant.
				else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) 
				{					
					X = document.documentElement.scrollLeft;
					Y = document.documentElement.scrollTop;					
				}  // IE 6 standards mode.
				
				return [ X, Y ];
		        
			}
			else if ( arguments.length == 0 )
			{
				
				return [ this.scrollLeft, this.scrollTop ];
				
			}	
			else
			{
				
				if ( arguments[0] !== null ) this.scrollLeft = arguments[0];
				if ( arguments[1] !== null ) this.scrollTop = arguments[1];
				return this;
				
			}
			
		}
		
	};
	
}();

DOMAssistant.attach( Legato_DOM_Library.DOMAssistantPlugIn );
//------------------------------------------------------------------------
// Name: Legato_Form
// Desc: A helper class to help with the PHP Form class.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// Name: Legato_Form
// Desc: Class constructor.
//------------------------------------------------------------------------
function Legato_Form( form, options_object )
{

	this.form                  = $$( form );
	this.input_elements        = [];
	this.groups                = [];

	// Store the parameters.
	this.submit_button         = options_object.submit_button;
	this.submit_form           = (options_object.submit_form == null) ? false : true;
	this.redirect_url          = options_object.redirect_url;
	this.request_url           = options_object.request_url;
	this.modified_request_url  = '';
	this.errors                = [];

	this.processing            = false;
	
	// Add on onsubmit event for the form.
	var form = this;
	Legato_Events_Handler.addEvent( this.form, 'onsubmit', function(){ return form.validateForm(); } );

}


//----------------------------------------------------------------------
// Name: validateForm()
// Desc: Start the validation.
//----------------------------------------------------------------------
Legato_Form.prototype.validateForm = function()
{
	
	var errors = false;

	// Are we processing?
	if ( this.processing == true )
		return false;
	else
		this.processing = true;

	// Disable the submit button.
	if ( this.submit_button != null ) 
		this.submit_button.disabled = true;

	// First make sure the form is clean.
	this.cleanupForm();
	
	// If there is a request URL, send the request.
	if ( this.request_url != null )
	{
		
		var query_string = this.getQueryString();

		// Send the request.
		var form = this;
		Legato_RequestManager.makeRequest( this.modified_request_url, function( response ){ form.processResponse( response ); }, query_string );

		// Return. We will continue processing in the processResponse() function.
		return false;

	}
	
	// Finish the validation.
	return this.finishValidation();

};


//----------------------------------------------------------------------
// Name: processResponse()
// Desc: Processes the response from the XHR request.
//----------------------------------------------------------------------
Legato_Form.prototype.processResponse = function( response )
{
	
	var any_errors = false;
	
	// JSON or XML?
	if ( !response.responseXML || response.responseXML.getElementsByTagName( 'errors' ).length == 0 )
	{

		var errors = eval( '(' + response.responseText + ')' );
		
		// Any errors?
		if ( errors.length != 0 )
		{
			
			// Set errors on.
			any_errors = true;
			
			// Loop through each error.
			for ( var id in errors )
			{
				
				// Get the message.
				var message = errors[id];
	
				// Post the error.
				this.postError( id, message );
	
			}  // Next error.
			
		}
		
	}
	else
	{
		
		response = response.responseXML;
		
		// Get the errors.
		var errors = response.getElementsByTagName( 'error' );
		
		// Is there any errors?
		if ( errors.length != 0 )
		{
			
			// Set errors on.
			any_errors = true;
	
			// Loop through each error.
			for ( var i = 0; i < errors.length; i++ )
			{
	
				// Get the error details.
				var id = errors[i].getElementsByTagName( "id" );
	
				if ( id.length == 0 )
					id = null;
				else
					id = id[0].firstChild.data;
	
				// Get the message.
				var message = errors[i].getElementsByTagName( "message" )[0].firstChild.data;
	
				// Post the error.
				this.postError( id, message );
	
			}  // Next error.
			
		}
		
	}
	
	// Any errors?
	if ( !any_errors )
	{
		
		// Finish validation.
		this.finishValidation();

		// Return.
		return;
		
	}

	// Show the generic error message.
	this.postError( null, 'There were errors while processing the form. Please fix them and try submitting the form again.' );

	// Set processing to false.
	this.processing = false;

	// Enable the submit button.
	if ( this.submit_button != null ) 
		this.submit_button.disabled = false;

};


//----------------------------------------------------------------------
// Name: finishValidation()
// Desc: Finishes the validation.
//----------------------------------------------------------------------
Legato_Form.prototype.finishValidation = function()
{

	// Check for a redirect URL and if there is one redirect them.
	if ( this.redirect_url != null )
	{
		window.location = this.redirect_url;
		return true;
	}
	
	// If the submit form flag is set, submit the form.
	if ( this.submit_form )
		return true;
		
	// Set processing to false.
	this.processing = false;

	// Enable the submit button.
	if ( this.submit_button != null ) 
		this.submit_button.disabled = false;
	
	// Don't submit form.
	return false;

};


//----------------------------------------------------------------------
// Name: postError()
// Desc: Posts an error to the form with information about what went
//       wrong and why it went wrong.
// Note: If null is passed in for element_id, the error will be placed
//       at the end of the form.
//----------------------------------------------------------------------
Legato_Form.prototype.postError = function( element_id, error )
{
	
	// Create the error node.
	var error_node = $( document.body ).create( 'p', { className: 'error' }, false, error );
	
	// What type of placement?
	if ( element_id == null )
	{

		// Do we have a submit button?
		if ( this.submit_button != null )
		{
			
			// A group or not?
			if ( $( this.submit_button.parentNode.parentNode ).hasClass( 'group_elements' ) )			
				this.submit_button.parentNode.parentNode.parentNode.insertBefore( error_node, this.submit_button.parentNode.parentNode.parentNode.firstChild );
			else
				this.submit_button.parentNode.insertBefore( error_node, this.submit_button.parentNode.firstChild );
		}
		else
			this.form.addContent( error_node );

	}  // End if general error message.
	else
	{

		var html_element = this.form.cssSelect( '#' + element_id )[0];

		// Group or normal?
		if ( $( html_element.parentNode.parentNode ).hasClass( 'group' ) )
			html_element.parentNode.parentNode.insertBefore( error_node, html_element.parentNode.parentNode.firstChild );
		else
			html_element.parentNode.insertBefore( error_node, html_element.parentNode.firstChild );

	}  // End if normal/group error message.
	
};


//----------------------------------------------------------------------
// Name: cleanupForm()
// Desc: Cleans up the form to make it ready for form validation.
//----------------------------------------------------------------------
Legato_Form.prototype.cleanupForm = function()
{

	// Get all the forms error's.
	var errors = this.form.cssSelect( 'p.error' );

	// Loop through each error.
	// We get the length before hand, because we take elements away from the
	// array in the loop.
	for ( var i = 0; i < errors.length; i++ )
	{

		// Get the element. We retrieve the 0th element because we remove
		// the child below, and the next one will fall in this place.
		var error_element = errors[i];

		// Is this an error element?
		error_element.remove();  // Remove the element.


	}  // Next error node.

};


//----------------------------------------------------------------------
// Name: getQueryString()
// Desc: Concatenates all the managed input elements in to a query
//       string suitable for appending to a URL.
//----------------------------------------------------------------------
Legato_Form.prototype.getQueryString = function()
{

	var values = Array();

	// Loop through each input element in the form.
	for ( var i = 0; i < this.form.elements.length; i++ )
	{

		var element = this.form.elements[i];

		// What type of element?
		switch ( element.type )
		{

		// Simple elements.
		case 'text':
		case 'password':
		case 'file':
		case 'textarea':
		case 'hidden':
		case 'select-one':

			values.push( (element.name + "=" + encodeURIComponent( element.value )) );
			break;

		// Checkboxes and radio buttons.
		case 'checkbox':
		case 'radio':

			// Only add if checked.
			if ( element.checked )
				values.push( (element.name + "=" + encodeURIComponent( element.value )) );

			break;

		// Select multiples.
		case 'select-multiple':

			// Loop through each option.
			for ( var n = 0; n < element.options.length; n++ )
			{

				// Only add if option is selected.
				if ( element.options[n].selected )
					values.push( (element.name + "=" + encodeURIComponent( element.options[n].value )) );

			}  // Next option.

			break;

		}  // End what type of elements.

	}  // Next input element.

	// Put the values together into a string.
	var query_string = values.join( "&" );

	// Get the pieces of the request URL.
	var pieces = this.request_url.match( /(\S*)\?(\S*)/ );

	// If the request_url does not have a ? in it, get rid of the query values from
	// the request URL, and add it to the query string.
	if ( pieces != null )
	{

		this.modified_request_url = pieces[1];
		query_string              = pieces[2] + "&" + query_string;

	}
	else
	{

		this.modified_request_url = this.request_url;

	}

	// Return the query string.
	return query_string;

};


//------------------------------------------------------------------------
// Package: Ajax
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Class: Legato_XHR
// This class is managed by the Legato_RequestManager and holds a single
// XML HTTP Request.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Constructor: Legato_XHR()
// The class constructor.
//------------------------------------------------------------------------
function Legato_XHR()
{

	// The user function to be called after the request has been completed.
	this.user_func = null;

	// The actual http request object.
	this.xml_http_request = null;

	// Time to create the XMLHttpRequest object.
	if ( window.XMLHttpRequest )
	{

		// This is for Opera, Mozilla, Safari, and all the smart browsers...
		this.xml_http_request = new XMLHttpRequest();

		// Try to override the mime type.
		if ( this.xml_http_request.overrideMimeType )
			this.xml_http_request.overrideMimeType( "text/xml" );

	}
	else if ( window.ActiveXObject )
	{

		// This is for IE.
		try
		{
			this.xml_http_request = new ActiveXObject( "Msxml2.XMLHTTP" );
		}
		catch ( e )
		{
			try { this.xml_http_request = new ActiveXObject( "Microsoft.XMLHTTP" ); }
			catch ( e ) {}
		}

	}

}


//------------------------------------------------------------------------
// (Exclude)
// Function: inProgress()
// Called to check if the request is still processing.
//------------------------------------------------------------------------
Legato_XHR.prototype.inProgress = function()
{

	return (this.xml_http_request.readyState != 0 && this.xml_http_request.readyState != 4);

};


//------------------------------------------------------------------------
// (Exclude)
// Function: clear()
// Called to clear out the object.
//------------------------------------------------------------------------
Legato_XHR.prototype.clear = function()
{

	// Clear out the object.
	this.xml_http_request.onreadystatechange = function(){};
	this.user_func = null;
	
	// Push onto the stack of available XHR objects.
	Legato_RequestManager.stack.push( this );

};


//------------------------------------------------------------------------
// Class: Legato_RequestManager
// This class manages all the request objects. You use this class to send
// out an Ajax request.
//------------------------------------------------------------------------
Legato_RequestManager =
{

	num_requests: 0,     // How many requests have been created.
	stack: new Array(),  // The stack of available XHR objects.
	stack_size: 10,      // The initial size of the stack.


	//--------------------------------------------------------------------
	// Function: makeRequest()
	// Sends out an HTTP request.
	//
	// Parameters:
	//     url - The URL that you would like to request.
	//     user_func - (Optional) A function that you would like to
	//                 execute once the response is returned.
	//     query_string - (Optional) The query string that you would like
	//                    to pass in to the request. If a query string is
	//                    passed in, the request will revert to a POS, if
	//                    left blank it will send as a GET.
	//--------------------------------------------------------------------
	makeRequest: function( url, user_func, query_string )
	{
		
		var http_request = null;	

		// Get the method.
		if ( query_string != null )
			method = "POST";
		else
			method = "GET";

		// Increment the number of requests.
		++Legato_RequestManager.num_requests;

		// Create the request object.
		if ( Legato_RequestManager.stack.length < 1 )
			http_request = new Legato_XHR();
		else
			http_request = Legato_RequestManager.stack.pop();

		// Store the arguments.
		http_request.user_func = user_func;

		// Set up the request.
		http_request.xml_http_request.open( method, url, true );
		http_request.xml_http_request.onreadystatechange = function() { Legato_RequestManager.handleResponse( http_request ) };

		// If we are posting...
		if ( query_string != null )
		{

			// Set the appropriate values for posting.
			http_request.xml_http_request.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
			http_request.xml_http_request.setRequestHeader( "Content-length", query_string.length );
			http_request.xml_http_request.setRequestHeader( "Connection", "close" );

			// Send the request.
			http_request.xml_http_request.send( query_string );

		}
		else
		{

			// Send the request.
			http_request.xml_http_request.send( null );

		}

	},


	//--------------------------------------------------------------------
	// (Exclude)
	// Function: handleResponse()
	// This function is called when the response status changes.
	//--------------------------------------------------------------------
	handleResponse: function( http_request )
	{

		// Are we completed?
		if ( http_request.inProgress() )
			return;

		// Did it pass through?
		if ( http_request.xml_http_request.status == 200 )
		{

			// Pass on to the response user function.
			if ( http_request.user_func )
			{
				http_request.user_func( http_request.xml_http_request );

			}  // End user func.

		}  // End if passed through.

		// Release the XHR object.
		Legato_RequestManager.releaseRequest( http_request );

	},


	//--------------------------------------------------------------------
	// (Exclude)
	// Function: releaseRequest()
	// Called to release the request object.
	//--------------------------------------------------------------------
	releaseRequest: function( request_object )
	{
		
		// Clear out the object.
		request_object.clear();

	}

};

// Set up the stack.
for ( var i = 0; i < Legato_RequestManager.stack_size; i++ )
	Legato_RequestManager.stack.push( new Legato_XHR() );

//------------------------------------------------------------------------
// Package: Animation
// For animating elements in the DOM in various ways.
//
// Topic: Dependencies
// - <Events Handler>
// - <Structures>
// - <DOM Library>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class: Legato_Animation_Controller
// Stores the animations parameters.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation_Controller()
// Class constructor. You don't need to instantiate this as it is used by
// the system.
//------------------------------------------------------------------------
function Legato_Animation_Controller()
{

	// Store the default values.
	this.move              = { to:    new Legato_Structure_Point(),
	                           by:    new Legato_Structure_Point(),
							   ease:  Legato_Animation.EASE_NONE };

	this.width             = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.height            = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.opacity           = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.background_color  = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.border_color      = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.text_color        = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.delay             = 0;

}


//------------------------------------------------------------------------
// Class: Legato_Animation
// Holds a single animation for an element and the necessary methods to
// handle it.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class Constants
//------------------------------------------------------------------------
Legato_Animation.EASE_NONE          = 0;
Legato_Animation.EASE_IN            = 1;
Legato_Animation.EASE_OUT           = 2;
Legato_Animation.EASE_BOTH          = 3;
Legato_Animation.STRONG_EASE_IN     = 4;
Legato_Animation.STRONG_EASE_OUT    = 5;
Legato_Animation.STRONG_EASE_BOTH   = 6;
Legato_Animation.BACK_EASE_IN       = 7;
Legato_Animation.BACK_EASE_OUT      = 8;
Legato_Animation.BACK_EASE_BOTH     = 9;
Legato_Animation.BOUNCE_EASE_IN     = 10;
Legato_Animation.BOUNCE_EASE_OUT    = 11;
Legato_Animation.BOUNCE_EASE_BOTH   = 12;
Legato_Animation.ELASTIC_EASE_IN    = 13;
Legato_Animation.ELASTIC_EASE_OUT   = 14;
Legato_Animation.ELASTIC_EASE_BOTH  = 15;


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation()
// Class constructor.
//
// Parameters:
//     element - The DOM element that you'd like to animate.
//     run_time - The length that you'd like the animation to run.
//------------------------------------------------------------------------
function Legato_Animation( element, run_time )
{

	// Store the values.
	this.element               = $( element );
	this.element_properties    = Object();
	this.run_time              = run_time;
	this.controller            = new Legato_Animation_Controller();

	this.onStart               = null;
	this.onInterval            = null;
	this.onAdvance             = null;
	this.onEventFrame          = null;
	this.onStop                = null;
	this.onFinish              = null;

	// These values are used by the Legato_Animation system internally.
	this.status                = false;
	this.event_frames          = new Array();

	this.start_time            = null;
	this.current_time          = null;

	this.begin_width           = null;
	this.begin_height          = null;
	this.begin_pos             = null;
	this.begin_back_color      = null;
	this.begin_border_color    = null;
	this.begin_text_color      = null;
	this.begin_opacity         = null;

	this.offset_width          = null;
	this.offset_height         = null;
	this.offset_pos            = new Legato_Structure_Point();
	this.offset_back_color     = new Legato_Structure_Color();
	this.offset_border_color   = new Legato_Structure_Color();
	this.offset_text_color     = new Legato_Structure_Color();
	this.offset_opacity        = null;

	this.desired_width         = null;
	this.desired_height        = null;
	this.desired_pos           = new Legato_Structure_Point();
	this.desired_back_color    = null;
	this.desired_border_color  = null;
	this.desired_text_color    = null;
	this.desired_opacity       = null;

}


//------------------------------------------------------------------------
// Function: addEventFrame()
// Adds an event frame to the animation.
//
// Parameters:
//      time_offset - The offset from the beginning of the animation that
//                    you'd like this event to be fired.
//      event_func - The function that you'd like to execute.
//------------------------------------------------------------------------
Legato_Animation.prototype.addEventFrame = function( time_offset, event_func )
{

  // Add the new event frame to the animation.
	this.event_frames.push( { time_offset: time_offset, event_func: event_func, triggered: false } );

}


//------------------------------------------------------------------------
// Function: start()
// Sets the animation to start playing.
//------------------------------------------------------------------------
Legato_Animation.prototype.start = function()
{

	// Don't start if in the middle of playing.
	if ( this.status ) return;

	// Set the necessary values.
	this.status      = true;
	this.start_time  = new Date();

	// Width.
	if ( this.controller.width.to != null || this.controller.width.by != null )
	{

		// Get the element's width.
		this.begin_width = this.element.dimensions()[0];

		// Get the desired width and offset width.
		this.desired_width  = (this.controller.width.to != null) ? (this.controller.width.to) : (this.begin_width + this.controller.width.by);
		this.offset_width   = this.desired_width - this.begin_width;

	}

	// Height.
	if ( this.controller.height.to != null || this.controller.height.by != null )
	{

		// Get the element's height.
		this.begin_height = this.element.dimensions()[1];

		// Get the desired height and offset height.
		this.desired_height  = (this.controller.height.to != null) ? (this.controller.height.to) : (this.begin_height + this.controller.height.by);
		this.offset_height   = this.desired_height - this.begin_height;

	}

	// Position.
	if ( this.controller.move.to.X != null || this.controller.move.to.Y != null || this.controller.move.by.X != null || this.controller.move.by.Y != null )
	{

		// Get the element's position.
		this.begin_pos = this.element.position();
		this.begin_pos = new Legato_Structure_Point( this.begin_pos[0], this.begin_pos[1] );

		// Get the desired X position and X offset position.
		if ( this.controller.move.to.X != null || this.controller.move.by.X != null )
		{

			this.desired_pos.X  = (this.controller.move.to.X != null) ? (this.controller.move.to.X) : (this.begin_pos.X + this.controller.move.by.X);
			this.offset_pos.X   = this.desired_pos.X - this.begin_pos.X;

		}

		// Get the desired Y position and Y offset position.
		if ( this.controller.move.to.Y != null || this.controller.move.by.Y != null )
		{

			this.desired_pos.Y  = (this.controller.move.to.Y != null) ? (this.controller.move.to.Y) : (this.begin_pos.Y + this.controller.move.by.Y);
			this.offset_pos.Y   = this.desired_pos.Y - this.begin_pos.Y;

		}

	}

	// Opacity.
	if ( this.controller.opacity.to != null || this.controller.opacity.by != null )
	{
		
		// Get the element's opacity.
		this.begin_opacity = this.element.getStyle( 'opacity' );
		
		// Get the desired opacity and offset opacity.
		this.desired_opacity  = (this.controller.opacity.to != null) ? (this.controller.opacity.to) : (this.begin_opacity + this.controller.opacity.by);
		this.offset_opacity   = this.desired_opacity - this.begin_opacity;
		
	}

	// Background Color.
	if ( this.controller.background_color.to.R != null || 
	     this.controller.background_color.to.G != null || 
		 this.controller.background_color.to.B != null || 
	     this.controller.background_color.by.R != null ||
		 this.controller.background_color.by.G != null ||
		 this.controller.background_color.by.B != null )
	{
		
		// Get the element's background color.
		this.begin_back_color   = new Legato_Structure_Color( this.element.getStyle( 'background-color' ).substring( 1 ) );
		this.desired_back_color = new Legato_Structure_Color( this.begin_back_color.toHexString() );

		// Get the desired red value and offset value.
		if ( this.controller.background_color.to.R != null || this.controller.background_color.by.R != null )
		{

			this.desired_back_color.R  = (this.controller.background_color.to.R != null) ? (this.controller.background_color.to.R) : (this.begin_back_color.R + this.controller.background_color.by.R);
			this.offset_back_color.R   = this.desired_back_color.R - this.begin_back_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.background_color.to.G != null || this.controller.background_color.by.G != null )
		{

			this.desired_back_color.G  = (this.controller.background_color.to.G != null) ? (this.controller.background_color.to.G) : (this.begin_back_color.G + this.controller.background_color.by.G);
			this.offset_back_color.G   = this.desired_back_color.G - this.begin_back_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.background_color.to.B != null || this.controller.background_color.by.B != null )
		{

			this.desired_back_color.B  = (this.controller.background_color.to.B != null) ? (this.controller.background_color.to.B) : (this.begin_back_color.B + this.controller.background_color.by.B);
			this.offset_back_color.B   = this.desired_back_color.B - this.begin_back_color.B;

		}

	}

	// Border Color.
	if ( this.controller.border_color.to.R != null || 
	     this.controller.border_color.to.G != null || 
		 this.controller.border_color.to.B != null || 
	     this.controller.border_color.by.R != null ||
		 this.controller.border_color.by.G != null ||
		 this.controller.border_color.by.B != null )
	{
		
		// Get the element's border color.
		this.begin_border_color   = new Legato_Structure_Color( this.element.getStyle( 'border-color' ).substring( 1 ) );
		this.desired_border_color = new Legato_Structure_Color( this.begin_border_color.toHexString() );

		// Get the desired red value and offset value.
		if ( this.controller.border_color.to.R != null || this.controller.border_color.by.R != null )
		{

			this.desired_border_color.R  = (this.controller.border_color.to.R != null) ? (this.controller.border_color.to.R) : (this.begin_border_color.R + this.controller.border_color.by.R);
			this.offset_border_color.R   = this.desired_border_color.R - this.begin_border_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.border_color.to.G != null || this.controller.border_color.by.G != null )
		{

			this.desired_border_color.G  = (this.controller.border_color.to.G != null) ? (this.controller.border_color.to.G) : (this.begin_border_color.G + this.controller.border_color.by.G);
			this.offset_border_color.G   = this.desired_border_color.G - this.begin_border_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.border_color.to.B != null || this.controller.border_color.by.B != null )
		{

			this.desired_border_color.B  = (this.controller.border_color.to.B != null) ? (this.controller.border_color.to.B) : (this.begin_border_color.B + this.controller.border_color.by.B);
			this.offset_border_color.B   = this.desired_border_color.B - this.begin_border_color.B;

		}

	}

	// Text Color.
	if ( this.controller.text_color.to.R != null || 
	     this.controller.text_color.to.G != null || 
		 this.controller.text_color.to.B != null || 
	     this.controller.text_color.by.R != null ||
		 this.controller.text_color.by.G != null ||
		 this.controller.text_color.by.B != null )
	{

		// Get the element's text color.
		this.begin_text_color   = new Legato_Structure_Color( this.element.getStyle( 'color' ).substring( 1 ) );
		this.desired_text_color = new Legato_Structure_Color( this.begin_text_color.toHexString() );
		
		// Get the desired red value and offset value.
		if ( this.controller.text_color.to.R != null || this.controller.text_color.by.R != null )
		{

			this.desired_text_color.R  = (this.controller.text_color.to.R != null) ? (this.controller.text_color.to.R) : (this.begin_text_color.R + this.controller.text_color.by.R);
			this.offset_text_color.R   = this.desired_text_color.R - this.begin_text_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.text_color.to.G != null || this.controller.text_color.by.G != null )
		{

			this.desired_text_color.G  = (this.controller.text_color.to.G != null) ? (this.controller.text_color.to.G) : (this.begin_text_color.G + this.controller.text_color.by.G);
			this.offset_text_color.G   = this.desired_text_color.G - this.begin_text_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.text_color.to.B != null || this.controller.text_color.by.B != null )
		{

			this.desired_text_color.B  = (this.controller.text_color.to.B != null) ? (this.controller.text_color.to.B) : (this.begin_text_color.B + this.controller.text_color.by.B);
			this.offset_text_color.B   = this.desired_text_color.B - this.begin_text_color.B;

		}

	}

	// Call the onStart function if there is any.
	if ( this.onStart != null )
	  this.onStart( this );

	// Call the incrementAnimation function. It will start the animation.
	Legato_Animation_Manager.addAnimation( this );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceWidth()
// Advances the width.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceWidth = function()
{

	// Get the new width.
	var new_width = Legato_Animation.tweenValue( this.controller.width.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_width, this.offset_width );

	// Bounds.
	new_width = Math.max( new_width, 0 );

	// Set the new width on the element.
	this.element.dimensions( Math.ceil( new_width ), null );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceHeight()
// Advances the height.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceHeight = function()
{

	// Get the new height.
	var new_height = Legato_Animation.tweenValue( this.controller.height.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_height, this.offset_height );
	
	// Bounds.
	new_height = Math.max( new_height, 0 );
	
	// Set the new height on the element.
	this.element.dimensions( null, Math.ceil( new_height ) );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advancePosition()
// Advances the position.
//------------------------------------------------------------------------
Legato_Animation.prototype.advancePosition = function()
{

	// Updating X position?
	if ( this.offset_pos.X != null )
	{

		// Get the new X position.
		var new_X_pos = Legato_Animation.tweenValue( this.controller.move.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_pos.X, this.offset_pos.X );
		
		// Bounds.
		new_X_pos = Math.max( new_X_pos, 0 );

		// Set the new X position on the element.
		this.element.position( Math.ceil( new_X_pos ), null );

	}  // End if updating X position.

	// Updating Y position?
	if ( this.offset_pos.Y != null )
	{

		// Get the new Y position.
		var new_Y_pos = Legato_Animation.tweenValue( this.controller.move.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_pos.Y, this.offset_pos.Y );
		
		// Bounds.
		new_Y_pos = Math.max( new_Y_pos, 0 );

		// Set the new Y position on the element.
		this.element.position( null, Math.ceil( new_Y_pos ) );

	}  // End if updating Y position.

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceOpacity()
// Advances the opacity.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceOpacity = function()
{

	// Get the new opacity.
	var new_opacity = (Legato_Animation.tweenValue( this.controller.opacity.ease, (this.current_time - this.controller.delay), this.run_time, (this.begin_opacity * 100), (this.offset_opacity * 100) ) / 100);
	
	// Bounds.
	new_opacity = Math.min( Math.max( new_opacity, 0 ), 1 );
	
	// Set the new opacity on the element.
	this.element.setStyle( 'opacity', new_opacity );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceBackgroundColor()
// Advances the background color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceBackgroundColor = function()
{

	// Set the new back color as the beginning color.
	var new_back_color = new Legato_Structure_Color( this.begin_back_color.toHexString() );

	// Updating red value?
	if ( this.offset_back_color.R != null )
	{

		// Get the new background color.
		new_back_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.R, this.offset_back_color.R ) );
	
	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_back_color.G != null )
	{

		// Get the new background color.
		new_back_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.G, this.offset_back_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_back_color.B != null )
	{

		// Get the new background color.
		new_back_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.B, this.offset_back_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_back_color.R = Math.min( Math.max( new_back_color.R, 0 ), 255 );
	new_back_color.G = Math.min( Math.max( new_back_color.G, 0 ), 255 );
	new_back_color.B = Math.min( Math.max( new_back_color.B, 0 ), 255 );
	
	// Set the new background color on the element.
	this.element.setStyle( 'background-color', '#' + new_back_color.toHexString() );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceBorderColor()
// Advances the border color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceBorderColor = function()
{

	// Set the new border color as the beginning color.
	var new_border_color = new Legato_Structure_Color( this.begin_border_color.toHexString() );

	// Updating red value?
	if ( this.offset_border_color.R != null )
	{

		// Get the new border color.
		new_border_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.R, this.offset_border_color.R ) );

	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_back_color.G != null )
	{

		// Get the new border color.
		new_border_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.G, this.offset_border_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_border_color.B != null )
	{

		// Get the new border color.
		new_border_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.B, this.offset_border_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_border_color.R = Math.min( Math.max( new_border_color.R, 0 ), 255 );
	new_border_color.G = Math.min( Math.max( new_border_color.G, 0 ), 255 );
	new_border_color.B = Math.min( Math.max( new_border_color.B, 0 ), 255 );

	// Set the new border color on the element.
	this.element.setStyle( 'border-color', '#' + new_border_color.toHexString() );
	
}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceTextColor()
// Advances the text color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceTextColor = function()
{
	
	// Set the new text color as the beginning color.
	var new_text_color = new Legato_Structure_Color( this.begin_text_color.toHexString() );

	// Updating red value?
	if ( this.offset_text_color.R != null )
	{
		
		// Get the new text color.
		new_text_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.R, this.offset_text_color.R ) );

	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_text_color.G != null )
	{

		// Get the new text color.
		new_text_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.G, this.offset_text_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_text_color.B != null )
	{

		// Get the new text color.
		new_text_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.B, this.offset_text_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_text_color.R = Math.min( Math.max( new_text_color.R, 0 ), 255 );
	new_text_color.G = Math.min( Math.max( new_text_color.G, 0 ), 255 );
	new_text_color.B = Math.min( Math.max( new_text_color.B, 0 ), 255 );

	// Set the new text color on the element.
	this.element.setStyle( 'color', '#' + new_text_color.toHexString() );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceFrame()
// Carries out the next frame of the animation.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceFrame = function()
{

	// If the animation is stopped, return false.
	if ( !this.status )
	  return false;

	// Update the current time.
	this.current_time = new Date() - this.start_time;

	// Only start incrementing if we have passed the delay time.
	if ( this.current_time > this.controller.delay )
	{
		
		// Animating width?
		if ( this.desired_width != null )
			this.advanceWidth();

		// Animating height?
		if ( this.desired_height != null )
			this.advanceHeight();

		// Animating position?
		if ( this.desired_pos.X != null || this.desired_pos.Y != null )
			this.advancePosition();

		// Animating opacity?
		if ( this.desired_opacity != null )
			this.advanceOpacity();

		// Animating background color?
		if ( this.desired_back_color != null )
			this.advanceBackgroundColor();

		// Animating border color?
		if ( this.desired_border_color != null )
			this.advanceBorderColor();

		// Animating text color?
		if ( this.desired_text_color != null )
			this.advanceTextColor();
			
		// Loop through each event frame.
		var event_triggered = false;
		for ( var i = 0; i < this.event_frames.length; i++ )
		{

			// If it is time (or passed time) to trigger the event, do so.
			if ( !this.event_frames[i].triggered && this.current_time >= this.controller.delay + this.event_frames[i].time_offset )
			{

			  this.event_frames[i].event_func( this );
				this.event_frames[i].triggered = true;
				event_triggered = true;

			}

		}  // Next event frame.

		// If an event was triggered and there's an onEventFrame function, call it.
		if ( event_triggered && this.onEventFrame )
		  this.onEventFrame( this );

		// Call the onAdvance function if there is any.
		if ( this.onAdvance )
			this.onAdvance( this );

	}  // End if delay is over.

	// Call the onAdvance function if there is any.
	if ( this.onInterval )
		this.onInterval( this );

	// Should we continue processing?
	if ( this.current_time < this.run_time + this.controller.delay )
		return true;
	else
		return false;

}


//------------------------------------------------------------------------
// Function: stop()
// Stops the animation where it currently is. Does not finish it.
//------------------------------------------------------------------------
Legato_Animation.prototype.stop = function()
{

	// Set the animation's status to not playing.
	this.status = false;

	// Call the onStop function if there is any.
	if ( this.onStop )
	  this.onStop( this );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: finish()
// Does the required clean up of the animation.
//------------------------------------------------------------------------
Legato_Animation.prototype.finish = function()
{

	// Set the animation's status to not playing.
	this.status = false;
	
	// Get rid of any animation errors. Set the desired values on the elements.
	if ( this.desired_width        ) this.element.dimensions( this.desired_width, null );
	if ( this.desired_height       ) this.element.dimensions( null, this.desired_height );

	if ( this.desired_pos.X        ) this.element.position( this.desired_pos.X, null );
	if ( this.desired_pos.Y        ) this.element.position( null, this.desired_pos.Y );

	if ( this.desired_opacity      ) this.element.setStyle( 'opacity', this.desired_opacity );

	if ( this.desired_back_color   ) this.element.setStyle( 'background-color', '#' + this.desired_back_color.toHexString() );

	if ( this.desired_border_color ) this.element.setStyle( 'border-color', '#' + this.desired_border_color.toHexString() );

	if ( this.desired_text_color   ) this.element.setStyle( 'color', '#' + this.desired_text_color.toHexString() );

	// Call the onFinish function if there is any.
	if ( this.onFinish )
	  this.onFinish( this );

}


//------------------------------------------------------------------------
// Public Static Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Function: tweenValue()
// Tweens the value.
//------------------------------------------------------------------------
Legato_Animation.tweenValue = function( ease_type, current_time, duration, begin_val, change_val )
{
	
	// What easing equation?
	switch( ease_type )
	{
		
	// EASE NONE
	case Legato_Animation.EASE_NONE:	
		return change_val * (current_time / duration) + begin_val;
		
	// EASE IN
	case Legato_Animation.EASE_IN:
		return change_val * (current_time /= duration) * current_time + begin_val;
		
	// EASE OUT
	case Legato_Animation.EASE_OUT:
		return -change_val * (current_time /= duration) * (current_time - 2) + begin_val;
		
	// EASE BOTH
	case Legato_Animation.EASE_BOTH:
		
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * current_time * current_time + begin_val;

		return -change_val / 2 * ((--current_time) * (current_time - 2) - 1) + begin_val;
		
	// STRONG EASE IN
	case Legato_Animation.STRONG_EASE_IN:
		return change_val * (current_time /= duration) * current_time * current_time * current_time + begin_val;
	
	// STRONG EASE OUT	
	case Legato_Animation.STRONG_EASE_OUT:
		return -change_val * ((current_time = current_time / duration - 1) * current_time * current_time * current_time - 1) + begin_val;
		
	// STRONG EASE BOTH
	case Legato_Animation.STRONG_EASE_BOTH:
	
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * current_time * current_time * current_time * current_time + begin_val;

		return -change_val / 2 * ((current_time -= 2) * current_time * current_time * current_time - 2) + begin_val;
		
	// BACK EASE IN
	case Legato_Animation.BACK_EASE_IN:
		return change_val * (current_time /= duration) * current_time * (2.70158 * current_time - 1.70158) + begin_val;
		
	// BACK EASE OUT
	case Legato_Animation.BACK_EASE_OUT:
		return change_val * ((current_time = current_time / duration - 1) * current_time * (2.70158 * current_time + 1.70158) + 1) + begin_val;
		
	// BACK EASE BOTH
	case Legato_Animation.BACK_EASE_BOTH:
		
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * (current_time * current_time * (3.5949095 * current_time - 2.5949095)) + begin_val;

		return change_val / 2 * ((current_time -= 2) * current_time * (3.5949095 * current_time + 2.5949095) + 2) + begin_val;
		
	// BOUNCE EASE IN
	case Legato_Animation.BOUNCE_EASE_IN:
		
		current_time = duration - current_time;

		if ( (current_time /= duration) < (1 / 2.75) )
			return change_val - (change_val * (7.5625 * current_time * current_time)) + begin_val;
		else if ( current_time < (2 / 2.75 ) )
			return change_val - (change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75)) + begin_val;
		else if ( current_time < (2.5 / 2.75) )
			return change_val - (change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375)) + begin_val;
		else
			return change_val - (change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375)) + begin_val;
		
	// BOUNCE EASE OUT	
	case Legato_Animation.BOUNCE_EASE_OUT:
	
		if ( (current_time /= duration) < (1 / 2.75) )
		  return change_val * (7.5625 * current_time * current_time) + begin_val;
		else if ( current_time < (2 / 2.75 ) )
		  return change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75) + begin_val;
		else if ( current_time < (2.5 / 2.75) )
		  return change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375) + begin_val;
		else
		  return change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375) + begin_val;
		  
	// BOUNCE EASE BOTH
	case Legato_Animation.BOUNCE_EASE_BOTH:
	
		if ( current_time < duration / 2 )
		{

			current_time = duration - (current_time * 2);

			if ( (current_time /= duration) < (1 / 2.75) )
				return (change_val - (change_val * (7.5625 * current_time * current_time))) * 0.5 + begin_val;
			else if ( current_time < (2 / 2.75 ) )
				return (change_val - (change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75))) * 0.5 + begin_val;
			else if ( current_time < (2.5 / 2.75) )
				return (change_val - (change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375))) * 0.5 + begin_val;
			else
				return (change_val - (change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375))) * 0.5 + begin_val;

		}

		current_time = current_time * 2 - duration;

		if ( (current_time /= duration) < (1 / 2.75) )
		  return change_val * (7.5625 * current_time * current_time) * 0.5 + change_val * 0.5 + begin_val;
		else if ( current_time < (2 / 2.75 ) )
		  return change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75) * 0.5 + change_val * 0.5 + begin_val;
		else if ( current_time < (2.5 / 2.75) )
		  return change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375) * 0.5 + change_val * 0.5 + begin_val;
		else
		  return change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375) * 0.5 + change_val * 0.5 + begin_val;
		  
	// ELASTIC EASE IN
	case Legato_Animation.ELASTIC_EASE_IN:
	
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration) == 1 ) 
			return begin_val + change_val;

		var p = duration * 0.3;
		var a = change_val;
		var s = p / 4;

		return -(a * Math.pow( 2, 10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p )) + begin_val;
		
	// ELASTIC EASE OUT
	case Legato_Animation.ELASTIC_EASE_OUT:
		
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration) == 1 ) 
			return begin_val + change_val;

		var p = duration * 0.3;
		var a = change_val;
		var s = p / 4;

		return a * Math.pow( 2, -10 * current_time ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p ) + change_val + begin_val;
		
	// ELASTIC EASE BOTH
	case Legato_Animation.ELASTIC_EASE_BOTH:
	
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration / 2) == 2 ) 
			return begin_val + change_val;

		var p = duration * (0.3 * 1.5);
		var a = change_val;
		var s = p / 4;

		if ( current_time < 1 ) return -0.5 * (a * Math.pow( 2, 10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p )) + begin_val;

		return a * Math.pow( 2, -10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p ) * 0.5 + change_val + begin_val;
		
	}

}


//------------------------------------------------------------------------
// Class: Legato_Animation_Sequence
// Stores a sequence of <Legato_Animation> objects.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Static Variables
//------------------------------------------------------------------------
Legato_Animation_Sequence.sequences = new Array();


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation_Sequence()
// Class constructor.
//
// Parameters:
//     options - An optional object of options for the Animation Sequence.
//------------------------------------------------------------------------
function Legato_Animation_Sequence( options )
{

	// Store the default values.
	this.animations               = new Array();
	this.current_animation_index  = 0;
	this.sequence_index           = Legato_Animation_Sequence.sequences.length;
	this.status                   = false;
	this.options                  = options;
	
	// Callbacks.
	this.onStart               = null;
	this.onAdvance             = null;
	this.onLoop                = null;
	this.onFinish              = null;

	// Store this animation sequence in the global sequences array.
	Legato_Animation_Sequence.sequences[this.sequence_index] = this;

}


//------------------------------------------------------------------------
// Function: addAnimation()
// Adds an <Legato_Animation> object to the animation sequence.
//
// Parameters:
//     animation - An <Legato_Animation> object that you would like to set up
//                 to play in the animation. Will add it at the end of the
//                 sequence.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.addAnimation = function( animation )
{

	// Store the animation in the sequence.
	this.animations.push( animation );

	// Add the onFinish and onStop functions.
	Legato_Events_Handler.addEvent( animation, "onFinish", Legato_Animation_Sequence.nextAnimation );
	Legato_Events_Handler.addEvent( animation, "onStop", Legato_Animation_Sequence.nextAnimation );

	// Store the sequence index in the animation.
	animation.sequence_index = this.sequence_index;

}


//------------------------------------------------------------------------
// Function: start()
// Sets the Animation Sequence to start playing.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.start = function()
{

	// Only start if there is at least one animation in the sequence
	// and we are not already playing.
	if ( this.animations.length == 0 || this.status ) return;

	// Set as playing.
	this.status = true;
	
	// On start callback.
	if ( this.onStart != null && this.onStart( this ) == false )
		return;

	// Start the first animation in the sequence.
	this.animations[0].start();

}


//------------------------------------------------------------------------
// (Exclude)
// Function: reset()
// Cleans up the animation sequence.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.reset = function()
{

	// Reset the values.
	this.status                   = false;
	this.current_animation_index  = 0;

}


//------------------------------------------------------------------------
// Public Static Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Function: nextAnimation()
// Plays the next animation in the sequence. This is set as the previous
// animation's onFinish function so that a chain forms.
//------------------------------------------------------------------------
Legato_Animation_Sequence.nextAnimation = function( animation )
{

	// Get the animation sequence.
	var animation_sequence = Legato_Animation_Sequence.sequences[animation.sequence_index];

	// If the animation sequence is stopped, return false.
	if ( !animation_sequence.status )
	  return false;

	// Increment the current animation index.
	animation_sequence.current_animation_index++;

	// Play the next animation if there is one.
	if ( animation_sequence.animations[animation_sequence.current_animation_index] != null )
	{
		
		// On advance callback.
		if ( animation_sequence.onAdvance != null && animation_sequence.onAdvance( animation_sequence ) == false )
			return;

		// Start the next animation.
		animation_sequence.animations[animation_sequence.current_animation_index].start();

	}  // End if next animation.
	else
	{
		
		// Should we loop?
		if ( animation_sequence.options && animation_sequence.options.loop == true )
		{
			
			// On loop callback.
			if ( animation_sequence.onLoop != null && animation_sequence.onLoop( animation_sequence ) == false )
				return;
				
			// Loop.
			animation_sequence.reset();
			animation_sequence.start();
			
		}  // End if looping.
		else
		{

			// Finish the animation sequence.
			animation_sequence.reset();
			
			// On finish callback.
			if ( animation_sequence.onFinish != null )
				animation_sequence.onFinish( animation_sequence );
			
		}

	}  // End if no more animations.

}


//------------------------------------------------------------------------
// (Exclude)
// Class: Legato_Animation_Manager
// Manages each animation. All the animations are incremented through the
// manager.
//------------------------------------------------------------------------
Legato_Animation_Manager =
{

	//----------------------------------------------------------------------
	// Public Variables
	//----------------------------------------------------------------------
	increment_speed:     20,           // The speed at which the animation manager will increment each animation.
	playing_animations:  new Array(),  // An array of all the currently playing animations.
	interval_handle:     null,         // The handle that the setInterval() function returns.


	//----------------------------------------------------------------------
	// Public Member Functions
	//----------------------------------------------------------------------
	//----------------------------------------------------------------------
	// (Exclude)
	// Function: addAnimation()
	// This function is used to add an animation to the animation manager
	// for playing.
	//----------------------------------------------------------------------
	addAnimation: function( animation )
	{

		// Loop through each animation being played.
		for ( var i = 0; i < this.playing_animations.length; i++ )
		{

			// Get the animation.
			var playing_animation = this.playing_animations[i];

			// Is the animation we're adding animate the
			// same element than this animation's element?
			if ( animation.element == playing_animation.element )
			{

				// Remove the animation from the playing animations array.
				this.playing_animations.splice( i, 1 );

				// Stop the currently playing animation so that we can play this one.
				playing_animation.stop();

			}  // End if managing the same element.

		}  // Next playing animation.

		// Add the animation to the playing animations array.
		this.playing_animations.push( animation );

		// If we don't have any animations playing,
		// we have to set up the timeout.
		if ( this.interval_handle == null )
		{

			// Set to advanced all animations.
			this.interval_handle = setInterval( Legato_Animation_Manager.advanceAnimations, this.increment_speed, null );

		}  // End if no animations currently playing.

	},


	//------------------------------------------------------------------------
	// (Exclude)
	// Function: advanceAnimations()
	// Advances each animation being played.
	//------------------------------------------------------------------------
	advanceAnimations: function()
	{

		// Loop through each animation being played.
		for ( var i = 0; i < Legato_Animation_Manager.playing_animations.length; i++ )
		{

			// Get the animation from the array.
			var animation = Legato_Animation_Manager.playing_animations[i];

			// Advance the animation.
			var continue_playing = animation.advanceFrame();

			// Is the animation done playing?
			if ( !continue_playing )
			{
				
				// Remove the animation from the playing animations array.
				Legato_Animation_Manager.playing_animations.splice( i, 1 );
				
				// Finish up the animation.
				animation.finish();
				
				// If we don't have any more animations to play, stop
				// JavaScript from calling this function again.
				if ( Legato_Animation_Manager.playing_animations.length == 0 )
				{
					clearInterval( Legato_Animation_Manager.interval_handle );
					Legato_Animation_Manager.interval_handle = null;
				}

			}  // End if stop playing this animation.

		}  // Next playing animation.

	}

}



/*
	Class: Legato_Widgets_DynamicTable
	Make a table dynamic, with the ability to sort, search, pull data in
	from a data source, etc.
*/


/*
	Group: Functions
*/

/*
	Function: Legato_Widgets_DynamicTable()
	The class constructor.
	
	Syntax:
		object Legato_Widgets_DynamicTable( mixed table_id )
		
	Parameters:
		mixed table_id - Can be either the ID of your table element, or an element reference.
		
	Notes:
		You must call the <Legato_Widgets_DynamicTable::initialize()> function on this
		object to actually initialize it.
						
	Examples:
		> var table = new Legato_Widgets_DynamicTable( 'cool_table' );
	
	See Also:
		- <Legato_Widgets_DynamicTable::initialize()>
*/
function Legato_Widgets_DynamicTable( table_id )
{
	
	this.table_element = $$( table_id );
	this.head_element = null;
	this.body_element = null;
	this.table_data = [];
	this.last_clicked = null;
	this.direction = 'asc';
	this.unsortable = {};
	this.initialized = false;
	this.data_source = '';
	
	this.search_field = null;
	this.search_timeout = null;
	
	// Events.
	this.onPreSort = null;
	this.onPostSort = null;
	this.onPreUpdateData = null;
	this.onPostUpdateData = null;
	this.onInitSearchTimeout = null;
	this.onSendDataSearch = null;

}


/*
	Function: initialize()
	Called to actually set up and initialize the table to be dynamic.
	
	Syntax:
		void initialize( object options = {} )
		
	Parameters:
		object options - *optional* - This is the options object. 
		Defaults to the default options, or a blank options object.
		
	Options:
		These are the options that you are able to pass in to the initialize() function.
	
		- search_field - The ID/element reference of a text input element that you'd like to use
		as a search field.
		
		- sorted_col - If you've pre-sorted the table data, you can use this to pass in the sorted
		column index.
		
		- sorted_dir - If you've pre-sorted the table data, you can use this to pass in the sorted
		column's direction. Can be either 'asc' or 'desc'.
		
		- unsortable - An array of indices for any column's that you wouldn't like to be sortable.
		
		- data_source - A URL that you would like to query when updating the table's data. Note that
		you can define this option if you wouldn't like to sort with JavaScript, but would rather sort/search
		by querying a data source.
						
	Examples:
	(begin code)
		<input type="submit" id="results_search">
	
		<table id="results">
			<thead>
				<tr>
					<th>Name</th>
					<th>Position</th>
				</tr>			
			</thead>
			<tbody>
				<tr>
					<td>David DeCarmine</td>
					<td>Lead Developer</td>
				</tr>
				<tr>
					<td>Trevor Gerhardt</td>
					<td>Programmer</td>
				</tr>
			</tbody>	
		</table>		
	(end)
	
	(begin code)
		var table = new Legato_Widgets_DynamicTable( 'results' );
		table.initialize
		( {		
			search_field: 'results_search',
			sorted_col: 0	
		} );
	(end)
*/
Legato_Widgets_DynamicTable.prototype.initialize = function( options )
{
	
	// Clear out variables.
	this.table_data = [];
	this.last_clicked = null;
	this.direction = 'asc';
	
	// Any search field?
	if ( options && options.search_field != undefined )
		this.search_field = $$( options.search_field );
		
	// Only do this stuff the first time initialization is called.
	if ( !this.initialized )
	{
		
		// Make sure the table is correctly structured.
		this.restructureTable();
		
		// Store the head and body elements for easy access.
		this.head_element = this.table_element.elmsByTag( 'thead' ).first();
		this.body_element = this.table_element.elmsByTag( 'tbody' ).first();
		
		// Add the table's events.
		this.addEvents();
		
	}
	else  // Only do on reinitialization.
	{
		
		// Get the table's headings.
		this.head_element.elmsByTag( 'th' ).each( function()
		{
			$( this ).removeClass( 'sorted_asc' );
			$( this ).removeClass( 'sorted_desc' );
		} );
		
	}
	
	// Initialize the rows.
	this.initRows();
	
	// Any options?
	if ( options )
	{
		
		// Get the table's headings.
		var table_headings = this.table_element.elmsByTag( 'th' );
		
		// Are we already sorted?
		if ( options.sorted_col != undefined )
		{
			if ( options.sorted_dir == undefined )
				options.sorted_dir = 'asc';
			
			this.direction = options.sorted_dir;
			this.last_clicked = options.sorted_col;
			$( table_headings[options.sorted_col] ).addClass( 'sorted_' + this.direction );
		}
		
		// If there's any unsortable columns passed in, make an object out of the
		// unsortable columns so we can test for it quickly.
		if ( options.unsortable != undefined )
			for( var i = 0; i < options.unsortable.length; i++ )
			{
				this.unsortable[options.unsortable[i]] = true;
				$( table_headings[options.unsortable[i]] ).addClass( 'unsortable' );
			}
			
		// Any datasource?
		if ( options.data_source != undefined )
			this.data_source = options.data_source;
		
	}  // End if options passed in.
	
	// Set as initialized.
	this.initialized = true;
	
	// Send the data search.
	if ( this.data_source )
		this.sendDataSearch();
	
}


/*
	(Exclude)
	Function: initRows()
	Stores all the rows' data so that we can sort through it later.
	This should be called any time the table's data is changed manually.
*/
Legato_Widgets_DynamicTable.prototype.initRows = function()
{
	
	this.table_data = [];
	
	// Loop through the table's rows.
	var table_rows = this.body_element.elmsByTag( 'tr' );
	
	for ( var i = 0; i < table_rows.length; i++ )
	{
		
		// Get the columns.
		var table_cols = $( table_rows[i] ).elmsByTag( 'td' );
		
		// Skip empty rows.
		if ( table_cols.length == 0 )
			continue;
		
		// Loop through the cols and store them.
		for ( n = 0; n < table_cols.length; n++ )
		{
			
			// Make sure we have an array for this table data index.
			if ( typeof( this.table_data[n] ) != "object" )
				this.table_data[n] = [];
			
			// Add the column.
			this.table_data[n].push( table_cols[n] );
			
			
		}  // Next col.
		
	}  // Next row.
	
}


/*
	(Exclude)
	Function: initSort()
	Called to sort the table data.
	This will set up the system to sort, either with JavaScript or through
	a data source.
*/
Legato_Widgets_DynamicTable.prototype.initSort = function( col_index )
{
	
	// If this is an unsortable column, don't process.
	if ( this.unsortable[col_index] )
		return;
	
	// Call the onPreSort event.
	if ( this.onPreSort != null && this.onPreSort( col_index ) == false )
		return;
	
	// Get the correct direction that the clicked column is facing.
	if ( (col_index != this.last_clicked) || (col_index == this.last_clicked && this.direction == 'desc') )
		this.direction = 'asc';	
	else
		this.direction = 'desc';
	
	// Are we using a data source?
	if ( this.data_source )
	{
		
		// Set last clicked.
		this.last_clicked = col_index;
		
		// Send off the search request now.
		this.sendDataSearch();
		
	}
	else
	{
		
		// Is this the last clicked column, or a new column to sort?
		if ( col_index != this.last_clicked )
		{
			// Set last clicked and sort.
			this.last_clicked = col_index;			
			this.sort( col_index );
		}
		else
			this.reverse( col_index );
			
		// Update the table with the new information.
		this.updateTable( col_index );
		
	}
	
	// Call the onPostSort event.
	if ( this.onPostSort != null )
		this.onPostSort( col_index );
	
}


/*
	(Exclude)
	Function: sort()
	Called to sort the table data.
	This will call updateTable to update the actual table's rows once the data
	is sorted.
*/
Legato_Widgets_DynamicTable.prototype.sort = function( col_index )
{
	
	// Sort the table data.
	if ( this.table_data[col_index][0].innerText.charAt( 0 ) == "$" )
	{
		
		// The function to use in comparing.
		var sort_func = function ( a, b )
		{
			
			a = a.innerText.replace( /[,\\s]/g, "" );
			b = b.innerText.replace( /[,\\s]/g, "" );
			
			a = new Number( a.substring( 1 ) );
			b = new Number( b.substring( 1 ) );
			
			return (b < a) - (a < b);
		
		}
		
	}  // Money sort.
	else
	{
		
		// The function to use in comparing.
		var sort_func = function ( a, b )
		{
			
			a = a.innerText;
			b = b.innerText;
			
			return (b.charAt( 0 ).toLowerCase() < a.charAt( 0 ).toLowerCase()) - (a.charAt( 0 ).toLowerCase() < b.charAt( 0 ).toLowerCase());
		
		}
		
	}  // Normal sort.
	
	// Sort the data.
	this.table_data[col_index].sort( sort_func );
	
}


/*
	(Exclude)
	Function: reverse()
	Called to simply reverse all the rows in the table.
	This is here so that we can quickly reverse if the user clicks on the same
	column twice. So that when the direction changes, all we do is reverse.
*/
Legato_Widgets_DynamicTable.prototype.reverse = function( col_index )
{
	
	// Reverse the active column.
	this.table_data[col_index].reverse();
	
	// Update the table with the new information.
	this.updateTable( col_index );
	
}


/*
	(Exclude)
	Function: updateTable()
	This is called to reorder the table's rows to match the data modified
	by the sort function.
*/
Legato_Widgets_DynamicTable.prototype.updateTable = function( col_index )
{
	
	var th = this.table_element.elmsByTag( 'th' );
	
	th.each( function()
	{
		$( this ).removeClass( 'sorted_asc' );
		$( this ).removeClass( 'sorted_desc' );
	} );
	
	// Set the class for the column heading.
	th[col_index].addClass( 'sorted_' + this.direction );
	
	// If there is no data for this column, just return.
	if ( !this.table_data[col_index] )
		return;
	
	// Loop through all the rows in the table data and update the table.
	for ( var i = 0; i < this.table_data[col_index].length; i++ )
		this.body_element.appendChild( this.table_data[col_index][i].parentNode );
		
	// Zebra stripe the table.
	this.body_element.cssSelect( 'tr:nth-child(odd)' ).each( function(){ this.addClass( 'row1' ); } );				
	this.body_element.cssSelect( 'tr:nth-child(even)' ).each( function(){ this.addClass( 'row2' ); } );
	
}


/*
	(Exclude)
	Function: sendDataSearch()
	Sends a request out to get data to populate the table.
	Queries the data source passed in when the table was constructed.
	It should receive a JSON request.
*/
Legato_Widgets_DynamicTable.prototype.sendDataSearch = function()
{
	
	// Make sure a data source was passed in.
	if ( !this.data_source )
		return false;
		
	var th = this.head_element.elmsByTag( 'th' );
		
	var params = {};		
	params['query'] = '';
	params['page'] = 1;
	params['column'] = th[this.last_clicked].id;
	params['direction'] = this.direction;
	
	// If there is a search field, try to get the value of it.
	if ( this.search_field )
	{
		this.search_timeout = null;
		params['query'] = this.search_field.value;
	}
	
	// Call the onSendDataSearch event.
	if ( this.onSendDataSearch != null )
		params = this.onSendDataSearch( params );
	
	// Create the query string from the parameters.
	var query_string = '';
	for ( var key in params )
		query_string = query_string + key + '=' + params[key] + '&';
	
	// Send off the request.
	var table = this;
	Legato_RequestManager.makeRequest( this.data_source, function( response ){ table.updateTableData( response ); }, query_string );

}


/*
	(Exclude)
	Function: updateTableData()
	Updates the table with data received from the data source.
*/
Legato_Widgets_DynamicTable.prototype.updateTableData = function( response )
{
	
	var response = eval( '(' + response.responseText + ')' );
	
	// Call the onPreUpdateData event.
	if ( this.onPreUpdateData != null && this.onPreUpdateData( response ) == false )
		return false;
	
	// Remove all the rows.
	this.body_element.elmsByTag( 'tr' ).each( function(){ this.remove(); } );
	
	// Loop through all the rows returned.
	if ( response.rows && response.total_count > 0 )
	{
		for ( var row_id in response.rows )
		{
			
			var row_data = response.rows[row_id];
			var row = this.body_element.create( 'tr', {}, true );
			var new_cells = [];
			
			// Add all the new cells before we populate them with data.
			for ( var i in row_data )
			{
				if ( i == 'class_name' )
					continue;
					
				new_cells.push( row.create( 'td', {}, true ) );
			}
				
			// Now run through the data and populate the correct cells with the data.
			var headings = this.head_element.elmsByTag( 'th' );
			for ( var i in row_data )
			{
				if ( i == 'class_name' )
					continue;
				
				var th = $$( i );	
				new_cells[th.cellIndex].innerHTML = row_data[i];
			}
			
			// Check for a class name.
			if ( row_data['class_name'] )
				$( row ).addClass( row_data['class_name'] );
			
		}
	}
	else
	{
			
		var td_elem = document.createElement( 'td' );
		td_elem.colSpan = this.head_element.elmsByTag( 'th' ).length;
		td_elem.className = 'no_results';
		td_elem.innerHTML = 'No results to show.';
		
		var row_elem = this.body_element.create( 'tr', {}, true ).addContent( td_elem );
	
	}
	
	// Reinitialize the dynamic table.
	this.initRows();
		
	// Call the onPostUpdateData event.
	if ( this.onPostUpdateData != null )
		this.onPostUpdateData( response );
		
	this.updateTable( this.last_clicked );
	
}


/*
	(Exclude)
	Function: initSearchTimeout()
	Initializes the search timeout.
*/
Legato_Widgets_DynamicTable.prototype.initSearchTimeout = function()
{
	
	// Send off the request.
	var table = this;
	
	// If the timeout already started, clear it out so we can start it again.
	if ( this.search_timeout != null )
		window.clearTimeout( this.search_timeout );
	
	// Set the timeout.
	// Delay one second, then send the search.
	this.search_timeout = window.setTimeout( function(){ table.sendDataSearch(); }, 1000 );
	
	// Call the onInitSearchTimeout event.
	if ( this.onInitSearchTimeout != null )
		this.onInitSearchTimeout();

}


/*
	(Exclude)
	Function: addEvents()
	Adds the correct events to the table.
*/
Legato_Widgets_DynamicTable.prototype.addEvents = function()
{
	
	var table = this;
	
	// Set an onclick event for the heading. We'll get the target of the click
	// in the event handler.
	Legato_Events_Handler.addEvent( this.head_element, 'onclick', function( e )
	{ 
		var col_index = Legato_Events_Handler.getTarget( e ).cellIndex;
		table.initSort( col_index ); 
	} );
	
	// Any search field to worry about?
	if ( this.search_field )
	{
		// Set up the timeout event on key up.
		Legato_Events_Handler.addEvent( this.search_field, 'onkeyup', function(){ table.initSearchTimeout(); } );		
	}

}


/*
	(Exclude)
	Function: restructureTable()
	Restructures the table to make sure it's in a good format to work with.
*/
Legato_Widgets_DynamicTable.prototype.restructureTable = function()
{
	
	// Try to get thead and th elements.
	var thead = this.table_element.elmsByTag( 'thead');
	var th = this.table_element.elmsByTag( 'th' )
	
	// If there are THs, make sure there is a THEAD to enclose them.
	if ( thead.length == 0 && th.length != 0 )
	{
		
		var head_tr = th.first().parentNode;
		
		// Add the TR element to a new THEAD element.
		var thead_elem = $( this.table_element ).create( 'thead' ).addContent( head_tr );
		
		// Insert the THEAD element as the first element in the table.
		this.table_element.insertBefore( thead_elem, this.table_element.firstChild );
		
	}
	
}


/*
	Group: Events

	Event: onPreSort( int col_index )
	Called prior to sorting a column. Is passed in the index of the column that's being sorted.
	If the event handler returns false, the column will not be sorted.
	
	Event: onPostSort( int col_index )
	Called after sorting a column. Is passed in the index of the column that's being sorted.
	
	Event: onSendDataSearch( object params )
	Called when sending a data search to a data source. Is passed in the array of parameters
	that will be sent to the data source. This allows you to modify the parameters before they're
	actually sent. Note that you MUST return the parameter object or no parameters will be sent.
	The parameters set in the params object are as follows:
	
	- query - The search string to be sent.
	- page - The page number. Currently is always equal to 1. This allows you to modify it correctly.
	- column - The currently active column's ID.
	- direction - The direction the currently active column is being sorted.
	
	Event: onPreUpdateData( object response )
	Called prior to updating the data from a retrieval from a data source.
	Is passed the response object returned from the data source.
	If you return false, the table will not be updated with the newly retreived data.
	
	Event: onPostUpdateData( object response )
	Called after updating the data from a retrieval from a data source.
	Is passed the response object returned from the data source.
	
	Event: onInitSearchTimeout()
	Called when someone starts typing into the search box specified. If you specified one.
*/