/* Handy Objects
--------------------------------------------------------------------------*/
var Cookie = {
	get: function(name) {
		var cookies = document.cookie.split('; ');
		for (var x=0; x<cookies.length; x++) {
			var cookie = cookies[x].split('=');
			if (cookie[0] == name) return cookie[1];
		}
		return false;
	},

	set: function(name, value, expDate) {
		document.cookie = name+"="+escape(value) + ";expires="+expDate.toGMTString();
	},

	remove: function(name) {
		Cookie.set(name, '', new Date(Date.parse('Thu, 01-Jan-1970 00:00:01 GMT')));
	}
};

var Request = {
	getVar: function(name) {
		var getvars = document.location.search.substring(1).split('&');
		for (var x=0; x<getvars.length; x++) {
			var getvar = getvars[x].split('=');
			if (getvar[0] == name) return getvar[1];
		}
		return false;
	}
};

// Legacy function names
var getCookie = Cookie.get;
var getGet = Request.getVar;

/* Extensions to native JS objects
--------------------------------------------------------------------------*/
Object.extend(String.prototype, {
	/**
	 * Extends the .substring() method to allow negative numbers to
	 * reference indices from the end of the string rather than the beginning.
	 */
	substring: function(start, end) {
		if (start < 0) start = this.length + start;
		if (end < 0) end = this.length + end;
		if (end !== 0 && !end) end = this.length;
		var newString = '';
		for (var ssi=start; ssi<end; ssi++) {
			newString += this.charAt(ssi);
		}
		return newString;
	},

	// Deprecated
	// See Prototype's String.escapeHTML()
	// http://www.prototypejs.org/api/string/escapeHTML
	/**
	 * Converts special characters to their HTML entities
	 */
	 htmlEntities: function() {
		var chars = {
			'&': 'amp',	'<': 'lt', '>': 'gt', '\"': 'quot'
		};

		var newString = this;
		for (var chacacter in chars) {
			var regExp = new RegExp();
			regExp.compile(chacacter,'g');
			newString = newString.replace(regExp, '&'+chars[chacacter]+';');
		}
		return newString;
	},

	// Deprecated
	// Don't hide JS data inside HREF ever again.
	// Use DOM.getCommentData() instead.
	/**
	 * Get Url Argument
	 * Turns a URL into an argument for links which function
	 * purely as event launchers.
	 */
	getUrlArgument: function() {
		// Parses a URL like "javascript:void(42);" and returns "42"
		if (val = this.match(/[Jj]avascript:void\(\'?(.*?)\'?\);?/i)) {
			if (val[1]) return val[1];
		}
		return this;
	},

	// Deprecated by Prototype's String.strip()
	// See: http://www.prototypejs.org/api/string/strip
	/**
	 * Trims a string
	 */
	trim: function() {
		return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
	},

	/**
	 * Is Numeric
	 * checks if a string is a number
	 */
	isNumeric: function () {
		return !this.match(/\D/);
	},

	toDOMNodes: function() {
		var wrapper = document.createElement('div');
		wrapper.innerHTML = this;
		return $(wrapper).immediateDescendants();
	}
});

Object.extend(Form.Element, {
	setValue: function(inputElement, newValue) {
		inputElement = $(inputElement);
		switch(inputElement.type) {
			case 'text':
			case 'password':
				inputElement.value = newValue;
			break;
			case 'select':
			case 'select-one':
				for (var x=0; x<inputElement.options.length; x++) {
					if (inputElement.options[x].value == newValue) {
						inputElement.selectedIndex = x;
						return true;
					}
				}
				return false;
			break;
			case 'checkbox':
				inputElement.checked = bool(newValue);
			break;
			default:
				alert('setValue() can\'t yet handle input type: '+inputElement.type);
				return false;
			break;
		}
		return true;
	}
});

Object.extend(Form.Element, {
	getForm: function(element) {
    element = $(element);
    while (element.tagName != 'FORM') {
    	element = element.parentNode;
    	if (element == document) return false;
    }
    return element;
	}
});

Object.extend(Number.prototype, {
	// Deprecated
	// See Prototype's Number.toPaddedString(length)
	// http://www.prototypejs.org/api/number/toPaddedString
	/**
	 * Pads a number with zeroes until it is the desired length (digits)
	 * (Caution: Returns a string, not a float, out of necessity)
	 * Example: (7).zeroPad(3) == '007'
	 */
	zeroPad: function(digits) {
		var str = this.toString();
		while (str.length < digits) {
			str = '0'+str;
		}
		return str;
	}
});

Object.extend(Array.prototype, {
	// Deprecated
	// See Prototype's Array.without(Array)
	// http://www.prototypejs.org/api/array/without
	/**
	 * Deletes any occurrences of needle from Array
	 * Not recommended for use on multidimensional arrays.
	 */
	deleteVals: function(needle) {
		var newValue = [];
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] != needle) newValue.push(this[n]);
			}
		}
		return newValue;
	},

	// Deprecated
	// See Prototype's Array.uniq()
	// http://www.prototypejs.org/api/array/uniq
	/**
	 * Weeds out duplicate values in an array.
	 * Not recommended for use on multidimensional arrays.
	 */
	unique: function() {
		newArray = new Array();
		for (key in this) {
			if (typeof(this[key]) != 'function') {
				if (newArray.inArray(this[key]) == -1) newArray.push(this[key]);
			}
		}
		return newArray;
	},

	// Deprecated
	// See JavaScript's native Array.indexOf()
	/**
	 * In Array
	 * Finds a value in an array
	 */
	inArray: function(needle) {
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] == needle) return n;
			}
		}
		return -1;
	}
});



/* Patches to Prototype
--------------------------------------------------------------------------*/
Object.extend(Position, {
	distanceBetween: function(coords0, coords1) {
		// Determines the straight distance between two points.
		// coords1 = [x: 1, y: 1]
		// or
		// coords1 = [1,1]
		if (coords0 instanceof Array) {
			var x0=coords0[0];
			var y0=coords0[1];
		} else {
			var x0=coords0.x;
			var y0=coords0.y;
		}
		if (coords1 instanceof Array) {
			var x1=coords1[0];
			var y1=coords1[1];
		} else {
			var x1=coords1.x;
			var y1=coords1.y;
		}
		return Math.sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1))
	}
});

Object.extend(Object, {
	extendProperties: function(originalObject, extendingObject) {
		// Tries to intelligently extend an object's properties based
		// on direction given in the property names of the extending
		// object.
		for (var originalKeyName in extendingObject) {
			var modifier = originalKeyName.substring(0,1);
			var keyName = originalKeyName.substring(1);
			if (extendingObject[originalKeyName] instanceof Array) {
				// - Prune: Remove from it
				// + Merge: Add to it
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var x=0; x<extendingObject[originalKeyName].length; x++) {
							originalObject[keyName] = originalObject[keyName].deleteVals(extendingObject[originalKeyName][x]);
						}
					break;
					case '+':
						originalObject[keyName] = originalObject[keyName].concat(extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else if (extendingObject[originalKeyName] instanceof Object) {
				// - Prune: Remove from it
				// + Merge: Add to it or overwrite preexisting conflicting values.
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var key in extendingObject[originalKeyName]) {
							if (typeof(Object.prototype[key]) == 'undefined') {
								delete(originalObject[keyName][key]);
							}
						}
					break;
					case '+':
						Object.extend(originalObject[keyName], extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else {
				originalObject[originalKeyName] = extendingObject[originalKeyName];
			}
		}
	}
});

Object.extend(Element, {
	/**
	 * Tons faster and simpler than Prototype's
	 */
	hasClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'find');
	},

	addClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'add');
	},

	removeClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'remove');
	},

	clearClassNames: function(element) {
		element.className = '';
		return element;
	},

	manipulateClass: function(element, className, action) {
		if (typeof(element.tagName) != 'undefined') {
			var classes = element.className;
			if (action == 'add') {
				if (!Element.hasClassName(element, className)) {
					element.className += ' '+className;
					return element;
				}
			}
			var cNames = classes.split(' ');
			if (cNames.length > 0) {
				if (action == 'remove') {
					cNames = cNames.deleteVals(className);
				} else if (action == 'find') {
					return (cNames.indexOf(className) != -1);
				}
			}
			if (action == 'add') {
				if (typeof(className) == 'array') {
					cNames = cNames.concat(className);
				} else {
					cNames.push(className);
				}
			}
			if (action == 'add' || action == 'remove') element.className = cNames.join(' ').trim();
			if (action == 'find') return false;
		} else {
			return false;
		}
	},

	/**
	 * Element.visible
	 * By Kramer
	 *
	 * I was disappointed that Prototype's Element.visible really only
	 * reports on the status of element.style.display. What I really
	 * wanted to know was "can the user see this element, or is it
	 * contained inside a hidden element?" This extension fixes that with
	 * the new "recursive" option.
	 *
	 * Recursive option added to seek up the tree to make sure
	 * all parent nodes are visible, too. This should effectively
	 * return a true/false indicating whether the given element is
	 * indeed VISIBLE TO THE USER.
	 */
	visible: function(element, recursive) {
		if (!recursive) return $(element).style.display != 'none';
		var search = element;
		while (search = search.parentNode) {
			if ($(search).style.display == 'none') return false;
		}
		return true;
	},
	insertedNodes: [],
	insertNode: function(node, tagName, key){
		for(var x=0; x<Element.insertedNodes.length; x++){
			if(Element.insertedNodes[x].node == node){
				return false;
			}
		}
		var insertedNode = {
			node: node,
			key: (key)? key : null
		};

		/* Create and place container */
		insertedNode.container = document.createElement('span');
		Element.addClassName(insertedNode.container, "iFrameHackContainer");
		insertedNode.node.parentNode.insertBefore(insertedNode.container, insertedNode.node);
		insertedNode.container.appendChild(insertedNode.node);

		/* Create and place throbber */
		insertedNode.newNode = document.createElement(tagName);
		Element.addClassName(insertedNode.newNode, "newNode");
		insertedNode.container.appendChild(insertedNode.newNode);

		var dims = Element.getDimensions(insertedNode.container);

		/* Add essential styles to container and throbber */
		Object.extend(insertedNode.container.style, {
			position: "relative",
			display: "block",
			zoom: "1"
		});
		Object.extend(insertedNode.newNode.style, {
			position: "absolute",
			top: "0",
			left: "0",
			width: (document.all)?dims['width']+"px" : "100%",
			height: (document.all)?dims['height']+"px" : "100%"
		});

		Element.insertedNodes.push(insertedNode);
		return insertedNode;
	},
	removeNode: function(node, key){
		var insertedNode = null;
		for(var x=0; x<Element.insertedNodes.length; x++){
			if(Element.insertedNodes[x].node == node && ((!key && !Element.insertedNodes[x].key) || Element.insertedNodes[x].key == key)){
				insertedNode = Element.insertedNodes.splice(x, 1);
				break;
			}
		}
		if(!insertedNode){
			return false;
		}
		insertedNode = insertedNode[0];
		insertedNode.container.parentNode.insertBefore(node, insertedNode.container);
		insertedNode.container.parentNode.removeChild(insertedNode.container);
		return true;
	},
	iFrameHacks: [],
	iFrameHack: function(node){
		if(!document.all){
			return false;
		}
		var iFrameHack = Element.insertNode(node, 'iframe', 'iFrameHack');
		if(!iFrameHack){
			return false;
		}
		Element.addClassName(iFrameHack.container, "iFrameContainer");
		Element.addClassName(iFrameHack.newNode, "iFrame");
		Object.extend(iFrameHack.newNode.style, {
			filter: "alpha(opacity=0)",
			opacity: "0",
			zIndex: "2"
		});
		if(parseInt(iFrameHack.node.style.zIndex) > 1){
			Object.extend(iFrameHack.newNode.style, {
				zIndex: parseInt(iFrameHack.node.style.zIndex)+1
			});
		}
		return iFrameHack;
	},
	cancelIFrameHack: function(node){
		if(!document.all){
			return false;
		}
		Element.removeNode(node, 'iFrameHack');
	},
	parseClasses: function(el, parseChildren){
		var classes = String(el.className).split(" ");
		var keepClasses = [];
		for(var x=0; x<classes.length; x++){
			if(classes[x].indexOf(':') != -1){
				var matches = classes[x].match(/^(\w*):{1,2}\{(.*)\}$/);
				var ob = {};
				var vars = matches[2].split(",");
				for(var y=0; y<vars.length; y++){
					vars[y] = vars[y].split(/::?/);
					if(!vars[y][1]) vars[y][1] = '';
					ob[vars[y][0]] = vars[y][1];
				}
				el[matches[1]] = ob;
			}else{
				keepClasses.push(classes[x]);
			}
		}
		el.className = keepClasses.join(" ");
		if(parseChildren){
			var eles = el.getElementsByTagName('*');
			for(var x=0; x<eles.length; x++){
				this.parseClasses(eles[x]);
			}
		}
	}
});


Event._observe = Event.observe;
Object.extend(Event, {
	/**
	 * Returns the keycode related to an event, browser-independent
	 */
	keyCode: function(event) {
		if (typeof(event.which) == 'undefined') return event.keyCode;
		return Math.max(event.which, event.keyCode);
	},

	/**
	 * Event.observe now returns an ID which can be sent to Event.stopObserving to terminate that event handler.
	 */
	registry: [],

	id: 0,

	observe: function(element, name, observer, useCapture) {
		Event.id++;
		var regEntry = {
			id: Event.id,
			element: element,
			name: name,
			observer: observer,
			useCapture: useCapture
		};
		Event.registry.push(regEntry);
		Event._observe(element, name, observer, useCapture);
		return regEntry;
	},

	unObserve: function(regEntry) {
		for (var x=0; x<Event.registry.length; x++) {
			var a = Event.registry[x];
			if (a.id == regEntry.id) {
				Event.registry.splice(x, 1);
				Event.stopObserving(a.element, a.name, a.observer, a.useCapture);
				return true;
			}
		}
		return false;
	},

	fire: function(el, name) {
		for(var x=0; x<Event.registry.length; x++){
			if(Event.registry[x].element == el && Event.registry[x].name == name){
				Event.registry[x].observer();
			}
		}
	}
});


/* Plain old functions
--------------------------------------------------------------------------*/
document.getElementsBySelector = function(selectors, withinNodes) {
	if (!(selectors instanceof Array)) selectors = [selectors];
	if (typeof(withinNodes) == 'undefined') {
		var withinNodes = [document.getElementsByTagName('body')[0]];
	} else if (!(withinNodes instanceof Array)) {
		var withinNodes = [withinNodes];
	}
	var resultNodes = [];
	for (var cSel=0; cSel<selectors.length; cSel++) {
		for (var cNodes=0; cNodes<withinNodes.length; cNodes++) {
			var resultNodes = resultNodes.concat(Element.getElementsBySelector(withinNodes[cNodes], selectors[cSel]));
		}
	}
	return resultNodes.unique();
};

function getCoordsCenter(coords) {
	return {
		x: coords.x/2,
		y: coords.y/2
	};
};
function getViewportSize() {
	var retVal = {
		x: self.innerWidth || (document.documentElement.clientWidth || document.body.clientWidth),
		y: self.innerHeight || (document.documentElement.clientHeight || document.body.clientHeight)
	};
	// Legacy property names
	retVal.height = retVal.y;
	retVal.width = retVal.x;
	return retVal;
};
function getViewportCenter() {
	return getCoordsCenter(getViewportSize());
};
function getScreenSize() {
	var screenW = 640, screenH = 480;
	if (parseInt(navigator.appVersion)>3) {
	 screenW = screen.width;
	 screenH = screen.height;
	}
	else if (navigator.appName == "Netscape"
	    && parseInt(navigator.appVersion)==3
	    && navigator.javaEnabled()
	   )
	{
	 var jToolkit = java.awt.Toolkit.getDefaultToolkit();
	 var jScreenSize = jToolkit.getScreenSize();
	 screenW = jScreenSize.width;
	 screenH = jScreenSize.height;
	}

	return {
		x: screenW,
		y: screenH
	}
};
function getScreenCenter() {
	return getCoordsCenter(getScreenSize());
}

/**
 * Changes pretty much any variable into its boolean equivalent
 * Strings like yes, no, true, false, y, n
 * Numbers like 0, 1, 2...
 * @return bool
 */
function bool(arg) {
	// Returns true or false
	// Does everything concievable to make arg into a boolean value
	if (typeof(arg) == 'undefined') return false;
	if (typeof(arg) == 'boolean') return arg;
	switch(arg.toLowerCase()) {
		case 'yes':
		case 'true':
		case 'y':
			return true;
		break;
		case 'false':
		case 'no':
		case 'n':
			return false;
		break;
		default:
			if (arg === true) return true;
			if (arg === false) return false;
			if (parseInt(arg) > 0) return true;
			if (parseInt(arg) == 0) return false;
		break;
	}
	return null; // Inconclusive
}

/**
 * Converts a UNIX timestamp into a JS Date object
 */
function unixToDate(unixtime) {
	var time = new Date();
	time.setTime(unixtime*1000);
	return time;
}

// Deprecated (partially)
// See Prototype's each()
// http://www.prototypejs.org/api/hash/each
// Although I don't think each() is recursive, not sure.
/**
 * For-each within an object. Recursive if you want it to be.
 **/
function foreach(obj, func, recursive) {
	for (var key in obj) {
		if (!Object.prototype[key]) {
			if (obj[key] instanceof Object && recursive) {
				foreach(obj[key], func);
			}
			func(obj[key], obj);
		}
	}
}

/**
 * Executes the given command within a different thread
 **/
Function.prototype.newThread = function(delay) {
	if (typeof(delay) == 'undefined') delay = 1;
	setTimeout(this, delay);
};
newThread = function(toExec, delay) {
	toExec.newThread(delay);
};

/* Adam added 5-22-07 to fix getElementsBySelector in IE */
Object.extend(Selector.prototype, {
	buildMatchExpression: function() {
		var params = this.params, conditions = [], clause;
		
		if (params.wildcard)
			conditions.push('true');
		if (clause = params.id)
			conditions.push('Element.readAttribute(element, "id") == ' + clause.inspect());
		if (clause = params.tagName)
			conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
		if ((clause = params.classNames).length > 0)
			for (var i = 0, length = clause.length; i < length; i++)
				conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
		if (clause = params.attributes) {
			clause.each(function(attribute) {
				var value = 'Element.readAttribute(element, ' + attribute.name.inspect() + ')';
				var splitValueBy = function(delimiter) {
					return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
				}
				switch (attribute.operator) {
					case '=':       conditions.push(value + ' == ' + attribute.value.inspect()); break;
					case '~=':      conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
					case '|=':      conditions.push(splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()); break;
					case '!=':      conditions.push(value + ' != ' + attribute.value.inspect()); break;
					case '':
					case undefined: conditions.push('Element.hasAttribute(element, ' + attribute.name.inspect() + ')'); break;
					default:        throw 'Unknown operator ' + attribute.operator + ' in selector';
				}
			});
		}
		return conditions.join(' && ');
	}
});
/* End added */
/**
 * Kramer's DOM Library
 * DOM manipulation functions
 * March 28, 2006
 */
var DOM = {
	// Gives a random and meaningless ID attribute to
	// an element which needs one
	giveId: function(el) {
		if (!el.id) {
			var uniqueId = '';
			var counter = 0;
			do {
				counter++;
				uniqueId = 'unique'+counter;
			} while (document.getElementById(uniqueId));
		}
		element.id = uniqueId;
		return uniqueId;
	},

	empty: function(el) {
		// Removes all children of the given element
		if (DOM.isNode(el)) {
			while (el.hasChildNodes()) {
				el.removeChild(el.firstChild);
			}
			return true;
		} else {
			return false;
		}
	},

	/**
	 * Get Children By Tag Name
	 * Returns immediate children by tag name
	 * Differs from getElementsByTagName() in that this version is not recursive.
	 * @param   DOMELEMENT element which element to start on
	 * @param   STRING     tagName the name of the tags to grab
	 * @return  array of applicable elements
	 */
	getChildrenByTagName: function(element, tagName) {
		if (DOM.isNode(element)) {
			var retval = Array();
			var descendants = element.getElementsByTagName(tagName);
			for (var i=0; i<descendants.length; i++) {
				if (descendants[i].parentNode == element) {
					retval.push(descendants[i]);
				}
			}
			return retval;
		} else {
			return false;
		}
	},

	getSiblingsByTagName: function(element, tagName, nextPrev) {
		if (DOM.isNode(element)) {
			var retval = Array();
			var context = element;
			if (!nextPrev) return DOM.getChildrenByTagName(element.parentNode, tagName);
			var dir = (nextPrev == 'next') ? 'nextSibling' : 'previousSibling';
			while (context = context[dir]) if (context.tagName) if (context.tagName.toLowerCase() == tagName.toLowerCase()) retval.push(context);
			return retval;
		} else {
			return false;
		}
	},

	/**
	 * Returns true if argument is a DOM node
	 */
	isNode: function(node) {
		if (!node) return false;
		return (typeof(node.nodeType) != 'undefined');
	},

	getElementText: function(el) {
		// Seeks out the first text element node contained in el and returns it
		if (typeof(el) != 'undefined' && typeof(el.childNodes) != 'undefined') {
			var nodeTypes = [3,4];                                         // Node types to check for data
			var nodes = {};
			for (var x=0; x<nodeTypes.length; x++) {
				nodes[nodeTypes[x]] = [];
			}
			for (var x=0; x<el.childNodes.length; x++) {
				if (nodeTypes.inArray(el.childNodes[x].nodeType) != -1) {
					nodes[el.childNodes[x].nodeType].push(el.childNodes[x]);
				}
			}
			if (nodes[4].length > 0) return nodes[4][0].nodeValue;
			if (nodes[3].length > 0) return nodes[3][0].nodeValue;
		}
		return false;
	},

	getXMLData: function(startFrom, tagName) {
		// Gets data from inside an XML tag by name. Seeks the first tag inside of startFrom with the correct name,
		// returns the text node from inside it.
		var tags = startFrom.getElementsByTagName(tagName);
		if (tags.length) {
			return DOM.getElementText(tags[0]);
		}
		return false;
	},

	nodeDisplayValue: function(node, replacementValue) {
		// Uses simple logic to drill down into a DOM node element in
		// search of the numeric value it is trying to display to the user,
		// and to return that value in float form.
		// If replacementValue is set, then, once the value has been
		// located; it will be replaced with that value.
		var replace = (typeof(replacementValue) != 'undefined');

		if (DOM.isNode(node)) {
			var value = null;
			switch (node.tagName.toLowerCase()) {
				case 'input':
					if (replace) node.value = replacementValue;
					value = node.value;
				break;
				default:
					// Place transition code here if desired.
					if (replace) node.childNodes[0].nodeValue = replacementValue;
					value = node.childNodes[0].nodeValue;
				break;
			}
			value = this.parseNumber(value);
			if (isNaN(value)) return 0;
			return value;
		} else {
			return false;
		}
	},

	/**
	 * Replaces one DOM node with another
	 */
	replace: function(originalNode, newNode) {
		if (typeof(newNode) == 'string') newNode = document.createTextNode(newNode);
		return originalNode.parentNode.replaceChild(newNode, originalNode);
	},

	/**
	 * Locates a comment with particular text in a document.
	 * Optionally searches only within a given node.
	 */
	findFlag: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (list[x].nodeType == 8 && list[x].nodeValue == flagText) return list[x];
			if (list[x].childNodes.length) {
				var recursionResult = DOM.findFlag(flagText, list[x]);
				if (recursionResult != false) return recursionResult;
			}
		}
		return false;
	},

	/**
	 * Retrieves data encoded in a class="" attribute
	 *
	 * DOM.getClassData('secret', aNode) where aNode is:
	 * <a href="#" class="nav1 {secret=password}"> would return:
	 * "password"
	 *
	 * Non alphanumeric chars permitted in classnames per spec: http://www.w3.org/TR/CSS21/syndata.html#q6
	 **/
	getClassData: function(flagText, node) {
		var result = null;
		var className = node.className.replace(/.*\{(.*)\}.*/, "$1");

		return this.getEncodedData(flagText, className);
	},

	getCommentData: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (list[x].nodeType == 8) {
				var result = null;
				if (result = this.getEncodedData(flagText, list[x].nodeValue)) return result;
			}
			if (list[x].childNodes.length) {
				var recursionResult = DOM.getCommentData(flagText, list[x]);
				if (recursionResult != false && recursionResult != null) return recursionResult;
			}
		}
	},

	getElementData: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (Element.hasClassName(list[x], 'elementData')) {
				var result = null;
				if (result = this.getEncodedData(flagText, list[x].innerHTML)) return result;
			}
			if (list[x].childNodes.length) {
				var recursionResult = DOM.getCommentData(flagText, list[x]);
				if (recursionResult != false && recursionResult != null) return recursionResult;
			}
		}
	},

	getEncodedData: function(flagText, data) {
		var statements = data.split(';');
		for (var y=0; y<statements.length; y++) {
			var eqAt = statements[y].indexOf('=');
			var keyName = statements[y].substr(0, eqAt).trim();
			if (keyName == flagText) {
				return statements[y].substr(eqAt+1).trim();
			}
		}
		return false;
	},

	isDescendant: function(rootNode, childNode) {
		// Returns true if childNode is a descendant of rootNode
		var context = childNode;
		while (!(context.nodeType == 1 && context.tagName == 'BODY')) {
			context = context.parentNode;
			if (context == null) return false;
			if (context == rootNode) return true;
		}
		return false;
	},

	getAncestorsByTagName: function(startNode, tagName) {
		var ancestors = [];
		while (startNode != document.documentElement) {
			startNode = startNode.parentNode;
			if (startNode.tagName.toLowerCase() == tagName.toLowerCase()) ancestors.push(startNode);
		}
		return ancestors;
	},

	insertChild: function(newNode, refNode) {
		// Inserts a child element as first child, not last like appendChild does
		if (refNode.firstChild) {
			newNode = refNode.insertBefore(newNode, refNode.firstChild);
		} else {
			newNode = refNode.appendChild(newNode);
		}
		return newNode;
	},

	cut: function(node) {
		// Removes the given node from the DOM and returns it
		var clone = node.cloneNode(true);
		DOM.remove(node);
		return clone;
	},

	insertAfter: function(newNode, refNode) {
		// Acts like insertBefore, but does so after refNode.
		if (refNode.nextSibling) {
			newNode = refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
		} else {
			newNode = refNode.parentNode.appendChild(newNode);
		}
		return newNode;
	},

	remove: function(node) {
		if (node.parentNode) return node.parentNode.removeChild(node);
	},

	/**
	 * Takes a DOM node and returns an HTML representation of it.
	 * (Not the exact tag as written in the HTML, necessarily)
	 */
	showTag: function(el, contents) {
		var tag = '';
		var att = null;
		var val = null;
		if (el) {
			if (el.nodeType == 1) {
				var tagName = el.tagName.toLowerCase();
				tag = "<"+tagName;
				if (el.attributes) {
					for (var i=0; i<el.attributes.length; i++) {
						att = el.attributes[i].nodeName;
						val = el.attributes[i].nodeValue;
						if (val) tag += ' '+att+'="'+val+'"';
					}
				}
				var innerHTML = (typeof(el.innerHTML) == 'unknown') ? '**innerHTML property undefined**' : el.innerHTML;
				if (contents && innerHTML) {
					tag += '>';
					tag += innerHTML;
					tag += '</'+tagName+'>';
				} else if (!contents && innerHTML) {
					tag += '>';
				} else {
					tag += ' />';
				}
			} else {
				tag = 'Invalid node type: '+el.nodeType;
			}
		} else {
			tag = 'Parameter is not a tag node: '+el;
		}
		return tag;
	},
	makeTag: function(el, deep) {
		return DOM.showTag(el, deep);
	},
	outerHTML: function(el, deep) {
		return DOM.showTag(el, deep);
	},

	/**
	 * Opposite of makeTag, takes HTML and returns a node.
	 * Only works with ONE top-level node in the HTML.
	 */
	makeNode: function(html) {
		var wrapper = document.createElement('div');
		wrapper.innerHTML = html;
		return wrapper.childNodes[0];
	},

	/**
	 * Shows the style of a given element
	 */
	showStyle: function(el) {
		if (el) {
			var st = "Element Style: <"+el.tagName+">\n\n";
			if (el.style) {
				for (afds in el.style) {
					var val = el.style[afds];
					if (val && typeof(val) != 'function') st += afds+': "'+el.style[afds]+'"\n';
				}
			} else {
				st = 'No style';
			}
		} else {
			var st = 'Parameter is not an element: '+el;
		}
		return st;
	}
};
/*
	Base, version 1.0.2
	Copyright 2006, Dean Edwards
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/

var Base = function() {
	if (arguments.length) {
		if (this == window) { // cast an object to this class
			Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
		} else {
			this.extend(arguments[0]);
		}
	}
};

Base.version = "1.0.2";

Base.prototype = {
	extend: function(source, value) {
		var extend = Base.prototype.extend;
		if (arguments.length == 2) {
			var ancestor = this[source];
			// overriding?
			if ((ancestor instanceof Function) && (value instanceof Function) &&
				ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
				var method = value;
			//	var _prototype = this.constructor.prototype;
			//	var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
				value = function() {
					var previous = this.base;
				//	this.base = fromPrototype ? _prototype[source] : ancestor;
					this.base = ancestor;
					var returnValue = method.apply(this, arguments);
					this.base = previous;
					return returnValue;
				};
				// point to the underlying method
				value.valueOf = function() {
					return method;
				};
				value.toString = function() {
					return String(method);
				};
			}
			return this[source] = value;
		} else if (source) {
			var _prototype = {toSource: null};
			// do the "toString" and other methods manually
			var _protected = ["toString", "valueOf"];
			// if we are prototyping then include the constructor
			if (Base._prototyping) _protected[2] = "constructor";
			for (var i = 0; (name = _protected[i]); i++) {
				if (source[name] != _prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
			// copy each of the source object's properties to this object
			for (var name in source) {
				if (!_prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
		}
		return this;
	},

	base: function() {
		// call this method from any other method to invoke that method's ancestor
	}
};

Base.extend = function(_instance, _static) {
	var extend = Base.prototype.extend;
	if (!_instance) _instance = {};
	// build the prototype
	Base._prototyping = true;
	var _prototype = new this;
	extend.call(_prototype, _instance);
	var constructor = _prototype.constructor;
	_prototype.constructor = this;
	delete Base._prototyping;
	// create the wrapper for the constructor function
	var klass = function() {
		if (!Base._prototyping) constructor.apply(this, arguments);
		this.constructor = klass;
	};
	klass.prototype = _prototype;
	// build the class interface
	klass.extend = this.extend;
	klass.implement = this.implement;
	klass.toString = function() {
		return String(constructor);
	};
	extend.call(klass, _static);
	// single instance
	var object = constructor ? klass : _prototype;
	// class initialisation
	if (object.init instanceof Function) object.init();
	return object;
};

Base.implement = function(_interface) {
	if (_interface instanceof Function) _interface = _interface.prototype;
	this.prototype.extend(_interface);
};


/**
 * stdClass
 */
var stdClass = Base.extend({
	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.s = {                                                       // Settings
				showErrors: true,
				showDebugs: true
			};
			Object.extend(this.s, settings);
			this.n = {                                                       // Nodes
				_body: document.getElementsByTagName('body')[0]
			};
			this.c = {                                                       // Collections
				events: [],
				messages: []
			};
		}
	},

	constructor: function(settings) {
		this.settings(settings);
	},

	eObserve: function(node, eventType, callback, useCapture) {
		var eventRef = Event.observe(node, eventType, callback, useCapture);
		this.c.events.push(eventRef);
		return eventRef;
	},

	eUnObserve: function(eventRef) {
		Event.unObserve(eventRef);
	},

	eUnObserveAll: function() {
		this.c.events.each(function (eventRef) {
			this.eUnObserve(eventRef);
		});
	},

	error: function(text) {
		this.shout(text, 'error', this.s.showErrors);
	},

	debug: function(text) {
		this.shout(text, 'debug', this.s.showDebugs);
	},

	shout: function(text, type, toWhom) {
		this.c.messages.push({
			date: new Date(),
			text: text,
			type: type,
			recipient: toWhom
		});
		if (toWhom === true) { // true == everybody
			alert(text);
		} else if (toWhom instanceof Array) {
			if (typeof(shout) == 'function') {
				for (var x=0; x<toWhom.length; x++) {
					shout(text, toWhom[x]);
				}
			} else {
				alert('Shout() is not available, using alert(). Sorry, Grady!\n_____________________________________________\n'+text);
			}
		}
	}
});

/*
// Use the following code to create extensible classes:

var ClassName = stdClass.extend({
	// Static properties

	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			Object.extendProperties(this.s, {
				whatever: 'whateverElse'
			});
			Object.extendProperties(this.s, settings);
		}
	},

	constructor: function(settings) {
		this.settings(settings);
	}
});
*/
// EventSelectors
// Copyright (c) 2005-2006 Justin Palmer (http://encytemedia.com)
// Examples and documentation (http://encytemedia.com/event-selectors)
//
// EventSelectors allow you access to Javascript events using a CSS style syntax.
// It goes one step beyond Javascript events to also give you :loaded, which allows
// you to wait until an item is loaded in the document before you begin to interact
// with it.
//
// Inspired by the work of Ben Nolan's Behaviour (http://bennolan.com/behaviour)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Modified by Grady and Kramer of SolutionSet
var classEventSelectors = Class.create();
classEventSelectors.prototype = {
	version: '1.0_pre_ss',
	eventZonesFired: [], //records the events that have fired
	events: [],
	eventZones: {
		DOMLoad: [],
		BINLoad: []
	},
	funcs: [],
	nodes: [],

	apply: function() {
		this._unloadEventCache();
		this._fireEventZone('BINLoad');
		this._fireEventZone('DOMLoad');
	},

	addLoadEvent: function(func) {                                      // Convenience function in global scope
		this.register({
			'window:load': func
		})
	},

	initialize: function() {                                           // Register self as the all-powerful onload handler
		// Traditional binary-inclusive full-page-load handler
		var oldOnLoad = (window.onload instanceof Function) ? window.onload : function() {};
		var newOnLoad = this._fireEventZone.bind(this, 'BINLoad');
		window.onload = function() {
			oldOnLoad();
			newOnLoad();
		};

		/**
		 * Special (expedient) binary-exclusive DOM-load handler
		 *
		 * Fires an event after the DOM has loaded, but before binary
		 * assets have necessarily loaded.
		 *
		 * Original code by:
		 *   Dean Edwards/Matthias Miller/John Resig (http://dean.edwards.name/weblog/2006/06/again/)
		 *
		 * Modified to work with EventSelectors by:
		 *   Kramer (060915)
		 **/

		/* for Mozilla/Opera9 */
		if (document.addEventListener) document.addEventListener("DOMContentLoaded", this._fireEventZone.bind(this, 'DOMLoad'), false);

		/* for Internet Explorer */
		/*@cc_on @*/
		/*@if (@_win32)
			document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
			var script = document.getElementById("__ie_onload");
			script.onreadystatechange = function() {
				if (this.readyState == "complete") {
					EventSelectors._fireEventZone('DOMLoad'); // call the onload handler
				}
			};
		/*@end @*/

		/* for Safari */
		if (/WebKit/i.test(navigator.userAgent)) { // sniff
			// Safari doesn't register 'this' within the function (nor should it, afaik) therefore this variable was created
			var elBackup = this;
			this.safariTimer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					elBackup._fireEventZone('DOMLoad'); // call the onload handler
				}
			}, 10);
		}
	},

	// onDOMLoad option (BOOL): true, rule fires on DOM load. False, rule fires on BIN load.
	register: function(rules, onDOMLoad) {                           // PUBLIC: Register functions to run against selectors
		var eventZone = 'BINLoad';
		if (onDOMLoad === true) eventZone = 'DOMLoad';
		this.eventZones[eventZone].push(rules);
		for(var key in this.eventZonesFired){
			if(this.eventZonesFired[key] == eventZone){
				//alert('ran');
				this._executeRules(rules);
				break;
			}
		}
	},

	_executeRules: function(rules) {
		this.timer = new Array();
		for (var key in rules) {
			var rule = {
				selector: key,
				func: rules[key]
			};
			switch (rule.selector) {                                     // Special selector tokens
				case 'window:load':
				case 'window:binLoad':
				case 'window:domLoad':
					rule.func();
				break;
				default:                                                   // Default selector operation
					var selectors = $A(rule.selector.split(','));
					for (var x=0; x<selectors.length; x++) {
						var selector = selectors[x];
						var pair = selector.split(':');
						var event = pair[1];
						var elements = $$(pair[0]);
						for (var y=0; y<elements.length; y++) {
							var element = elements[y];
							if(pair[1] == '' || pair.length == 1) {
								var funcId = this.funcs.indexOf(rule.func);
								if(funcId == -1) {
									this.funcs.push(rule.func);
									funcId = this.funcs.length-1;
								}
								if (!this.nodes[funcId])
									this.nodes[funcId] = [];
								if (this.nodes[funcId].indexOf(element) == -1) {
									this.nodes[funcId].push(element);
									rule.func(element);
								}
								continue;
							}
							if(event.toLowerCase() == 'loaded') {
								this.timer[pair[0]] = setInterval(function(element, timer, rule) {
									var node = $(element);
									if(element.tagName != 'undefined') {
										clearInterval(this.timer[timer]);
										rule.func(node);
									}
								}.bind(this, element, pair[0], rule), 15);
							} else {
								var observer = function(event) {
									var element = Event.element(event);
									if (element.nodeType == 3)                       // Safari Bug (Fixed in Webkit)
										element = element.parentNode;
									rule.func($(element), event);
								}
								this.events.push(Event.observe(element, event, observer));
							}
						}
					}
				break;
			}
		}
	},

	_fireEventZone: function(zone) {
		this.eventZonesFired.push(zone);
		if (zone == 'DOMLoad') {
			if (arguments.callee.done) return;                               // quit if this function has already been called
			arguments.callee.done = true;                                    // flag this function so we don't do the same thing twice
			if (this.safariTimer) clearInterval(this.safariTimer);           // kill the timer
		}
		for (var x=0; x<this.eventZones[zone].length; x++) {
			this._executeRules(this.eventZones[zone][x]);
		}
	},

	_unloadEventCache: function() {
		if (!this.events) return;
		for (var i=0; i<this.events.length; i++) {
			Event.unObserve(this.events[i]);
			this.events[i] = null;
		}
		this.events = [];
		this._fireEventZone.done = false;									// Adam added to fix the problem that _fireEventZone only runs
																			// once. After this function has run, the _fireEventZone should
																			// run again to re-init the EventSelectors
	}
}
EventSelectors = new classEventSelectors();                          // Instantiate
Behaviour = EventSelectors;                                          // For old code that expects Behaviour

// Remove/Comment this if you do not wish to reapply Rules automatically
// on Ajax request.
//Ajax.Responders.register({
//	onComplete: function() { EventSelectors.apply();}
//});
/* tabManager.js */
var tabsClass = Class.create();
tabsClass.prototype = {
	
	//Properties
	ul: null,
	nodeid: null,
	tabcontent: null,

	//Constructor
	initialize: function(el) {
		this.el = el;
		this.observeLink();
		this.findUL();
	},
	
	//Methods
	observeLink: function () {
		this.clickFunc = this.clickLink.bindAsEventListener(this)
		Event.observe(this.el, 'click', this.clickFunc);
		this.el.onclick = function() {return false;}; // be nice to Safari
	},
	clickLink: function (e) {
		for (i=0; i<this.ul.getElementsByTagName('a').length; i++) {
			Element.removeClassName(this.ul.getElementsByTagName('li')[i], 'selected'); // remove selected class from all tabs (links)
			Element.removeClassName(this.ul.getElementsByTagName('a')[i], 'selected'); // remove selected class from all tabs (links)
		}		
		this.el.className = 'selected'; // add selected class to current tab
		this.el.parentNode.className = 'selected'; // add selected class to current tab
		this.nodeid = this.el.href.substring(this.el.href.indexOf('#')+1); // get the node id (pulled from the href)
		this.tabcontent = this.ul.parentNode.getElementsByTagName('div');
		for (i=0; i<this.tabcontent.length; i++) {
			if (Element.hasClassName(this.tabcontent[i], 'tab_content') && this.nodeid != this.tabcontent[i].id) {
				Element.hide(this.tabcontent[i]); // hide tab, if it is not the current tab
			}
			else {
				Element.show(this.tabcontent[i]); // show the current tab
			}
		}
		this.el.blur();
		Event.stop(e); // doesn't work in Safari, fixed above by explicitly assigning an event handler to the link
	},
	findUL: function () {
		var element = this.el;
		var tagName = 'ul';
		while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))) {
			element = element.parentNode;
		}
		this.ul = element;
	}
};

var tabs_Rules= {
	'.jstabs ul.tabs li a' : function(el) {
		new tabsClass (el);
	}
};
EventSelectors.register(tabs_Rules);

/*
		json.js
		2006-12-06

		Tweaked by Kramer & Grady 070104
			Used Object.extend()
			Converted Object.prototype changes to a global function toJSONString()

		This file adds these methods to JavaScript:

				array.toJSONString()
				boolean.toJSONString()
				date.toJSONString()
				number.toJSONString()
				string.toJSONString()
				toJSONString() (for objects)
						These methods produce a JSON text from a JavaScript value.
						It must not contain any cyclical references. Illegal values
						will be excluded.

						The default conversion for dates is to an ISO string. You can
						add a toJSONString method to any date object to get a different
						representation.

				string.parseJSON(hook)
						This method parses a JSON text to produce an object or
						array. It can throw a SyntaxError exception.

						The optional hook parameter is a function which can filter and
						transform the results. It receives each of the values, and its
						return value is used instead. If it returns what it received, then
						structure is not modified.

						Example:

						// Parse the text. If it contains any "NaN" strings, replace them
						// with the NaN value. All other values are left alone.

						myData = text.parseJSON(function (value) {
								if (value === 'NaN') {
										return NaN;
								}
								return value;
						});

		It is expected that these methods will formally become part of the
		JavaScript Programming Language in the Fourth Edition of the
		ECMAScript standard in 2007.
*/
Object.extend(Array.prototype, {
	toJSONString: function () {
			var a = ['['], b, i, l = this.length, v;

			function p(s) {
					if (b) {
							a.push(',');
					}
					a.push(s);
					b = true;
			}

			for (i = 0; i < l; i += 1) {
					v = this[i];
					switch (typeof v) {
					case 'undefined':
					case 'function':
					case 'unknown':
							break;
					case 'object':
							if (v) {
									if (typeof toJSONString === 'function') {
											p(toJSONString(v));
									}
							} else {
									p("null");
							}
							break;
					default:
							p(toJSONString(v));
					}
			}
			a.push(']');
			return a.join('');
	}
});

Object.extend(Boolean.prototype, {
	toJSONString: function () {
			return String(this);
	}
});

Object.extend(Date.prototype, {
	toJSONString: function () {

			function f(n) {
					return n < 10 ? '0' + n : n;
			}

			return '"' + this.getFullYear() + '-' +
							f(this.getMonth() + 1) + '-' +
							f(this.getDate()) + 'T' +
							f(this.getHours()) + ':' +
							f(this.getMinutes()) + ':' +
							f(this.getSeconds()) + '"';
	}
});

Object.extend(Number.prototype, {
	toJSONString: function () {
			return isFinite(this) ? String(this) : "null";
	}
});

function toJSONString(subject) {
	if (subject instanceof Object) {
		var a = ['{'], b, i, v;

		function p(s) {
				if (b) {
						a.push(',');
				}
				a.push(toJSONString(i), ':', s);
				b = true;
		}

		for (i in subject) {
				if (subject.hasOwnProperty(i)) {
						v = subject[i];
						switch (typeof v) {
						case 'undefined':
						case 'function':
						case 'unknown':
								break;
						case 'object':
								if (v) {
										if (typeof toJSONString === 'function') {
												p(toJSONString(v));
										}
								} else {
										p("null");
								}
								break;
						default:
								p(toJSONString(v));
						}
				}
		}
		a.push('}');
		return a.join('');
	} else {
		return subject.toJSONString();
	}
};


Object.extend(String.prototype, {
	parseJSON: function (filter) {
		try {
                var j;
                function walk(k, v) {
                    var i, n;
                    if (v && typeof v === 'object') {
                        for (i in v) {
                            if (Object.prototype.hasOwnProperty.apply(v, [i])) {
                                n = walk(i, v[i]);
                                if (n !== undefined) {
                                    v[i] = n;
                                }
                            }
                        }
                    }
                    return filter(k, v);
                }

// Parsing happens in three stages. In the first stage, we run the text against
// regular expressions that look for non-JSON patterns. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we want to reject all
// unexpected forms.

// We split the first stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace all backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.test(this.replace(/\\./g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + this + ')');

                    return typeof filter === 'function' ? walk('', j) : j;
                }

                return j;
            
  			} catch (e) { //alert('rutro: '+e);
		}
		throw new SyntaxError("parseJSON");
	},

	toJSONString: function () {
		var m = {
			'\b': '\\b',
			'\t': '\\t',
			'\n': '\\n',
			'\f': '\\f',
			'\r': '\\r',
			'"' : '\\"',
			'\\': '\\\\'
		};
		if (/["\\\x00-\x1f]/.test(this)) {
			return '"' + this.replace(/([\x00-\x1f\\"])/g, function(a, b) {
				var c = m[b];
				if (c) {
					return c;
				}
				c = b.charCodeAt();
				return '\\u00' +
					Math.floor(c / 16).toString(16) +
					(c % 16).toString(16);
			}) + '"';
		}
		return '"' + this + '"';
	}
});
/**
 * NodeBase
 */

var NodeBase = stdClass.extend({
	// Static properties
	activeNodes: [],
	nodeContainer: null,
	mousePosition: {
		x: null,
		y: null
	},

	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			this.moveOnPointerMove = false;
			Object.extendProperties(this.s, {
				useIframe: (document.all && !window.XMLHttpRequest) ? true : false,
				nodeTagName: 'div',
				iframeShrink: 0,
				nodeId: null,
				groupId: 'generic',
				groupLimit: null,
				classNames: [],
				zIndex: 100,
				visible: false,
				extant: false,
				position: {
					exemplarAnchor: 'center middle',
					selfAnchor: 'center middle',
					exemplar: 'viewport',
					offsetY: 0,
					offsetX: 0
				}
			});
			Object.extendProperties(this.s, settings);

			Object.extendProperties(this.n, {
				superNode: null,
				subNode: null,
				iframe: null
			});
		}
	},

	constructor: function(settings) {
		this.settings(settings);
	},

	watchMouse: function(e) {
		if (typeof(e) == 'undefined') {
			this.eObserve(document, 'mousemove', this.watchMouse.bind(this));
		} else {
			this.mousePosition = {
				x: Event.pointerX(e),
				y: Event.pointerY(e)
			};
			if (this.moveOnPointerMove) {
				this.moveOnPointerMove = false;
				this.position();
			}
		}
	},

	// Public methods
	hide: function() {
		Element.hide(this.n.superNode);
		this.s.visible = false;
	},

	show: function() {
		if (this.s.groupLimit !== null) {                                // Check the registry for group conflicts
			var permitToRemain = this.s.groupLimit-1;
			for (var x=0; x<this.activeNodes.length; x++) {
				if (this.activeNodes[x]._isVisible() && this.activeNodes[x].getGroupId() == this.s.groupId) {
					if (permitToRemain--<=0) this.activeNodes[x].hide();
				}
			}
		}
		if (!this.n.superNode) this.create();
		this.position();
  		//Effect.SlideRightIntoView(this.n.superNode);
		//Effect.SlideDown(this.n.superNode);
		Element.show(this.n.superNode);
		this._sizeIframe();
		this.s.visible = true;
	},

	create: function() {
		if (this.n.subNode) this.destroy();
		if (!this.s.zIndex) this.s.zIndex = 1000 + this.activeNodes.length;
		this.n.superNode = this._createNode(this.s.nodeTagName);
		this.n.superNode.style.position = 'absolute';
		this.n.superNode.className = 'superNode';
		//this.n.superNode.style.zIndex = this.s.zIndex-1;

		this.n.subNode = document.createElement(this.s.nodeTagName);
		this.n.subNode.style.zIndex = this.s.zIndex;
		this.s.classNames.push('subNode');
		if (this.s.classNames.length > 0) this.n.subNode.className = this.s.classNames.join(' ');
		this.n.superNode.appendChild(this.n.subNode);

		this._createIframe();
		this.hide();
		this._register();
		this.watchMouse();
		this.s.extant = true;
	},

	destroy: function() {
		//Effect.SlideRightOutOfView(this.n.superNode);
		//Effect.SlideUp(this.n.superNode);
		DOM.remove(this.n.subNode);
		this.n.subNode = null;
		this.n.iframe = null;
		this.c.events = [];
		this._unRegister();
		this.s.extant = false;
		this.s.visible = false;
	},

	position: function (settings) {
		Object.extendProperties(this.s.position, settings);

		var things = {
			self: {
				anchors: this.s.position.selfAnchor.split(' '),
				node: this.n.superNode,
				height: Element.getDimensions(this.n.superNode).height,
				width: Element.getDimensions(this.n.superNode).width,
				top: Position.cumulativeOffset(this.n.superNode)[1],
				left: Position.cumulativeOffset(this.n.superNode)[0]
			},
			exemplar: {
				anchors: this.s.position.exemplarAnchor.split(' '),
				node: this.n.superNode,
				height: Element.getDimensions(this.n.superNode).height,
				width: Element.getDimensions(this.n.superNode).width,
				top: Position.cumulativeOffset(this.n.superNode)[1],
				left: Position.cumulativeOffset(this.n.superNode)[0]
			}
		};

		switch (this.s.position.exemplar) {
			case 'viewport':
				//detect scroll offset
				if (document.documentElement && document.documentElement.scrollTop)
					theTop = document.documentElement.scrollTop;
				else if (document.body)
					theTop = document.body.scrollTop

				Object.extend(things.exemplar, {
					width: getViewportSize().width,
					height: getViewportSize().height,
					top: theTop,
					left: 0
				});
			break;
			case 'pointer':
				Object.extend(things.exemplar, {
					width: 0,
					height: 0,
					top: this.mousePosition.y,
					left: this.mousePosition.x
				});
				// If the mouse has not moved yet, values will be 0,0 - so set a flag that
				// on next mouse move, div must be positioned.
				if (things.exemplar.top == 0 && things.exemplar.left == 0) this.moveOnPointerMove = true;
			break;
			case 'document':
				things.exemplar = document;
			default:                                                       // Assuming it's a DOM node
				Object.extend(things.exemplar, {
					node: this.s.position.exemplar,
					height: Element.getDimensions(this.s.position.exemplar).height,
					width: Element.getDimensions(this.s.position.exemplar).width,
					top: Position.cumulativeOffset(this.s.position.exemplar)[1],
					left: Position.cumulativeOffset(this.s.position.exemplar)[0]
				});
			break;
		}
		for (thing in things) {
			if (typeof(Object.prototype[thing]) == 'undefined') {
				Object.extend(things[thing], {
					right: things[thing].left+things[thing].width,
					bottom: things[thing].top+things[thing].height,
					center: things[thing].left+(things[thing].width/2),
					middle:	things[thing].top+(things[thing].height/2)
				});
			}
		}

		var newPosition = {
			top: this.s.position.offsetY|0, //Adam added the |0 to both of these lines to make the offsetY and offsetX optional
			left: this.s.position.offsetX|0
		};
		
		//start it out in the position of the exemplar top left is equal to the self top left
		newPosition.left += things.exemplar.left;
		newPosition.top += things.exemplar.top;
		
		//if they want it horizontally centered in the exemplar, add half the width of the exemplar
		newPosition.left += (things.exemplar.anchors.inArray('center') != -1) ? things.exemplar.width/2 : 0;
		//if they want it on the right of the exemplar, add the whole width of the exemplar
		newPosition.left += (things.exemplar.anchors.inArray('right') != -1) ? things.exemplar.width : 0;
		//if they want it vertically centered in the exemplar, add half the height of the exemplar
		newPosition.top += (things.exemplar.anchors.inArray('middle') != -1) ? things.exemplar.height/2 : 0;
		//if they want it on the bottom of the exemplar, add the whole height of the exemplar
		newPosition.top += (things.exemplar.anchors.inArray('bottom') != -1) ? things.exemplar.height : 0;
		
		//if they want it horizontally centered in itself, subtract half the width of itself
		newPosition.left -= (things.self.anchors.inArray('center') != -1) ? things.self.width/2 : 0;
		//if they want it at the right of itself, subtract the whole width of itself
		newPosition.left -= (things.self.anchors.inArray('right') != -1) ? things.self.width : 0;
		//if they want it vertically centered in itself, subtract half the height of itself
		newPosition.top -= (things.self.anchors.inArray('middle') != -1) ? things.self.height/2 : 0;
		//if they want it at the bottom of itself, subtract the whole height of itself
		newPosition.top -= (things.self.anchors.inArray('bottom') != -1) ? things.self.height : 0;
		
		if(newPosition.top+things.self.height>document.viewport.getScrollOffsets()[1]+document.viewport.getHeight())
		{
			newPosition.top -= (things.self.height-things.exemplar.height);
		}
		
		if(newPosition.top<document.viewport.getScrollOffsets()[1])
		{
			newPosition.top += things.self.height-things.exemplar.height;
		}
		
		if(newPosition.left+things.self.width>document.viewport.getScrollOffsets()[0]+document.viewport.getWidth())
		{
			newPosition.left -= things.self.width+things.exemplar.width;
		}
		
		if(newPosition.left<document.viewport.getScrollOffsets()[0])
		{
			newPosition.left += things.self.width+things.exemplar.width;
		}

		/* Adam Removed. Kramer, please look at it and see if you agree that mine is correct.
		newPosition.left += (things.exemplar.anchors.inArray('left') != -1) ? things.exemplar.left : 0;
		newPosition.left += (things.exemplar.anchors.inArray('center') != -1) ? things.exemplar.center : 0;
		newPosition.left += (things.exemplar.anchors.inArray('right') != -1) ? things.exemplar.right : 0;
		newPosition.top += (things.exemplar.anchors.inArray('top') != -1) ? things.exemplar.left : 0;
		newPosition.top += (things.exemplar.anchors.inArray('middle') != -1) ? things.exemplar.middle : 0;
		newPosition.top += (things.exemplar.anchors.inArray('bottom') != -1) ? things.exemplar.bottom : 0;

		newPosition.left -= (things.self.anchors.inArray('left') != -1) ? things.self.left : 0;
		newPosition.left -= (things.self.anchors.inArray('center') != -1) ? things.self.center : 0;
		newPosition.left -= (things.self.anchors.inArray('right') != -1) ? things.self.right : 0;
		newPosition.top -= (things.self.anchors.inArray('top') != -1) ? things.self.left : 0;
		newPosition.top -= (things.self.anchors.inArray('middle') != -1) ? things.self.middle : 0;
		newPosition.top -= (things.self.anchors.inArray('bottom') != -1) ? things.self.bottom : 0;
		*/

		Object.extend(this.n.superNode.style, {
			top: newPosition.top+'px',
			left: newPosition.left+'px'
		});
	},

	setContent: function(html) {
		this.n.subNode.innerHTML = html;
		this._sizeIframe();
	},

	withAllRegisteredNodes: function(func) {
		// Executes a callback on all nodes in the node registry.
		for (var x=0; x<this.activeNodes.length; x++) {
			func(this.activeNodes[x]);
		}
	},

	getGroupId: function() {
		return this.s.groupId;
	},

	// Private methods
	_sizeIframe: function() {
		if (this.s.useIframe && this.n.iframe) {
			var nodeSize = Element.getDimensions(this.n.subNode);
			this.n.iframe.style.height = Math.max(0, (nodeSize['height'] - this.s.iframeShrink))+'px';
			this.n.iframe.style.width = Math.max(0, (nodeSize['width'] - this.s.iframeShrink))+'px';
		}
	},

	_createIframe: function() {
		if (this.s.useIframe && !this.n.iframe) {
			this.n.iframe = document.createElement('iframe');
			this.n.iframe.setAttribute('href','javascript:void();');
			Object.extend(this.n.iframe.style, {
				scrolling:    'no',
				position:     'relative',
				marginwidth:  0,
				marginheight: 0,
				top:          0,
				left:         0,
				frameborder:  0,
				zIndex:       -1,
				display:      'block',
				filter:		  'alpha(opacity=0)',
				opacity:	  '0'
			});
			this.n.superNode.appendChild(this.n.iframe);
			this._sizeIframe();
		}
	},

	_createNode: function(tagName) {
		if (this.nodeContainer === null) {
			this.nodeContainer = document.createElement('div');
			this.n._body.appendChild(this.nodeContainer);
		}
		var node = document.createElement(tagName);
		this.nodeContainer.appendChild(node);
		return node;
	},

	_isExtant: function() {
		return this.s.extant;
	},

	_isVisible: function() {
		return this.s.visible;
	},

	_register: function() {
		this.s.nodeId = this.activeNodes.length;
		this.activeNodes.push(this);
	},

	_unRegister: function() {
		this.eUnObserveAll();
		this.activeNodes.splice(this.s.nodeId,1);
	}
});
/**
 * kDialog2
 * @requires NodeBase
 */
var kDialog2 = NodeBase.extend({
	// Static properties
	activeDialogs: [],
	lightBox: null,

	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			this.modFlurryTimeout = null;

			Object.extendProperties(this.s, {
				groupId: 'kDialog2',
				modFlurryThrottle: 102, // MS to wait after modifications to content before events, triggers, etc are all reacquired. Prevents a million re-acquisitions during a flurry of modifications.
				useDraggable: (typeof(Draggable) != 'undefined'),
				outerTriggers: [],
				innerTriggers: [
					{node: '.close', event: 'click', action: 'destroy'},
					{node: '*', event: 'mousedown', action: 'activate'}
				],
				innerZones: [
					{node: '.heading', name: 'heading'},
					{node: '.content', name: 'content'},
					{node: '.text', name: 'text'}
				],
				contentHTML: '<div class="top"><span class="right"></span><span class="left"></span></div><div class="contwrapper">' +
					'<dl>' +
					'	<dt><em class="heading">Dialog</em><span class="close">x</span></dt>' +
					'	<dd class="content"><div class="text"></div></dd>' +
					'</dl>' +
					'</div><div class="bot"><span class="right"></span><span class="left"></span></div>',
				startHidden: false,
				classNames: ['kDialog2'],
				zones: {
					heading: 'kDialog2',
					text: ''
				},
				lightBox: {
					visible: true,
					opacity: 40,
					color: '#fff',
					className: 'lightbox'
				}
			});
			Object.extendProperties(this.s, settings);

			Object.extendProperties(this.c, {
				contentZones: [],
				triggerEvents: []
			});
		}
	},

	constructor: function(settings) {
		this.settings(settings);
		this.create();
	},

	// Convenience methods
	setContent: function(html) {
		this.setZone('content', html);
	},
	setText: function(html) {
		this.setZone('text', html);
	},
	setHeading: function(html) {
		this.setZone('heading', html);
	},

	create: function() {
		this.base();
		if (!kDialog2.lightBox && this.s.lightBox.visible) this._createLightBox();
		this._setInitialDialogContent();
		this.activeDialogs.push(this);
		if (!this.s.startHidden) this.show();
	},

	activate: function() {
		Element.addClassName(this.n.subNode, 'active');
		this.n.superNode.style.zIndex = this.s.zIndex+1000;
		this.n.subNode.style.zIndex = this.s.zIndex+1100;
		for (var x=0; x<this.activeDialogs.length; x++) {
			if (this.activeDialogs[x] != this) this.activeDialogs[x].deactivate();                                  // Deactivate all others
		}
	},

	deactivate: function() {
		Element.removeClassName(this.n.subNode, 'active');
		this.n.superNode.style.zIndex = this.s.zIndex-10;
		this.n.subNode.style.zIndex = this.s.zIndex-11;
	},

	show: function() {
		this.base();
		this.activate();
		if (this.lightBox) Element.show(this.lightBox);
	},

	hide: function() {
		this.base();
		if (this.lightBox) Element.hide(this.lightBox);
	},

	destroy: function() {
		for (var x=0; x<this.activeDialogs.length; x++) {
			if (this.activeDialogs[x] == this) this.activeDialogs.splice(x, 1);                                     // Remove me from the dialog registry
		}
		this.hide();
		this.base();
	},

	// Public methods
	setContent: function(html) {
		this.base(html);
		this._afterModification();
	},

	activateTrigger: function(trigger) {
		if (typeof(trigger.node) == 'string') {
			var nodes = document.getElementsBySelector(trigger.node, this.n.subNode);
		} else {
			var nodes = [trigger.node];
		}
		if (typeof(trigger.action) == 'string') {
			trigger.action = this[trigger.action].bind(this);
		} else if (trigger.action instanceof Function) {
			// noop
		} else if (trigger.action instanceof Object) {
			trigger.action = this[trigger.action.method].bind(this, trigger.action.argument);
		}
		for (var x=0; x<nodes.length; x++) {
			this.c.triggerEvents.push(this.eObserve(nodes[x], trigger.event, function(e) {
				trigger.action(this, e);
			}.bind(this)));
		}
	},

	setZone: function(zoneName, html, noAfterMod) {
		for (var x=0; x<this.c.contentZones.length; x++) {
			if (this.c.contentZones[x].name == zoneName) {
				for (var y=0; y<this.c.contentZones[x].nodes.length; y++) {
					this.c.contentZones[x].nodes[y].innerHTML = html;
				}
			}
		}
		if (!noAfterMod) this._afterModification();
	},

	getZoneNodes: function(zoneName) {
		for (var x=0; x<this.c.contentZones.length; x++) {
			if (this.c.contentZones[x].name == zoneName) {
				return this.c.contentZones[x].nodes;
			}
		}
		return [];
	},

	registerZone: function(zone) {
		var existingZone = this.getZoneNodes(zone.name);
		if (existingZone.length) {
			// already exists
		} else {
			this.s.innerZones.push(zone);
			this._locateZone(zone);
		}
	},

	// Private methods
	_createLightBox: function() {
		this.lightBox = document.createElement('div');
		Element.addClassName(this.lightBox, this.lightBox.className);
		var bodySize = Element.getDimensions(this.n._body);
		var lightBoxSize = {
			height: Math.max(bodySize.height, getViewportSize().height),
			width: Math.max(bodySize.width, getViewportSize().height)
		};
		Object.extend(this.lightBox.style, {
			height: lightBoxSize.height+'px',
			width: lightBoxSize.width+'px',
			background: this.s.lightBox.color,
			filter: 'alpha(opacity='+this.s.lightBox.opacity+')',
			opacity: (this.s.lightBox.opacity/100),
			position: 'absolute',
			left: '0',
			top: '0'
		});
		this.n._body.appendChild(this.lightBox);
	},

	_setInitialDialogContent: function() {
		this.setContent(this.s.contentHTML);
		for (var zoneName in this.s.zones) {
			if (typeof(Object.prototype[zoneName]) == 'undefined') {
				this.setZone(zoneName, this.s.zones[zoneName], true);
			}
		}
		this._afterModification();
	},

	_unObserveTriggerEvents: function() {
		for (var x=0; x<this.c.triggerEvents.length; x++) {
			this.eUnObserve(this.c.triggerEvents[x]);
		}
	},

	_afterModification: function(force) {
		this._locateZones();
		if (force) {
			this._sizeIframe();
			this._activateTriggers();
			if (this.s.useDraggable) new Draggable(this.n.superNode, {handle: this.n.subNode.getElementsByTagName('dt')[0]});
		} else {
			clearTimeout(this.modFlurryTimeout);
			this.modFlurryTimeout = setTimeout(this._afterModification.bind(this, true), this.s.modFlurryThrottle);
		}
	},

	_locateZones: function() {
		this.c.contentZones = [];
		for (var x=0; x<this.s.innerZones.length; x++) {
			this._locateZone(this.s.innerZones[x]);
		}
	},

	_locateZone: function(zone) {
		var nodes = null;
		if (typeof(zone.node) == 'string') {
			nodes = document.getElementsBySelector(zone.node, this.n.subNode);
		} else {
			nodes = [zone.node];
		}
		this.c.contentZones.push({
			name: zone.name,
			nodes: nodes
		});
	},

	_activateTriggers: function() {
		this._unObserveTriggerEvents();
		for (var x=0; x<this.s.innerTriggers.length; x++) {
			this.activateTrigger(this.s.innerTriggers[x]);
		}
		for (var x=0; x<this.s.outerTriggers.length; x++) {
			this.activateTrigger(this.s.outerTriggers[x]);
		}
	}
});
/**
 * FormDialog
 * @requires kDialog2
 */
var FormDialogIframeResponse = [];
var FormDialog = kDialog2.extend({
	// Static properties
	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			Object.extendProperties(this.s, {
				startHidden: true,
				onSubmit: function() {},
				'+innerZones': [
					{node: '.form', name: 'form'}
				],
				'+zones': {
					heading: 'Form Dialog',
					content: '		<div class="form"></div>'
				},
				isFile: false,
				button_types: [],

				moduleName: null,
				json: {
					ver: '0.1',
					meta: {},
					data: {
						requests: []
					}
				},
				showErrors: ['kramer'],
				showDebugs: ['kramer'],
				parentThis: null,
				recordID: null,
				moduleID: null,
				ceID: null,
				onClose: function() {},
				params: {}
			});
			Object.extendProperties(this.s, settings);
		}
	},

	constructor: function(settings) {
		this.settings(settings);
		this.create();
		//alert(this.getZoneNodes('form').length);
		this._setInitialDialogContent();
		this.addRequest('getForm', {});
		this.PopupOut();
	},

	serializeForm: function(form){
		/*
		// Adam's perpetually broken form serializer
		var eles = $A(form.getElementsByTagName('input'));
		eles = eles.concat($A(form.getElementsByTagName('select')));
		eles = eles.concat($A(form.getElementsByTagName('textarea')));
		var fields = [];
		for(var x=0; x<eles.length; x++){
			if(eles[x].name && !eles[x].disabled && (eles[x].type.toLowerCase() != "radio" || eles[x].checked == true)){
				fields.push(eles[x].name + "=" + escape(eles[x].value));
			}
		}
		return fields.join('&');
		*/

		// Let's use a serializer that works
		return Form.serialize(form);
	},

	onSubmit: function(el, e) {
		//el is the button object clicked

		// captures the serialized form
		var serialize = this.serializeForm(this.getZoneNodes('form')[0].getElementsByTagName('form')[0]);

		// let's remove the image buttons that weren't clicked on
		var new_Serialize = [];
		var params = serialize.split("&");
		for (var i=0 ; i<params.length; i++) {
			var param =  params[i].split("=");
			if(el.name == param[0] || this.s.button_types.inArray(param[0]) == -1)
				new_Serialize.push(params[i]);
		}
		serialize = new_Serialize.join('&');

		// builds the JSON object
		this.s.json.data.requests.push(
			{
				id: 0,
				type: 'submitForm',
				data: {
					postData: escape(serialize)
				}
			}
		);

		// Kills the event for everything except file upload
		if (!this.s.isFile) {
			Event.stop(e);
			this.PopupOut();
		}

		// Handles file uploads
		else {
			var form = this.getZoneNodes('form')[0].getElementsByTagName('form')[0];
			form.action = form.action + this.s.moduleName + '&data=' + toJSONString(this.s.json) + '&formId=' + FormDialogIframeResponse.length;
			this.s.json.data.requests = [];
			FormDialogIframeResponse.push(this.fakePopupIn.bind(this));
		}
	},

	initializeScripts: function() {
		//instantiate and use the object
		if(typeof(aAccordion) != 'undefined'){
			var aAccordions = document.getElementsBySelector('.aAccordion', this.getZoneNodes('form')[0]);
			for(var x=0; x<aAccordions.length; x++){
				new aAccordion(aAccordions[x], {
					selHeading: 'h3',
					openClass: 'selected'
				}, {
					topEl: aAccordions[x]
				});
			}
		}
		
		var languages = document.getElementsBySelector('select.languages', this.getZoneNodes('form')[0]);
		for(var x=0; x<languages.length; x++){
			new LocalizedFormData(languages[x]);
		}

		var showMore = document.getElementsBySelector('select.selectShowMore', this.getZoneNodes('form')[0]);
		for(var x=0; x<showMore.length; x++){
			new selectShowMore(showMore[x]);
		}

		var datePicker = document.getElementsBySelector('input.date', this.getZoneNodes('form')[0]);
		for(var x=0; x<datePicker.length; x++){
			new kDatePicker({
				n_formField: datePicker[x],
				format: 'Y-m-d'
			});
		}
		
		var timePicker = document.getElementsBySelector('input.time', this.getZoneNodes('form')[0]);
		for(var x=0; x<timePicker.length; x++){
			new kTimePicker({
				node: timePicker[x],
				imgUp: '/themes/admin_default/i/up.gif',
				imgDown: '/themes/admin_default/i/down.gif'
			});
		}
		
		if(typeof(kClonable) != 'undefined'){
			var kClonables = document.getElementsBySelector('.clone', this.getZoneNodes('form')[0]);
			for(var x=1; x<kClonables.length; x++){
				new kClonable({
					container: kClonables[x].parentNode,
					afterClone: function(node) {}
				})
			}
		}
		/*if(typeof(kNumberBox) != 'undefined'){
			var date = new Date();
			date.setHours(0);
			date.setMinutes(0);
			date.setSeconds(0);
			date.setMilliseconds(0);

			var curDate = String(date.getDate());
			var times = [];
			while(curDate == String(date.getDate())){
				times.push(date.format('g:i a'));
				date.setMinutes(date.getMinutes()+15);
			}
			var kNumberBoxes = document.getElementsBySelector('input.time', this.getZoneNodes('form')[0]);
			for(var x=0; x<kNumberBoxes.length; x++){
				new kNumberBox({
					node: kNumberBoxes[x],
					imgUp: '/themes/admin_default/i/up.gif',
					imgDown: '/themes/admin_default/i/down.gif',
					amount_ctrl: 10,
					amount_shift: 20,
					waitBeforeRepeat: 500,
					repeatDelay: 50,
					max: null,
					min: null,
					options: times,
					validate: /^\s*1?[0-9]{1}:[0-9]{2}\s*([ap]{1}m{1})\s*$/i
				});
			}
		}*/
		
		var kMultilists = document.getElementsBySelector('.multilistAJAJ', this.getZoneNodes('form')[0]);
		for(var x=0; x<kMultilists.length; x++){
			new kMultiListAJAJ(kMultilists[x]);
		}
		
		var rSelects = document.getElementsBySelector('select.rSelect', this.getZoneNodes('form')[0]);
		for(var x=0; x<rSelects.length; x++){
			new rSelectClass (rSelects[x], {
			});
		}
		
		var rSelectsFriends = document.getElementsBySelector('select.rSelectFriends', this.getZoneNodes('form')[0]);
		for(var x=0; x<rSelectsFriends.length; x++){
			new rSelectClass (rSelectsFriends[x], {
			   defaultLabel: 'No Friends Selected' 
			}); 
		}

		//new kMultiListAJAJ(el);
	},
	addRequest: function(requestName, dataObject) {
		Object.extend(dataObject, this.s.params);
		this.s.json.data.requests.push({
			id: this.s.json.data.requests.length,
			type: requestName,
			data: dataObject
		});
	},
	//popup methods
	PopupOut: function() {
		var data = '__popup=' + this.s.moduleName + ((this.s.recordID != null)? '&recordID='+this.s.recordID : '' ) + ((this.s.ceID != null)? '&ceID='+this.s.ceID : '' ) + ((this.s.moduleID != null)? '&moduleID='+this.s.moduleID : '' ) + '&data=' + toJSONString(this.s.json);
		//this.debug('Posting\n'+data+'\nto:\n'+window.location);
		this.s.json.data.requests = [];
		//send out the request
		var myAjaj = new Ajax.Request(window.location,
		{
			method: 'post',
			parameters: data,
			onSuccess: this.PopupIn.bind(this)
		});
	},
	fakePopupIn: function (t) {
		//document.write(t);
		this.PopupIn({responseText: t});
	},

	PopupIn: function(t) {
		//shout(t.responseText,'vinod');
		try {
			var result = t.responseText.parseJSON();
		} catch(e) {
			this.error('Cant parse JSON\n\n'+t.responseText);
			return false;
		}
		if (result instanceof Object) {
			if (result.meta) {
				if (result.meta.errors instanceof Array) {
					if (result.meta.errors.length) {
						var errorText = 'The following error(s) occurred on the server:\n\n';
						for (var x=0; x<result.meta.errors.length; x++) {
							errorText += x+') '+result.meta.errors[x]+'\n';
						}
						this.error(errorText);
					}
				}
			}
			if (result.responses instanceof Array) {
				(result.responses.length).times(function(i) {
					switch(result.responses[i].data.action){
					case "showForm":
						this.s.isFile = false;
						//alert(result.data.html);
						this.setZone('form', result.data.html);
						this.setZone('heading', result.data.headline);
						this.exploreForm();
						this.show();
						this._findSubmitButtons();
						this.initializeScripts();
						break;
					case "closeForm":
						if(result.responses[i].data.wait && result.responses[i].data.wait > 0){
							setTimeout(function() {
								this.destroy();
								this.s.onClose(result);
								if (this.s.parentThis.addRequest instanceof Function) this.s.parentThis.addRequest('refresh', {});
								if (this.s.parentThis.JsonOut instanceof Function) this.s.parentThis.JsonOut();
							}.bind(this), result.responses[i].data.wait);
						}else{
							this.destroy();
							this.s.onClose(result);
							if (this.s.parentThis.addRequest instanceof Function) this.s.parentThis.addRequest('refresh', {});
							if (this.s.parentThis.JsonOut instanceof Function) this.s.parentThis.JsonOut();
						}
					    break;
					case "killForm":
						this.destroy();
						this.s.onClose(result);
						break;
					case "refreshModules":
						RefreshModule.refresh(result.responses[i].data.moduleNames);
						break;
					case "redirect":
						window.location = result.responses[i].data.href;
						break;
					case "reload":
						window.location.reload();
						break;
					}
				}.bind(this));
			} else {
				this.error('No FormDialog command came from the server.');
			}
		} else {
			this.error('ParseJSON() did not return an object.');
			this.destroy();
		}
	},

	exploreForm: function () {
		var formInputNodes = this.getZoneNodes('form')[0].getElementsByTagName('input');
		$A(formInputNodes).each(function(el, index){
			if(this.s.isFile == false) {
				if (el.type.toLowerCase() == 'file') {
					this.s.isFile = true;
					this.TransformForm();
					return true;
				}
			}
		}.bind(this));
	},

	TransformForm: function() {
		//create iframe
		var iframe = document.createElement('iframe');
		iframe.name = 'iframeHidden';
		iframe.id = 'iframeHidden';
		Element.hide(iframe);
		var formDivNode = this.getZoneNodes('form')[0];
		formDivNode.appendChild(iframe);
		
		if(self.frames['iframeHidden'].name != 'iframeHidden') 
		{
			/* IMPORTANT: BUG FIX for IE 6 & 7 */ 
			self.frames['iframeHidden'].name = 'iframeHidden'; 
		}
		
		if(self.frames['iframeHidden'].name != 'iframeHidden') 
		{
			/* IMPORTANT: BUG FIX for IE 6 & 7 */ 
			self.frames['iframeHidden'].name = 'iframeHidden'; 
		}

		//modify form
		var form = this.getZoneNodes('form')[0].getElementsByTagName('form')[0];
		form.action = window.location + '?__popup=';
		form.target = 'iframeHidden';

		//create hidden <input type="hidden" name="fakeAJAJ" value="1" />
		var hidden = document.createElement('input');
		hidden.name = 'fakeAJAJ';
		hidden.type = 'hidden';
		hidden.value = '1';
		form.appendChild(hidden);
	},

	_findSubmitButtons: function() {
		var buttons = this.getZoneNodes('form')[0].getElementsByTagName('input');
		$A(buttons).each(function(object, index) {
			if((object.type.toLowerCase() == "submit" || object.type.toLowerCase() == "image")){
				this.s.button_types.push(object.name);
				if (!Element.hasClassName(object, 'close'))
				this.eObserve(object, 'click', this.onSubmit.bind(this, object));
			}
			//listen for the different types of image buttons. Base it off of a classname image_cancel, image_save, image_save_close, image_save_edit
		}.bind(this));
	},

	error: function(text) {
		this.base();
		//alert(text);
		//debugger;
	}
});

// script.aculo.us effects.js v1.7.1_beta2, Sat Apr 28 15:20:12 CEST 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/ 

// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
}

Element.collectTextNodesIgnoreClass = function(element, className) {  
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue : 
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
}

Element.setContentZoom = function(element, percent) {
  element = $(element);  
  element.setStyle({fontSize: (percent/100) + 'em'});   
  if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
}

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
}

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  tagifyText: function(element) {
    if(typeof Builder == 'undefined')
      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
      
    var tagifyStyle = 'position:relative';
    if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
    
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || {});
    Effect[element.visible() ? 
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {
  linear: Prototype.K,
  sinoidal: function(pos) {
    return (-Math.cos(pos*Math.PI)/2) + 0.5;
  },
  reverse: function(pos) {
    return 1-pos;
  },
  flicker: function(pos) {
    var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
    return (pos > 1 ? 1 : pos);
  },
  wobble: function(pos) {
    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
  },
  pulse: function(pos, pulses) { 
    pulses = pulses || 5; 
    return (
      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
      );
  },
  none: function(pos) {
    return 0;
  },
  full: function(pos) {
    return 1;
  }
};

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  initialize: function() {
    this.effects  = [];
    this.interval = null;    
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++) 
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if(typeof queueName != 'string') return queueName;
    
    if(!this.instances[queueName])
      this.instances[queueName] = new Effect.ScopedQueue();
      
    return this.instances[queueName];
  }
}
Effect.Queue = Effect.Queues.get('global');

Effect.DefaultOptions = {
  transition: Effect.Transitions.sinoidal,
  duration:   1.0,   // seconds
  fps:        100,   // 100= assume 66fps max.
  sync:       false, // true for combining
  from:       0.0,
  to:         1.0,
  delay:      0.0,
  queue:      'parallel'
}

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if(options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;
    
    eval('this.render = function(pos){ '+
      'if(this.state=="idle"){this.state="running";'+
      codeForEvent(options,'beforeSetup')+
      (this.setup ? 'this.setup();':'')+ 
      codeForEvent(options,'afterSetup')+
      '};if(this.state=="running"){'+
      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
      'this.position=pos;'+
      codeForEvent(options,'beforeUpdate')+
      (this.update ? 'this.update(pos);':'')+
      codeForEvent(options,'afterUpdate')+
      '}}');
    
    this.event('beforeStart');
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = Math.round(pos * this.totalFrames);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if(!this.options.sync)
      Effect.Queues.get(typeof this.options.queue == 'string' ? 
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if(typeof this[property] != 'function') data[property] = this[property];
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}

Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if(effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Event = Class.create();
Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
  initialize: function() {
    var options = Object.extend({
      duration: 0
    }, arguments[0] || {});
    this.start(options);
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    // make this work on IE on elements without 'layout'
    if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || {});
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Bug in Opera: Opera returns the "real" position of a static element or
    // relative element that does not have top/left explicitly set.
    // ==> Always set top and left for position relative elements in your stylesheets 
    // (to 0 if you do not need them) 
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if(this.options.mode == 'absolute') {
      // absolute movement, so we need to calc deltaX and deltaY
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    });
  }
});

// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element, 
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};

Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = Math.round(width) + 'px';
    if(this.options.scaleY) d.height = Math.round(height) + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    this.start(options);
  },
  setup: function() {
    // Prevent executing on elements not in the layout flow
    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    // Disable background image during the effect
    this.oldStyle = {};
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if(!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if(!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    // init color calculations
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    this.start(arguments[1] || {});
  },
  setup: function() {
    Position.prepare();
    var offsets = Position.cumulativeOffset(this.element);
    if(this.options.offset) offsets[1] += this.options.offset;
    var max = window.innerHeight ? 
      window.height - window.innerHeight :
      document.body.scrollHeight - 
        (document.documentElement.clientHeight ? 
          document.documentElement.clientHeight : document.body.clientHeight);
    this.scrollStart = Position.deltaY;
    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  },
  update: function(position) {
    Position.prepare();
    window.scrollTo(Position.deltaX, 
      this.scrollStart + (position*this.delta));
  }
});

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
  from: element.getOpacity() || 1.0,
  to:   0.0,
  afterFinishInternal: function(effect) { 
    if(effect.options.to!=0) return;
    effect.element.hide().setStyle({opacity: oldOpacity}); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  // force Safari to render floated elements properly
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show(); 
  }}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = { 
    opacity: element.getInlineOpacity(), 
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200, 
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
     Object.extend({ duration: 1.0, 
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element)
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || {})
   );
}

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleX: false, 
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || {})
  );
}

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || {}));
}

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, { 
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) { 
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      })
    }
  }, arguments[1] || {}));
}

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned(); 
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        } 
      }, arguments[1] || {}));
}

Effect.Shake = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element, 
      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}) }}) }}) }}) }}) }});
}

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleX: false, 
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || {})
  );
}

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false, 
    scaleX: false, 
    scaleMode: 'box',
    scaleFrom: 100,
    restoreAfterFinish: true,
    beforeStartInternal: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if(window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },  
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
      effect.element.down().undoPositioned();
    }
   }, arguments[1] || {})
  );
}

// Bug in opera makes the TD containing this element expand for a instance after finish 
Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping(); 
    }
  });
}

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();    
  var initialMoveX, initialMoveY;
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0; 
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }
  
  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01, 
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show(); 
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
             }
           }, options)
      )
    }
  });
}

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || {});
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;
  
  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':  
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }
  
  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({            
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping(); 
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
}

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || {};
  var oldOpacity = element.getInlineOpacity();
  var transition = options.transition || Effect.Transitions.sinoidal;
  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  reverser.bind(transition);
  return new Effect.Opacity(element, 
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
}

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({   
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, { 
      scaleContent: false, 
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || {}));
};

Effect.Morph = Class.create();
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
  initialize: function(element) {
    this.element = $(element);
    if(!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: {}
    }, arguments[1] || {});
    if (typeof options.style == 'string') {
      if(options.style.indexOf(':') == -1) {
        var cssText = '', selector = '.' + options.style;
        $A(document.styleSheets).reverse().each(function(styleSheet) {
          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
          else if (styleSheet.rules) cssRules = styleSheet.rules;
          $A(cssRules).reverse().each(function(rule) {
            if (selector == rule.selectorText) {
              cssText = rule.style.cssText;
              throw $break;
            }
          });
          if (cssText) throw $break;
        });
        this.style = cssText.parseStyle();
        options.afterFinishInternal = function(effect){
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            if(transform.style != 'opacity')
              effect.element.style[transform.style] = '';
          });
        }
      } else this.style = options.style.parseStyle();
    } else this.style = $H(options.style)
    this.start(options);
  },
  setup: function(){
    function parseColor(color){
      if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if(value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if(property == 'opacity') {
        value = parseFloat(value);
        if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if(Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return { 
        style: property.camelize(), 
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      )
    });
  },
  update: function(position) {
    var style = {}, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] = 
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        transform.originalValue + Math.round(
          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create();
Object.extend(Effect.Transform.prototype, {
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || {};
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      var data = $H(track).values().first();
      this.tracks.push($H({
        ids:     $H(track).keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var elements = [$(track.ids) || $$(track.ids)].flatten();
        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');
  
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.prototype.parseStyle = function(){
  var element = document.createElement('div');
  element.innerHTML = '<div style="' + this + '"></div>';
  var style = element.childNodes[0].style, styleRules = $H();
  
  Element.CSS_PROPERTIES.each(function(property){
    if(style[property]) styleRules[property] = style[property]; 
  });
  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
  }
  return styleRules;
};

Element.morph = function(element, style) {
  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
  return element;
};

['getInlineOpacity','forceRerendering','setContentZoom',
 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
  function(f) { Element.Methods[f] = Element[f]; }
);

Element.Methods.visualEffect = function(element, effect, options) {
  s = effect.dasherize().camelize();
  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  new Effect[effect_class](element, options);
  return $(element);
};

Element.addMethods();

Effect.SlideRightIntoView = function(element) {
  $(element).style.width = '0px';
  $(element).style.overflow = 'hidden';
  $(element).firstChild.style.position = 'relative';
  Element.show(element);
  new Effect.Scale(element, 100,
    Object.extend(arguments[1] || {}, {
      scaleContent: false,
      scaleY: false,
      scaleMode: 'contents',
      scaleFrom: 0,
      afterUpdate: function(effect){Element.show(effect.element);}
    })
  );
}

Effect.SlideRightOutOfView = function(element) {
  $(element).style.overflow = 'hidden';
  $(element).firstChild.style.position = 'relative';
  Element.show(element);
  new Effect.Scale(element, 0,
    Object.extend(arguments[1] || {}, {
      scaleContent: false,
      scaleY: false,
      afterUpdate: function(effect){},
      afterFinish: function(effect)
        { Element.hide(effect.element); }
    })
  );
}
// script.aculo.us dragdrop.js v1.7.1_beta2, Sat Apr 28 15:20:12 CEST 2007

// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// 
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

if(typeof Effect == 'undefined')
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || {});

    // cache containers
    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if((typeof containment == 'object') && 
        (containment.constructor == Array)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }
    
    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },
  
  findDeepestChild: function(drops) {
    deepest = drops[0];
      
    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];
    
    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode; 
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },
  
  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect( 
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var affected = [];
    
    if(this.last_active) this.deactivate(this.last_active);
    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });
        
    if(affected.length>0) {
      drop = Droppables.findDeepestChild(affected);
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
      
      Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) {
        this.last_active.onDrop(element, this.last_active.element, event); 
        return true; 
      }
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
}

var Draggables = {
  drags: [],
  observers: [],
  
  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
      
      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },
  
  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },
  
  activate: function(draggable) {
    if(draggable.options.delay) { 
      this._timeout = setTimeout(function() { 
        Draggables._timeout = null; 
        window.focus(); 
        Draggables.activeDraggable = draggable; 
      }.bind(this), draggable.options.delay); 
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },
  
  deactivate: function() {
    this.activeDraggable = null;
  },
  
  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    // Mozilla-based browsers fire successive mousemove events with
    // the same coordinates, prevent needless redrawing (moz bug?)
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;
    
    this.activeDraggable.updateDrag(event, pointer);
  },
  
  endDrag: function(event) {
    if(this._timeout) { 
      clearTimeout(this._timeout); 
      this._timeout = null; 
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },
  
  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },
  
  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },
  
  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },
  
  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },
  
  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
}

/*--------------------------------------------------------------------------*/

var Draggable = Class.create();
Draggable._dragging    = {};

Draggable.prototype = {
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){ 
            Draggable._dragging[element] = false 
          }
        }); 
      },
      zindex: 1000,
      revert: false,
      quiet: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };
    
    if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
        }
      });
    
    var options = Object.extend(defaults, arguments[1] || {});

    this.element = $(element);
    
    if(options.handle && (typeof options.handle == 'string'))
      this.handle = this.element.down('.'+options.handle, 0);
    
    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;
    
    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE    

    this.delta    = this.currentDelta();
    this.options  = options;
    this.dragging = false;   

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);
    
    Draggables.register(this);
  },
  
  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },
  
  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },
  
  initDrag: function(event) {
    if(typeof Draggable._dragging[this.element] != 'undefined' &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {    
      // abort on form elements, fixes a Firefox issue
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;
        
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
      
      Draggables.activate(this);
      Event.stop(event);
    }
  },
  
  startDrag: function(event) {
    this.dragging = true;
    
    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }
    
    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }
    
    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }
    
    Draggables.notify('onStart', this, event);
        
    if(this.options.starteffect) this.options.starteffect(this.element);
  },
  
  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);
    
    if(!this.options.quiet){
      Position.prepare();
      Droppables.show(pointer, this.element);
    }
    
    Draggables.notify('onDrag', this, event);
    
    this.draw(pointer);
    if(this.options.change) this.options.change(this);
    
    if(this.options.scroll) {
      this.stopScrolling();
      
      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }
    
    // fix AppleWebKit rendering
    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
    
    Event.stop(event);
  },
  
  finishDrag: function(event, success) {
    this.dragging = false;
    
    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

    if(this.options.ghosting) {
      Position.relativize(this.element);
      Element.remove(this._clone);
      this._clone = null;
    }

    var dropped = false; 
    if(success) { 
      dropped = Droppables.fire(event, this.element); 
      if (!dropped) dropped = false; 
    }
    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && typeof revert == 'function') revert = revert(this.element);
    
    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      if (dropped == 0 || revert != 'failure')
        this.options.reverteffect(this.element,
          d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect) 
      this.options.endeffect(this.element);
      
    Draggables.deactivate(this);
    Droppables.reset();
  },
  
  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },
  
  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },
  
  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }
    
    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];
    
    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }
    
    var p = [0,1].map(function(i){ 
      return (point[i]-pos[i]-this.offset[i]) 
    }.bind(this));
    
    if(this.options.snap) {
      if(typeof this.options.snap == 'function') {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(this.options.snap instanceof Array) {
        p = p.map( function(v, i) {
          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
      } else {
        p = p.map( function(v) {
          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
      }
    }}
    
    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";
    
    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },
  
  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },
  
  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },
  
  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }
    
    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }
    
    if(this.options.change) this.options.change(this);
  },
  
  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
}

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create();
SortableObserver.prototype = {
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },
  
  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
}

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  
  sortables: {},
  
  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {  
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },
  
  destroy: function(element){
    var s = Sortable.options(element);
    
    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');
      
      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({ 
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false, 
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,
      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || {});

    // clear any old sortable with same element
    this.destroy(element);

    // build options for the draggables
    var options_for_draggable = {
      revert:      true,
      quiet:       options.quiet,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    // build options for the droppables  
    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    }
    
    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    }

    // fix for gecko engine
    Element.cleanWhitespace(element); 

    options.draggables = [];
    options.droppables = [];

    // drop on empty handling
    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (this.findElements(element, options) || []).each( function(e) {
      // handles are per-draggable
      var handle = options.handle ? 
        $(e).down('.'+options.handle,0) : e;    
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);      
    });
    
    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    // keep reference
    this.sortables[element.id] = options;

    // for onupdate
    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  // return all suitable-for-sortable elements in a guaranteed order
  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },
  
  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode) 
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },
  
  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);
        
    if(!Element.isParent(dropon, element)) {
      var index;
      
      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;
            
      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
        
        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }
      
      dropon.insertBefore(element, child);
      
      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    // mark on ghosting only
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return; 

    if(!Sortable._marker) {
      Sortable._marker = 
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }    
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
    
    if(position=='after')
      if(sortable.overlap == 'horizontal') 
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
    
    Sortable._marker.show();
  },
  
  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];
  
    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;
      
      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      }
      
      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child)
      
      parent.children.push (child);
    }

    return parent; 
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || {});
    
    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    }
    
    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || {});
    
    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || {});
    
    var nodeMap = {};
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });
   
    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },
  
  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || {});
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
    
    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" + 
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
}

// Returns true if child is contained within element
Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
}

Element.findChildren = function(element, only, recursive, tagName) {    
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
}

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

