[fusion-commits] r2124 - sandbox/jxlib-3.0/lib

svn_fusion at osgeo.org svn_fusion at osgeo.org
Wed Mar 31 15:17:23 EDT 2010


Author: madair
Date: 2010-03-31 15:17:23 -0400 (Wed, 31 Mar 2010)
New Revision: 2124

Modified:
   sandbox/jxlib-3.0/lib/jxlib.uncompressed.js
Log:
update to most recent JxLib

Modified: sandbox/jxlib-3.0/lib/jxlib.uncompressed.js
===================================================================
--- sandbox/jxlib-3.0/lib/jxlib.uncompressed.js	2010-03-31 19:07:52 UTC (rev 2123)
+++ sandbox/jxlib-3.0/lib/jxlib.uncompressed.js	2010-03-31 19:17:23 UTC (rev 2124)
@@ -6,7 +6,7 @@
  * reset.css - Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  * Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt
  ******************************************************************************
- * Jx UI Library, 2.0.1
+ * Jx UI Library, 3.0alpha
  * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -27,13822 +27,14557 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  *****************************************************************************/
-/*
----
-
-script: Core.js
-
-description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
-
-license: MIT-style license.
-
-copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
-
-authors: The MooTools production team (http://mootools.net/developers/)
-
-inspiration:
-- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
-- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
-
-provides: [Mootools, Native, Hash.base, Array.each, $util]
-
-...
-*/
-
-var MooTools = {
-	'version': '1.2.4',
-	'build': '0d9113241a90b9cd5643b926795852a2026710d4'
-};
-
-var Native = function(options){
-	options = options || {};
-	var name = options.name;
-	var legacy = options.legacy;
-	var protect = options.protect;
-	var methods = options.implement;
-	var generics = options.generics;
-	var initialize = options.initialize;
-	var afterImplement = options.afterImplement || function(){};
-	var object = initialize || legacy;
-	generics = generics !== false;
-
-	object.constructor = Native;
-	object.$family = {name: 'native'};
-	if (legacy && initialize) object.prototype = legacy.prototype;
-	object.prototype.constructor = object;
-
-	if (name){
-		var family = name.toLowerCase();
-		object.prototype.$family = {name: family};
-		Native.typize(object, family);
-	}
-
-	var add = function(obj, name, method, force){
-		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
-		if (generics) Native.genericize(obj, name, protect);
-		afterImplement.call(obj, name, method);
-		return obj;
-	};
-
-	object.alias = function(a1, a2, a3){
-		if (typeof a1 == 'string'){
-			var pa1 = this.prototype[a1];
-			if ((a1 = pa1)) return add(this, a2, a1, a3);
-		}
-		for (var a in a1) this.alias(a, a1[a], a2);
-		return this;
-	};
-
-	object.implement = function(a1, a2, a3){
-		if (typeof a1 == 'string') return add(this, a1, a2, a3);
-		for (var p in a1) add(this, p, a1[p], a2);
-		return this;
-	};
-
-	if (methods) object.implement(methods);
-
-	return object;
-};
-
-Native.genericize = function(object, property, check){
-	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
-		var args = Array.prototype.slice.call(arguments);
-		return object.prototype[property].apply(args.shift(), args);
-	};
-};
-
-Native.implement = function(objects, properties){
-	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
-};
-
-Native.typize = function(object, family){
-	if (!object.type) object.type = function(item){
-		return ($type(item) === family);
-	};
-};
-
-(function(){
-	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
-	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
-
-	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
-	for (var t in types) Native.typize(types[t], t);
-
-	var generics = {
-		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
-		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
-	};
-	for (var g in generics){
-		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
-	}
-})();
-
-var Hash = new Native({
-
-	name: 'Hash',
-
-	initialize: function(object){
-		if ($type(object) == 'hash') object = $unlink(object.getClean());
-		for (var key in object) this[key] = object[key];
-		return this;
-	}
-
-});
-
-Hash.implement({
-
-	forEach: function(fn, bind){
-		for (var key in this){
-			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
-		}
-	},
-
-	getClean: function(){
-		var clean = {};
-		for (var key in this){
-			if (this.hasOwnProperty(key)) clean[key] = this[key];
-		}
-		return clean;
-	},
-
-	getLength: function(){
-		var length = 0;
-		for (var key in this){
-			if (this.hasOwnProperty(key)) length++;
-		}
-		return length;
-	}
-
-});
-
-Hash.alias('forEach', 'each');
-
-Array.implement({
-
-	forEach: function(fn, bind){
-		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
-	}
-
-});
-
-Array.alias('forEach', 'each');
-
-function $A(iterable){
-	if (iterable.item){
-		var l = iterable.length, array = new Array(l);
-		while (l--) array[l] = iterable[l];
-		return array;
-	}
-	return Array.prototype.slice.call(iterable);
-};
-
-function $arguments(i){
-	return function(){
-		return arguments[i];
-	};
-};
-
-function $chk(obj){
-	return !!(obj || obj === 0);
-};
-
-function $clear(timer){
-	clearTimeout(timer);
-	clearInterval(timer);
-	return null;
-};
-
-function $defined(obj){
-	return (obj != undefined);
-};
-
-function $each(iterable, fn, bind){
-	var type = $type(iterable);
-	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
-};
-
-function $empty(){};
-
-function $extend(original, extended){
-	for (var key in (extended || {})) original[key] = extended[key];
-	return original;
-};
-
-function $H(object){
-	return new Hash(object);
-};
-
-function $lambda(value){
-	return ($type(value) == 'function') ? value : function(){
-		return value;
-	};
-};
-
-function $merge(){
-	var args = Array.slice(arguments);
-	args.unshift({});
-	return $mixin.apply(null, args);
-};
-
-function $mixin(mix){
-	for (var i = 1, l = arguments.length; i < l; i++){
-		var object = arguments[i];
-		if ($type(object) != 'object') continue;
-		for (var key in object){
-			var op = object[key], mp = mix[key];
-			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
-		}
-	}
-	return mix;
-};
-
-function $pick(){
-	for (var i = 0, l = arguments.length; i < l; i++){
-		if (arguments[i] != undefined) return arguments[i];
-	}
-	return null;
-};
-
-function $random(min, max){
-	return Math.floor(Math.random() * (max - min + 1) + min);
-};
-
-function $splat(obj){
-	var type = $type(obj);
-	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
-};
-
-var $time = Date.now || function(){
-	return +new Date;
-};
-
-function $try(){
-	for (var i = 0, l = arguments.length; i < l; i++){
-		try {
-			return arguments[i]();
-		} catch(e){}
-	}
-	return null;
-};
-
-function $type(obj){
-	if (obj == undefined) return false;
-	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
-	if (obj.nodeName){
-		switch (obj.nodeType){
-			case 1: return 'element';
-			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
-		}
-	} else if (typeof obj.length == 'number'){
-		if (obj.callee) return 'arguments';
-		else if (obj.item) return 'collection';
-	}
-	return typeof obj;
-};
-
-function $unlink(object){
-	var unlinked;
-	switch ($type(object)){
-		case 'object':
-			unlinked = {};
-			for (var p in object) unlinked[p] = $unlink(object[p]);
-		break;
-		case 'hash':
-			unlinked = new Hash(object);
-		break;
-		case 'array':
-			unlinked = [];
-			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
-		break;
-		default: return object;
-	}
-	return unlinked;
-};
-/*
----
-
-script: Browser.js
-
-description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
-
-license: MIT-style license.
-
-requires: 
-- /Native
-- /$util
-
-provides: [Browser, Window, Document, $exec]
-
-...
-*/
-
-var Browser = $merge({
-
-	Engine: {name: 'unknown', version: 0},
-
-	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
-
-	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
-
-	Plugins: {},
-
-	Engines: {
-
-		presto: function(){
-			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
-		},
-
-		trident: function(){
-			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
-		},
-
-		webkit: function(){
-			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
-		},
-
-		gecko: function(){
-			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
-		}
-
-	}
-
-}, Browser || {});
-
-Browser.Platform[Browser.Platform.name] = true;
-
-Browser.detect = function(){
-
-	for (var engine in this.Engines){
-		var version = this.Engines[engine]();
-		if (version){
-			this.Engine = {name: engine, version: version};
-			this.Engine[engine] = this.Engine[engine + version] = true;
-			break;
-		}
-	}
-
-	return {name: engine, version: version};
-
-};
-
-Browser.detect();
-
-Browser.Request = function(){
-	return $try(function(){
-		return new XMLHttpRequest();
-	}, function(){
-		return new ActiveXObject('MSXML2.XMLHTTP');
-	}, function(){
-		return new ActiveXObject('Microsoft.XMLHTTP');
-	});
-};
-
-Browser.Features.xhr = !!(Browser.Request());
-
-Browser.Plugins.Flash = (function(){
-	var version = ($try(function(){
-		return navigator.plugins['Shockwave Flash'].description;
-	}, function(){
-		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
-	}) || '0 r0').match(/\d+/g);
-	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
-})();
-
-function $exec(text){
-	if (!text) return text;
-	if (window.execScript){
-		window.execScript(text);
-	} else {
-		var script = document.createElement('script');
-		script.setAttribute('type', 'text/javascript');
-		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
-		document.head.appendChild(script);
-		document.head.removeChild(script);
-	}
-	return text;
-};
-
-Native.UID = 1;
-
-var $uid = (Browser.Engine.trident) ? function(item){
-	return (item.uid || (item.uid = [Native.UID++]))[0];
-} : function(item){
-	return item.uid || (item.uid = Native.UID++);
-};
-
-var Window = new Native({
-
-	name: 'Window',
-
-	legacy: (Browser.Engine.trident) ? null: window.Window,
-
-	initialize: function(win){
-		$uid(win);
-		if (!win.Element){
-			win.Element = $empty;
-			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
-			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
-		}
-		win.document.window = win;
-		return $extend(win, Window.Prototype);
-	},
-
-	afterImplement: function(property, value){
-		window[property] = Window.Prototype[property] = value;
-	}
-
-});
-
-Window.Prototype = {$family: {name: 'window'}};
-
-new Window(window);
-
-var Document = new Native({
-
-	name: 'Document',
-
-	legacy: (Browser.Engine.trident) ? null: window.Document,
-
-	initialize: function(doc){
-		$uid(doc);
-		doc.head = doc.getElementsByTagName('head')[0];
-		doc.html = doc.getElementsByTagName('html')[0];
-		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
-			doc.execCommand("BackgroundImageCache", false, true);
-		});
-		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
-			doc.window.detachEvent('onunload', arguments.callee);
-			doc.head = doc.html = doc.window = null;
-		});
-		return $extend(doc, Document.Prototype);
-	},
-
-	afterImplement: function(property, value){
-		document[property] = Document.Prototype[property] = value;
-	}
-
-});
-
-Document.Prototype = {$family: {name: 'document'}};
-
-new Document(document);
-/*
----
-
-script: Array.js
-
-description: Contains Array Prototypes like each, contains, and erase.
-
-license: MIT-style license.
-
-requires:
-- /$util
-- /Array.each
-
-provides: [Array]
-
-...
-*/
-
-Array.implement({
-
-	every: function(fn, bind){
-		for (var i = 0, l = this.length; i < l; i++){
-			if (!fn.call(bind, this[i], i, this)) return false;
-		}
-		return true;
-	},
-
-	filter: function(fn, bind){
-		var results = [];
-		for (var i = 0, l = this.length; i < l; i++){
-			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
-		}
-		return results;
-	},
-
-	clean: function(){
-		return this.filter($defined);
-	},
-
-	indexOf: function(item, from){
-		var len = this.length;
-		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
-			if (this[i] === item) return i;
-		}
-		return -1;
-	},
-
-	map: function(fn, bind){
-		var results = [];
-		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
-		return results;
-	},
-
-	some: function(fn, bind){
-		for (var i = 0, l = this.length; i < l; i++){
-			if (fn.call(bind, this[i], i, this)) return true;
-		}
-		return false;
-	},
-
-	associate: function(keys){
-		var obj = {}, length = Math.min(this.length, keys.length);
-		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
-		return obj;
-	},
-
-	link: function(object){
-		var result = {};
-		for (var i = 0, l = this.length; i < l; i++){
-			for (var key in object){
-				if (object[key](this[i])){
-					result[key] = this[i];
-					delete object[key];
-					break;
-				}
-			}
-		}
-		return result;
-	},
-
-	contains: function(item, from){
-		return this.indexOf(item, from) != -1;
-	},
-
-	extend: function(array){
-		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
-		return this;
-	},
-	
-	getLast: function(){
-		return (this.length) ? this[this.length - 1] : null;
-	},
-
-	getRandom: function(){
-		return (this.length) ? this[$random(0, this.length - 1)] : null;
-	},
-
-	include: function(item){
-		if (!this.contains(item)) this.push(item);
-		return this;
-	},
-
-	combine: function(array){
-		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
-		return this;
-	},
-
-	erase: function(item){
-		for (var i = this.length; i--; i){
-			if (this[i] === item) this.splice(i, 1);
-		}
-		return this;
-	},
-
-	empty: function(){
-		this.length = 0;
-		return this;
-	},
-
-	flatten: function(){
-		var array = [];
-		for (var i = 0, l = this.length; i < l; i++){
-			var type = $type(this[i]);
-			if (!type) continue;
-			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
-		}
-		return array;
-	},
-
-	hexToRgb: function(array){
-		if (this.length != 3) return null;
-		var rgb = this.map(function(value){
-			if (value.length == 1) value += value;
-			return value.toInt(16);
-		});
-		return (array) ? rgb : 'rgb(' + rgb + ')';
-	},
-
-	rgbToHex: function(array){
-		if (this.length < 3) return null;
-		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
-		var hex = [];
-		for (var i = 0; i < 3; i++){
-			var bit = (this[i] - 0).toString(16);
-			hex.push((bit.length == 1) ? '0' + bit : bit);
-		}
-		return (array) ? hex : '#' + hex.join('');
-	}
-
-});
-/*
----
-
-script: Function.js
-
-description: Contains Function Prototypes like create, bind, pass, and delay.
-
-license: MIT-style license.
-
-requires:
-- /Native
-- /$util
-
-provides: [Function]
-
-...
-*/
-
-Function.implement({
-
-	extend: function(properties){
-		for (var property in properties) this[property] = properties[property];
-		return this;
-	},
-
-	create: function(options){
-		var self = this;
-		options = options || {};
-		return function(event){
-			var args = options.arguments;
-			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
-			if (options.event) args = [event || window.event].extend(args);
-			var returns = function(){
-				return self.apply(options.bind || null, args);
-			};
-			if (options.delay) return setTimeout(returns, options.delay);
-			if (options.periodical) return setInterval(returns, options.periodical);
-			if (options.attempt) return $try(returns);
-			return returns();
-		};
-	},
-
-	run: function(args, bind){
-		return this.apply(bind, $splat(args));
-	},
-
-	pass: function(args, bind){
-		return this.create({bind: bind, arguments: args});
-	},
-
-	bind: function(bind, args){
-		return this.create({bind: bind, arguments: args});
-	},
-
-	bindWithEvent: function(bind, args){
-		return this.create({bind: bind, arguments: args, event: true});
-	},
-
-	attempt: function(args, bind){
-		return this.create({bind: bind, arguments: args, attempt: true})();
-	},
-
-	delay: function(delay, bind, args){
-		return this.create({bind: bind, arguments: args, delay: delay})();
-	},
-
-	periodical: function(periodical, bind, args){
-		return this.create({bind: bind, arguments: args, periodical: periodical})();
-	}
-
-});
-/*
----
-
-script: Number.js
-
-description: Contains Number Prototypes like limit, round, times, and ceil.
-
-license: MIT-style license.
-
-requires:
-- /Native
-- /$util
-
-provides: [Number]
-
-...
-*/
-
-Number.implement({
-
-	limit: function(min, max){
-		return Math.min(max, Math.max(min, this));
-	},
-
-	round: function(precision){
-		precision = Math.pow(10, precision || 0);
-		return Math.round(this * precision) / precision;
-	},
-
-	times: function(fn, bind){
-		for (var i = 0; i < this; i++) fn.call(bind, i, this);
-	},
-
-	toFloat: function(){
-		return parseFloat(this);
-	},
-
-	toInt: function(base){
-		return parseInt(this, base || 10);
-	}
-
-});
-
-Number.alias('times', 'each');
-
-(function(math){
-	var methods = {};
-	math.each(function(name){
-		if (!Number[name]) methods[name] = function(){
-			return Math[name].apply(null, [this].concat($A(arguments)));
-		};
-	});
-	Number.implement(methods);
-})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
-/*
----
-
-script: String.js
-
-description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
-
-license: MIT-style license.
-
-requires:
-- /Native
-
-provides: [String]
-
-...
-*/
-
-String.implement({
-
-	test: function(regex, params){
-		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
-	},
-
-	contains: function(string, separator){
-		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
-	},
-
-	trim: function(){
-		return this.replace(/^\s+|\s+$/g, '');
-	},
-
-	clean: function(){
-		return this.replace(/\s+/g, ' ').trim();
-	},
-
-	camelCase: function(){
-		return this.replace(/-\D/g, function(match){
-			return match.charAt(1).toUpperCase();
-		});
-	},
-
-	hyphenate: function(){
-		return this.replace(/[A-Z]/g, function(match){
-			return ('-' + match.charAt(0).toLowerCase());
-		});
-	},
-
-	capitalize: function(){
-		return this.replace(/\b[a-z]/g, function(match){
-			return match.toUpperCase();
-		});
-	},
-
-	escapeRegExp: function(){
-		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
-	},
-
-	toInt: function(base){
-		return parseInt(this, base || 10);
-	},
-
-	toFloat: function(){
-		return parseFloat(this);
-	},
-
-	hexToRgb: function(array){
-		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
-		return (hex) ? hex.slice(1).hexToRgb(array) : null;
-	},
-
-	rgbToHex: function(array){
-		var rgb = this.match(/\d{1,3}/g);
-		return (rgb) ? rgb.rgbToHex(array) : null;
-	},
-
-	stripScripts: function(option){
-		var scripts = '';
-		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
-			scripts += arguments[1] + '\n';
-			return '';
-		});
-		if (option === true) $exec(scripts);
-		else if ($type(option) == 'function') option(scripts, text);
-		return text;
-	},
-
-	substitute: function(object, regexp){
-		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
-			if (match.charAt(0) == '\\') return match.slice(1);
-			return (object[name] != undefined) ? object[name] : '';
-		});
-	}
-
-});
-/*
----
-
-script: Hash.js
-
-description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
-
-license: MIT-style license.
-
-requires:
-- /Hash.base
-
-provides: [Hash]
-
-...
-*/
-
-Hash.implement({
-
-	has: Object.prototype.hasOwnProperty,
-
-	keyOf: function(value){
-		for (var key in this){
-			if (this.hasOwnProperty(key) && this[key] === value) return key;
-		}
-		return null;
-	},
-
-	hasValue: function(value){
-		return (Hash.keyOf(this, value) !== null);
-	},
-
-	extend: function(properties){
-		Hash.each(properties || {}, function(value, key){
-			Hash.set(this, key, value);
-		}, this);
-		return this;
-	},
-
-	combine: function(properties){
-		Hash.each(properties || {}, function(value, key){
-			Hash.include(this, key, value);
-		}, this);
-		return this;
-	},
-
-	erase: function(key){
-		if (this.hasOwnProperty(key)) delete this[key];
-		return this;
-	},
-
-	get: function(key){
-		return (this.hasOwnProperty(key)) ? this[key] : null;
-	},
-
-	set: function(key, value){
-		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
-		return this;
-	},
-
-	empty: function(){
-		Hash.each(this, function(value, key){
-			delete this[key];
-		}, this);
-		return this;
-	},
-
-	include: function(key, value){
-		if (this[key] == undefined) this[key] = value;
-		return this;
-	},
-
-	map: function(fn, bind){
-		var results = new Hash;
-		Hash.each(this, function(value, key){
-			results.set(key, fn.call(bind, value, key, this));
-		}, this);
-		return results;
-	},
-
-	filter: function(fn, bind){
-		var results = new Hash;
-		Hash.each(this, function(value, key){
-			if (fn.call(bind, value, key, this)) results.set(key, value);
-		}, this);
-		return results;
-	},
-
-	every: function(fn, bind){
-		for (var key in this){
-			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
-		}
-		return true;
-	},
-
-	some: function(fn, bind){
-		for (var key in this){
-			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
-		}
-		return false;
-	},
-
-	getKeys: function(){
-		var keys = [];
-		Hash.each(this, function(value, key){
-			keys.push(key);
-		});
-		return keys;
-	},
-
-	getValues: function(){
-		var values = [];
-		Hash.each(this, function(value){
-			values.push(value);
-		});
-		return values;
-	},
-
-	toQueryString: function(base){
-		var queryString = [];
-		Hash.each(this, function(value, key){
-			if (base) key = base + '[' + key + ']';
-			var result;
-			switch ($type(value)){
-				case 'object': result = Hash.toQueryString(value, key); break;
-				case 'array':
-					var qs = {};
-					value.each(function(val, i){
-						qs[i] = val;
-					});
-					result = Hash.toQueryString(qs, key);
-				break;
-				default: result = key + '=' + encodeURIComponent(value);
-			}
-			if (value != undefined) queryString.push(result);
-		});
-
-		return queryString.join('&');
-	}
-
-});
-
-Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
-/*
----
-
-script: Event.js
-
-description: Contains the Event Class, to make the event object cross-browser.
-
-license: MIT-style license.
-
-requires:
-- /Window
-- /Document
-- /Hash
-- /Array
-- /Function
-- /String
-
-provides: [Event]
-
-...
-*/
-
-var Event = new Native({
-
-	name: 'Event',
-
-	initialize: function(event, win){
-		win = win || window;
-		var doc = win.document;
-		event = event || win.event;
-		if (event.$extended) return event;
-		this.$extended = true;
-		var type = event.type;
-		var target = event.target || event.srcElement;
-		while (target && target.nodeType == 3) target = target.parentNode;
-
-		if (type.test(/key/)){
-			var code = event.which || event.keyCode;
-			var key = Event.Keys.keyOf(code);
-			if (type == 'keydown'){
-				var fKey = code - 111;
-				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
-			}
-			key = key || String.fromCharCode(code).toLowerCase();
-		} else if (type.match(/(click|mouse|menu)/i)){
-			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
-			var page = {
-				x: event.pageX || event.clientX + doc.scrollLeft,
-				y: event.pageY || event.clientY + doc.scrollTop
-			};
-			var client = {
-				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
-				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
-			};
-			if (type.match(/DOMMouseScroll|mousewheel/)){
-				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
-			}
-			var rightClick = (event.which == 3) || (event.button == 2);
-			var related = null;
-			if (type.match(/over|out/)){
-				switch (type){
-					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
-					case 'mouseout': related = event.relatedTarget || event.toElement;
-				}
-				if (!(function(){
-					while (related && related.nodeType == 3) related = related.parentNode;
-					return true;
-				}).create({attempt: Browser.Engine.gecko})()) related = false;
-			}
-		}
-
-		return $extend(this, {
-			event: event,
-			type: type,
-
-			page: page,
-			client: client,
-			rightClick: rightClick,
-
-			wheel: wheel,
-
-			relatedTarget: related,
-			target: target,
-
-			code: code,
-			key: key,
-
-			shift: event.shiftKey,
-			control: event.ctrlKey,
-			alt: event.altKey,
-			meta: event.metaKey
-		});
-	}
-
-});
-
-Event.Keys = new Hash({
-	'enter': 13,
-	'up': 38,
-	'down': 40,
-	'left': 37,
-	'right': 39,
-	'esc': 27,
-	'space': 32,
-	'backspace': 8,
-	'tab': 9,
-	'delete': 46
-});
-
-Event.implement({
-
-	stop: function(){
-		return this.stopPropagation().preventDefault();
-	},
-
-	stopPropagation: function(){
-		if (this.event.stopPropagation) this.event.stopPropagation();
-		else this.event.cancelBubble = true;
-		return this;
-	},
-
-	preventDefault: function(){
-		if (this.event.preventDefault) this.event.preventDefault();
-		else this.event.returnValue = false;
-		return this;
-	}
-
-});
-/*
----
-
-script: Class.js
-
-description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
-
-license: MIT-style license.
-
-requires:
-- /$util
-- /Native
-- /Array
-- /String
-- /Function
-- /Number
-- /Hash
-
-provides: [Class]
-
-...
-*/
-
-function Class(params){
-	
-	if (params instanceof Function) params = {initialize: params};
-	
-	var newClass = function(){
-		Object.reset(this);
-		if (newClass._prototyping) return this;
-		this._current = $empty;
-		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
-		delete this._current; delete this.caller;
-		return value;
-	}.extend(this);
-	
-	newClass.implement(params);
-	
-	newClass.constructor = Class;
-	newClass.prototype.constructor = newClass;
-
-	return newClass;
-
-};
-
-Function.prototype.protect = function(){
-	this._protected = true;
-	return this;
-};
-
-Object.reset = function(object, key){
-		
-	if (key == null){
-		for (var p in object) Object.reset(object, p);
-		return object;
-	}
-	
-	delete object[key];
-	
-	switch ($type(object[key])){
-		case 'object':
-			var F = function(){};
-			F.prototype = object[key];
-			var i = new F;
-			object[key] = Object.reset(i);
-		break;
-		case 'array': object[key] = $unlink(object[key]); break;
-	}
-	
-	return object;
-	
-};
-
-new Native({name: 'Class', initialize: Class}).extend({
-
-	instantiate: function(F){
-		F._prototyping = true;
-		var proto = new F;
-		delete F._prototyping;
-		return proto;
-	},
-	
-	wrap: function(self, key, method){
-		if (method._origin) method = method._origin;
-		
-		return function(){
-			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
-			var caller = this.caller, current = this._current;
-			this.caller = current; this._current = arguments.callee;
-			var result = method.apply(this, arguments);
-			this._current = current; this.caller = caller;
-			return result;
-		}.extend({_owner: self, _origin: method, _name: key});
-
-	}
-	
-});
-
-Class.implement({
-	
-	implement: function(key, value){
-		
-		if ($type(key) == 'object'){
-			for (var p in key) this.implement(p, key[p]);
-			return this;
-		}
-		
-		var mutator = Class.Mutators[key];
-		
-		if (mutator){
-			value = mutator.call(this, value);
-			if (value == null) return this;
-		}
-		
-		var proto = this.prototype;
-
-		switch ($type(value)){
-			
-			case 'function':
-				if (value._hidden) return this;
-				proto[key] = Class.wrap(this, key, value);
-			break;
-			
-			case 'object':
-				var previous = proto[key];
-				if ($type(previous) == 'object') $mixin(previous, value);
-				else proto[key] = $unlink(value);
-			break;
-			
-			case 'array':
-				proto[key] = $unlink(value);
-			break;
-			
-			default: proto[key] = value;
-
-		}
-		
-		return this;
-
-	}
-	
-});
-
-Class.Mutators = {
-	
-	Extends: function(parent){
-
-		this.parent = parent;
-		this.prototype = Class.instantiate(parent);
-
-		this.implement('parent', function(){
-			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
-			if (!previous) throw new Error('The method "' + name + '" has no parent.');
-			return previous.apply(this, arguments);
-		}.protect());
-
-	},
-
-	Implements: function(items){
-		$splat(items).each(function(item){
-			if (item instanceof Function) item = Class.instantiate(item);
-			this.implement(item);
-		}, this);
-
-	}
-	
-};
-/*
----
-
-script: Class.Extras.js
-
-description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
-
-license: MIT-style license.
-
-requires:
-- /Class
-
-provides: [Chain, Events, Options]
-
-...
-*/
-
-var Chain = new Class({
-
-	$chain: [],
-
-	chain: function(){
-		this.$chain.extend(Array.flatten(arguments));
-		return this;
-	},
-
-	callChain: function(){
-		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
-	},
-
-	clearChain: function(){
-		this.$chain.empty();
-		return this;
-	}
-
-});
-
-var Events = new Class({
-
-	$events: {},
-
-	addEvent: function(type, fn, internal){
-		type = Events.removeOn(type);
-		if (fn != $empty){
-			this.$events[type] = this.$events[type] || [];
-			this.$events[type].include(fn);
-			if (internal) fn.internal = true;
-		}
-		return this;
-	},
-
-	addEvents: function(events){
-		for (var type in events) this.addEvent(type, events[type]);
-		return this;
-	},
-
-	fireEvent: function(type, args, delay){
-		type = Events.removeOn(type);
-		if (!this.$events || !this.$events[type]) return this;
-		this.$events[type].each(function(fn){
-			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
-		}, this);
-		return this;
-	},
-
-	removeEvent: function(type, fn){
-		type = Events.removeOn(type);
-		if (!this.$events[type]) return this;
-		if (!fn.internal) this.$events[type].erase(fn);
-		return this;
-	},
-
-	removeEvents: function(events){
-		var type;
-		if ($type(events) == 'object'){
-			for (type in events) this.removeEvent(type, events[type]);
-			return this;
-		}
-		if (events) events = Events.removeOn(events);
-		for (type in this.$events){
-			if (events && events != type) continue;
-			var fns = this.$events[type];
-			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
-		}
-		return this;
-	}
-
-});
-
-Events.removeOn = function(string){
-	return string.replace(/^on([A-Z])/, function(full, first){
-		return first.toLowerCase();
-	});
-};
-
-var Options = new Class({
-
-	setOptions: function(){
-		this.options = $merge.run([this.options].extend(arguments));
-		if (!this.addEvent) return this;
-		for (var option in this.options){
-			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
-			this.addEvent(option, this.options[option]);
-			delete this.options[option];
-		}
-		return this;
-	}
-
-});
-/*
----
-
-script: Element.js
-
-description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
-
-license: MIT-style license.
-
-requires:
-- /Window
-- /Document
-- /Array
-- /String
-- /Function
-- /Number
-- /Hash
-
-provides: [Element, Elements, $, $$, Iframe]
-
-...
-*/
-
-var Element = new Native({
-
-	name: 'Element',
-
-	legacy: window.Element,
-
-	initialize: function(tag, props){
-		var konstructor = Element.Constructors.get(tag);
-		if (konstructor) return konstructor(props);
-		if (typeof tag == 'string') return document.newElement(tag, props);
-		return document.id(tag).set(props);
-	},
-
-	afterImplement: function(key, value){
-		Element.Prototype[key] = value;
-		if (Array[key]) return;
-		Elements.implement(key, function(){
-			var items = [], elements = true;
-			for (var i = 0, j = this.length; i < j; i++){
-				var returns = this[i][key].apply(this[i], arguments);
-				items.push(returns);
-				if (elements) elements = ($type(returns) == 'element');
-			}
-			return (elements) ? new Elements(items) : items;
-		});
-	}
-
-});
-
-Element.Prototype = {$family: {name: 'element'}};
-
-Element.Constructors = new Hash;
-
-var IFrame = new Native({
-
-	name: 'IFrame',
-
-	generics: false,
-
-	initialize: function(){
-		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
-		var props = params.properties || {};
-		var iframe = document.id(params.iframe);
-		var onload = props.onload || $empty;
-		delete props.onload;
-		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
-		iframe = new Element(iframe || 'iframe', props);
-		var onFrameLoad = function(){
-			var host = $try(function(){
-				return iframe.contentWindow.location.host;
-			});
-			if (!host || host == window.location.host){
-				var win = new Window(iframe.contentWindow);
-				new Document(iframe.contentWindow.document);
-				$extend(win.Element.prototype, Element.Prototype);
-			}
-			onload.call(iframe.contentWindow, iframe.contentWindow.document);
-		};
-		var contentWindow = $try(function(){
-			return iframe.contentWindow;
-		});
-		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
-		return iframe;
-	}
-
-});
-
-var Elements = new Native({
-
-	initialize: function(elements, options){
-		options = $extend({ddup: true, cash: true}, options);
-		elements = elements || [];
-		if (options.ddup || options.cash){
-			var uniques = {}, returned = [];
-			for (var i = 0, l = elements.length; i < l; i++){
-				var el = document.id(elements[i], !options.cash);
-				if (options.ddup){
-					if (uniques[el.uid]) continue;
-					uniques[el.uid] = true;
-				}
-				if (el) returned.push(el);
-			}
-			elements = returned;
-		}
-		return (options.cash) ? $extend(elements, this) : elements;
-	}
-
-});
-
-Elements.implement({
-
-	filter: function(filter, bind){
-		if (!filter) return this;
-		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
-			return item.match(filter);
-		} : filter, bind));
-	}
-
-});
-
-Document.implement({
-
-	newElement: function(tag, props){
-		if (Browser.Engine.trident && props){
-			['name', 'type', 'checked'].each(function(attribute){
-				if (!props[attribute]) return;
-				tag += ' ' + attribute + '="' + props[attribute] + '"';
-				if (attribute != 'checked') delete props[attribute];
-			});
-			tag = '<' + tag + '>';
-		}
-		return document.id(this.createElement(tag)).set(props);
-	},
-
-	newTextNode: function(text){
-		return this.createTextNode(text);
-	},
-
-	getDocument: function(){
-		return this;
-	},
-
-	getWindow: function(){
-		return this.window;
-	},
-	
-	id: (function(){
-		
-		var types = {
-
-			string: function(id, nocash, doc){
-				id = doc.getElementById(id);
-				return (id) ? types.element(id, nocash) : null;
-			},
-			
-			element: function(el, nocash){
-				$uid(el);
-				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
-					var proto = Element.Prototype;
-					for (var p in proto) el[p] = proto[p];
-				};
-				return el;
-			},
-			
-			object: function(obj, nocash, doc){
-				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
-				return null;
-			}
-			
-		};
-
-		types.textnode = types.whitespace = types.window = types.document = $arguments(0);
-		
-		return function(el, nocash, doc){
-			if (el && el.$family && el.uid) return el;
-			var type = $type(el);
-			return (types[type]) ? types[type](el, nocash, doc || document) : null;
-		};
-
-	})()
-
-});
-
-if (window.$ == null) Window.implement({
-	$: function(el, nc){
-		return document.id(el, nc, this.document);
-	}
-});
-
-Window.implement({
-
-	$$: function(selector){
-		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
-		var elements = [];
-		var args = Array.flatten(arguments);
-		for (var i = 0, l = args.length; i < l; i++){
-			var item = args[i];
-			switch ($type(item)){
-				case 'element': elements.push(item); break;
-				case 'string': elements.extend(this.document.getElements(item, true));
-			}
-		}
-		return new Elements(elements);
-	},
-
-	getDocument: function(){
-		return this.document;
-	},
-
-	getWindow: function(){
-		return this;
-	}
-
-});
-
-Native.implement([Element, Document], {
-
-	getElement: function(selector, nocash){
-		return document.id(this.getElements(selector, true)[0] || null, nocash);
-	},
-
-	getElements: function(tags, nocash){
-		tags = tags.split(',');
-		var elements = [];
-		var ddup = (tags.length > 1);
-		tags.each(function(tag){
-			var partial = this.getElementsByTagName(tag.trim());
-			(ddup) ? elements.extend(partial) : elements = partial;
-		}, this);
-		return new Elements(elements, {ddup: ddup, cash: !nocash});
-	}
-
-});
-
-(function(){
-
-var collected = {}, storage = {};
-var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
-
-var get = function(uid){
-	return (storage[uid] || (storage[uid] = {}));
-};
-
-var clean = function(item, retain){
-	if (!item) return;
-	var uid = item.uid;
-	if (Browser.Engine.trident){
-		if (item.clearAttributes){
-			var clone = retain && item.cloneNode(false);
-			item.clearAttributes();
-			if (clone) item.mergeAttributes(clone);
-		} else if (item.removeEvents){
-			item.removeEvents();
-		}
-		if ((/object/i).test(item.tagName)){
-			for (var p in item){
-				if (typeof item[p] == 'function') item[p] = $empty;
-			}
-			Element.dispose(item);
-		}
-	}	
-	if (!uid) return;
-	collected[uid] = storage[uid] = null;
-};
-
-var purge = function(){
-	Hash.each(collected, clean);
-	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
-	if (window.CollectGarbage) CollectGarbage();
-	collected = storage = null;
-};
-
-var walk = function(element, walk, start, match, all, nocash){
-	var el = element[start || walk];
-	var elements = [];
-	while (el){
-		if (el.nodeType == 1 && (!match || Element.match(el, match))){
-			if (!all) return document.id(el, nocash);
-			elements.push(el);
-		}
-		el = el[walk];
-	}
-	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
-};
-
-var attributes = {
-	'html': 'innerHTML',
-	'class': 'className',
-	'for': 'htmlFor',
-	'defaultValue': 'defaultValue',
-	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
-};
-var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
-var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
-
-bools = bools.associate(bools);
-
-Hash.extend(attributes, bools);
-Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
-
-var inserters = {
-
-	before: function(context, element){
-		if (element.parentNode) element.parentNode.insertBefore(context, element);
-	},
-
-	after: function(context, element){
-		if (!element.parentNode) return;
-		var next = element.nextSibling;
-		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
-	},
-
-	bottom: function(context, element){
-		element.appendChild(context);
-	},
-
-	top: function(context, element){
-		var first = element.firstChild;
-		(first) ? element.insertBefore(context, first) : element.appendChild(context);
-	}
-
-};
-
-inserters.inside = inserters.bottom;
-
-Hash.each(inserters, function(inserter, where){
-
-	where = where.capitalize();
-
-	Element.implement('inject' + where, function(el){
-		inserter(this, document.id(el, true));
-		return this;
-	});
-
-	Element.implement('grab' + where, function(el){
-		inserter(document.id(el, true), this);
-		return this;
-	});
-
-});
-
-Element.implement({
-
-	set: function(prop, value){
-		switch ($type(prop)){
-			case 'object':
-				for (var p in prop) this.set(p, prop[p]);
-				break;
-			case 'string':
-				var property = Element.Properties.get(prop);
-				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
-		}
-		return this;
-	},
-
-	get: function(prop){
-		var property = Element.Properties.get(prop);
-		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
-	},
-
-	erase: function(prop){
-		var property = Element.Properties.get(prop);
-		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
-		return this;
-	},
-
-	setProperty: function(attribute, value){
-		var key = attributes[attribute];
-		if (value == undefined) return this.removeProperty(attribute);
-		if (key && bools[attribute]) value = !!value;
-		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
-		return this;
-	},
-
-	setProperties: function(attributes){
-		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
-		return this;
-	},
-
-	getProperty: function(attribute){
-		var key = attributes[attribute];
-		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
-		return (bools[attribute]) ? !!value : (key) ? value : value || null;
-	},
-
-	getProperties: function(){
-		var args = $A(arguments);
-		return args.map(this.getProperty, this).associate(args);
-	},
-
-	removeProperty: function(attribute){
-		var key = attributes[attribute];
-		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
-		return this;
-	},
-
-	removeProperties: function(){
-		Array.each(arguments, this.removeProperty, this);
-		return this;
-	},
-
-	hasClass: function(className){
-		return this.className.contains(className, ' ');
-	},
-
-	addClass: function(className){
-		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
-		return this;
-	},
-
-	removeClass: function(className){
-		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
-		return this;
-	},
-
-	toggleClass: function(className){
-		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
-	},
-
-	adopt: function(){
-		Array.flatten(arguments).each(function(element){
-			element = document.id(element, true);
-			if (element) this.appendChild(element);
-		}, this);
-		return this;
-	},
-
-	appendText: function(text, where){
-		return this.grab(this.getDocument().newTextNode(text), where);
-	},
-
-	grab: function(el, where){
-		inserters[where || 'bottom'](document.id(el, true), this);
-		return this;
-	},
-
-	inject: function(el, where){
-		inserters[where || 'bottom'](this, document.id(el, true));
-		return this;
-	},
-
-	replaces: function(el){
-		el = document.id(el, true);
-		el.parentNode.replaceChild(this, el);
-		return this;
-	},
-
-	wraps: function(el, where){
-		el = document.id(el, true);
-		return this.replaces(el).grab(el, where);
-	},
-
-	getPrevious: function(match, nocash){
-		return walk(this, 'previousSibling', null, match, false, nocash);
-	},
-
-	getAllPrevious: function(match, nocash){
-		return walk(this, 'previousSibling', null, match, true, nocash);
-	},
-
-	getNext: function(match, nocash){
-		return walk(this, 'nextSibling', null, match, false, nocash);
-	},
-
-	getAllNext: function(match, nocash){
-		return walk(this, 'nextSibling', null, match, true, nocash);
-	},
-
-	getFirst: function(match, nocash){
-		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
-	},
-
-	getLast: function(match, nocash){
-		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
-	},
-
-	getParent: function(match, nocash){
-		return walk(this, 'parentNode', null, match, false, nocash);
-	},
-
-	getParents: function(match, nocash){
-		return walk(this, 'parentNode', null, match, true, nocash);
-	},
-	
-	getSiblings: function(match, nocash){
-		return this.getParent().getChildren(match, nocash).erase(this);
-	},
-
-	getChildren: function(match, nocash){
-		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
-	},
-
-	getWindow: function(){
-		return this.ownerDocument.window;
-	},
-
-	getDocument: function(){
-		return this.ownerDocument;
-	},
-
-	getElementById: function(id, nocash){
-		var el = this.ownerDocument.getElementById(id);
-		if (!el) return null;
-		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
-			if (!parent) return null;
-		}
-		return document.id(el, nocash);
-	},
-
-	getSelected: function(){
-		return new Elements($A(this.options).filter(function(option){
-			return option.selected;
-		}));
-	},
-
-	getComputedStyle: function(property){
-		if (this.currentStyle) return this.currentStyle[property.camelCase()];
-		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
-		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
-	},
-
-	toQueryString: function(){
-		var queryString = [];
-		this.getElements('input, select, textarea', true).each(function(el){
-			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
-			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
-				return opt.value;
-			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
-			$splat(value).each(function(val){
-				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
-			});
-		});
-		return queryString.join('&');
-	},
-
-	clone: function(contents, keepid){
-		contents = contents !== false;
-		var clone = this.cloneNode(contents);
-		var clean = function(node, element){
-			if (!keepid) node.removeAttribute('id');
-			if (Browser.Engine.trident){
-				node.clearAttributes();
-				node.mergeAttributes(element);
-				node.removeAttribute('uid');
-				if (node.options){
-					var no = node.options, eo = element.options;
-					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
-				}
-			}
-			var prop = props[element.tagName.toLowerCase()];
-			if (prop && element[prop]) node[prop] = element[prop];
-		};
-
-		if (contents){
-			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
-			for (var i = ce.length; i--;) clean(ce[i], te[i]);
-		}
-
-		clean(clone, this);
-		return document.id(clone);
-	},
-
-	destroy: function(){
-		Element.empty(this);
-		Element.dispose(this);
-		clean(this, true);
-		return null;
-	},
-
-	empty: function(){
-		$A(this.childNodes).each(function(node){
-			Element.destroy(node);
-		});
-		return this;
-	},
-
-	dispose: function(){
-		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
-	},
-
-	hasChild: function(el){
-		el = document.id(el, true);
-		if (!el) return false;
-		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
-		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
-	},
-
-	match: function(tag){
-		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
-	}
-
-});
-
-Native.implement([Element, Window, Document], {
-
-	addListener: function(type, fn){
-		if (type == 'unload'){
-			var old = fn, self = this;
-			fn = function(){
-				self.removeListener('unload', fn);
-				old();
-			};
-		} else {
-			collected[this.uid] = this;
-		}
-		if (this.addEventListener) this.addEventListener(type, fn, false);
-		else this.attachEvent('on' + type, fn);
-		return this;
-	},
-
-	removeListener: function(type, fn){
-		if (this.removeEventListener) this.removeEventListener(type, fn, false);
-		else this.detachEvent('on' + type, fn);
-		return this;
-	},
-
-	retrieve: function(property, dflt){
-		var storage = get(this.uid), prop = storage[property];
-		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
-		return $pick(prop);
-	},
-
-	store: function(property, value){
-		var storage = get(this.uid);
-		storage[property] = value;
-		return this;
-	},
-
-	eliminate: function(property){
-		var storage = get(this.uid);
-		delete storage[property];
-		return this;
-	}
-
-});
-
-window.addListener('unload', purge);
-
-})();
-
-Element.Properties = new Hash;
-
-Element.Properties.style = {
-
-	set: function(style){
-		this.style.cssText = style;
-	},
-
-	get: function(){
-		return this.style.cssText;
-	},
-
-	erase: function(){
-		this.style.cssText = '';
-	}
-
-};
-
-Element.Properties.tag = {
-
-	get: function(){
-		return this.tagName.toLowerCase();
-	}
-
-};
-
-Element.Properties.html = (function(){
-	var wrapper = document.createElement('div');
-
-	var translations = {
-		table: [1, '<table>', '</table>'],
-		select: [1, '<select>', '</select>'],
-		tbody: [2, '<table><tbody>', '</tbody></table>'],
-		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
-	};
-	translations.thead = translations.tfoot = translations.tbody;
-
-	var html = {
-		set: function(){
-			var html = Array.flatten(arguments).join('');
-			var wrap = Browser.Engine.trident && translations[this.get('tag')];
-			if (wrap){
-				var first = wrapper;
-				first.innerHTML = wrap[1] + html + wrap[2];
-				for (var i = wrap[0]; i--;) first = first.firstChild;
-				this.empty().adopt(first.childNodes);
-			} else {
-				this.innerHTML = html;
-			}
-		}
-	};
-
-	html.erase = html.set;
-
-	return html;
-})();
-
-if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
-	get: function(){
-		if (this.innerText) return this.innerText;
-		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
-		var text = temp.innerText;
-		temp.destroy();
-		return text;
-	}
-};
-/*
----
-
-script: Element.Event.js
-
-description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
-
-license: MIT-style license.
-
-requires: 
-- /Element
-- /Event
-
-provides: [Element.Event]
-
-...
-*/
-
-Element.Properties.events = {set: function(events){
-	this.addEvents(events);
-}};
-
-Native.implement([Element, Window, Document], {
-
-	addEvent: function(type, fn){
-		var events = this.retrieve('events', {});
-		events[type] = events[type] || {'keys': [], 'values': []};
-		if (events[type].keys.contains(fn)) return this;
-		events[type].keys.push(fn);
-		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
-		if (custom){
-			if (custom.onAdd) custom.onAdd.call(this, fn);
-			if (custom.condition){
-				condition = function(event){
-					if (custom.condition.call(this, event)) return fn.call(this, event);
-					return true;
-				};
-			}
-			realType = custom.base || realType;
-		}
-		var defn = function(){
-			return fn.call(self);
-		};
-		var nativeEvent = Element.NativeEvents[realType];
-		if (nativeEvent){
-			if (nativeEvent == 2){
-				defn = function(event){
-					event = new Event(event, self.getWindow());
-					if (condition.call(self, event) === false) event.stop();
-				};
-			}
-			this.addListener(realType, defn);
-		}
-		events[type].values.push(defn);
-		return this;
-	},
-
-	removeEvent: function(type, fn){
-		var events = this.retrieve('events');
-		if (!events || !events[type]) return this;
-		var pos = events[type].keys.indexOf(fn);
-		if (pos == -1) return this;
-		events[type].keys.splice(pos, 1);
-		var value = events[type].values.splice(pos, 1)[0];
-		var custom = Element.Events.get(type);
-		if (custom){
-			if (custom.onRemove) custom.onRemove.call(this, fn);
-			type = custom.base || type;
-		}
-		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
-	},
-
-	addEvents: function(events){
-		for (var event in events) this.addEvent(event, events[event]);
-		return this;
-	},
-
-	removeEvents: function(events){
-		var type;
-		if ($type(events) == 'object'){
-			for (type in events) this.removeEvent(type, events[type]);
-			return this;
-		}
-		var attached = this.retrieve('events');
-		if (!attached) return this;
-		if (!events){
-			for (type in attached) this.removeEvents(type);
-			this.eliminate('events');
-		} else if (attached[events]){
-			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
-			attached[events] = null;
-		}
-		return this;
-	},
-
-	fireEvent: function(type, args, delay){
-		var events = this.retrieve('events');
-		if (!events || !events[type]) return this;
-		events[type].keys.each(function(fn){
-			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
-		}, this);
-		return this;
-	},
-
-	cloneEvents: function(from, type){
-		from = document.id(from);
-		var fevents = from.retrieve('events');
-		if (!fevents) return this;
-		if (!type){
-			for (var evType in fevents) this.cloneEvents(from, evType);
-		} else if (fevents[type]){
-			fevents[type].keys.each(function(fn){
-				this.addEvent(type, fn);
-			}, this);
-		}
-		return this;
-	}
-
-});
-
-Element.NativeEvents = {
-	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
-	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
-	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
-	keydown: 2, keypress: 2, keyup: 2, //keyboard
-	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
-	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
-	error: 1, abort: 1, scroll: 1 //misc
-};
-
-(function(){
-
-var $check = function(event){
-	var related = event.relatedTarget;
-	if (related == undefined) return true;
-	if (related === false) return false;
-	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
-};
-
-Element.Events = new Hash({
-
-	mouseenter: {
-		base: 'mouseover',
-		condition: $check
-	},
-
-	mouseleave: {
-		base: 'mouseout',
-		condition: $check
-	},
-
-	mousewheel: {
-		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
-	}
-
-});
-
-})();
-/*
----
-
-script: Element.Style.js
-
-description: Contains methods for interacting with the styles of Elements in a fashionable way.
-
-license: MIT-style license.
-
-requires:
-- /Element
-
-provides: [Element.Style]
-
-...
-*/
-
-Element.Properties.styles = {set: function(styles){
-	this.setStyles(styles);
-}};
-
-Element.Properties.opacity = {
-
-	set: function(opacity, novisibility){
-		if (!novisibility){
-			if (opacity == 0){
-				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
-			} else {
-				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
-			}
-		}
-		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
-		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
-		this.style.opacity = opacity;
-		this.store('opacity', opacity);
-	},
-
-	get: function(){
-		return this.retrieve('opacity', 1);
-	}
-
-};
-
-Element.implement({
-
-	setOpacity: function(value){
-		return this.set('opacity', value, true);
-	},
-
-	getOpacity: function(){
-		return this.get('opacity');
-	},
-
-	setStyle: function(property, value){
-		switch (property){
-			case 'opacity': return this.set('opacity', parseFloat(value));
-			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
-		}
-		property = property.camelCase();
-		if ($type(value) != 'string'){
-			var map = (Element.Styles.get(property) || '@').split(' ');
-			value = $splat(value).map(function(val, i){
-				if (!map[i]) return '';
-				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
-			}).join(' ');
-		} else if (value == String(Number(value))){
-			value = Math.round(value);
-		}
-		this.style[property] = value;
-		return this;
-	},
-
-	getStyle: function(property){
-		switch (property){
-			case 'opacity': return this.get('opacity');
-			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
-		}
-		property = property.camelCase();
-		var result = this.style[property];
-		if (!$chk(result)){
-			result = [];
-			for (var style in Element.ShortStyles){
-				if (property != style) continue;
-				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
-				return result.join(' ');
-			}
-			result = this.getComputedStyle(property);
-		}
-		if (result){
-			result = String(result);
-			var color = result.match(/rgba?\([\d\s,]+\)/);
-			if (color) result = result.replace(color[0], color[0].rgbToHex());
-		}
-		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
-			if (property.test(/^(height|width)$/)){
-				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
-				values.each(function(value){
-					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
-				}, this);
-				return this['offset' + property.capitalize()] - size + 'px';
-			}
-			if ((Browser.Engine.presto) && String(result).test('px')) return result;
-			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
-		}
-		return result;
-	},
-
-	setStyles: function(styles){
-		for (var style in styles) this.setStyle(style, styles[style]);
-		return this;
-	},
-
-	getStyles: function(){
-		var result = {};
-		Array.flatten(arguments).each(function(key){
-			result[key] = this.getStyle(key);
-		}, this);
-		return result;
-	}
-
-});
-
-Element.Styles = new Hash({
-	left: '@px', top: '@px', bottom: '@px', right: '@px',
-	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
-	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
-	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
-	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
-	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
-	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
-});
-
-Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
-
-['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
-	var Short = Element.ShortStyles;
-	var All = Element.Styles;
-	['margin', 'padding'].each(function(style){
-		var sd = style + direction;
-		Short[style][sd] = All[sd] = '@px';
-	});
-	var bd = 'border' + direction;
-	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
-	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
-	Short[bd] = {};
-	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
-	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
-	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
-});
-/*
----
-
-script: Element.Dimensions.js
-
-description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
-
-license: MIT-style license.
-
-credits:
-- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
-- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
-
-requires:
-- /Element
-
-provides: [Element.Dimensions]
-
-...
-*/
-
-(function(){
-
-Element.implement({
-
-	scrollTo: function(x, y){
-		if (isBody(this)){
-			this.getWindow().scrollTo(x, y);
-		} else {
-			this.scrollLeft = x;
-			this.scrollTop = y;
-		}
-		return this;
-	},
-
-	getSize: function(){
-		if (isBody(this)) return this.getWindow().getSize();
-		return {x: this.offsetWidth, y: this.offsetHeight};
-	},
-
-	getScrollSize: function(){
-		if (isBody(this)) return this.getWindow().getScrollSize();
-		return {x: this.scrollWidth, y: this.scrollHeight};
-	},
-
-	getScroll: function(){
-		if (isBody(this)) return this.getWindow().getScroll();
-		return {x: this.scrollLeft, y: this.scrollTop};
-	},
-
-	getScrolls: function(){
-		var element = this, position = {x: 0, y: 0};
-		while (element && !isBody(element)){
-			position.x += element.scrollLeft;
-			position.y += element.scrollTop;
-			element = element.parentNode;
-		}
-		return position;
-	},
-
-	getOffsetParent: function(){
-		var element = this;
-		if (isBody(element)) return null;
-		if (!Browser.Engine.trident) return element.offsetParent;
-		while ((element = element.parentNode) && !isBody(element)){
-			if (styleString(element, 'position') != 'static') return element;
-		}
-		return null;
-	},
-
-	getOffsets: function(){
-		if (this.getBoundingClientRect){
-			var bound = this.getBoundingClientRect(),
-				html = document.id(this.getDocument().documentElement),
-				htmlScroll = html.getScroll(),
-				elemScrolls = this.getScrolls(),
-				elemScroll = this.getScroll(),
-				isFixed = (styleString(this, 'position') == 'fixed');
-
-			return {
-				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
-				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
-			};
-		}
-
-		var element = this, position = {x: 0, y: 0};
-		if (isBody(this)) return position;
-
-		while (element && !isBody(element)){
-			position.x += element.offsetLeft;
-			position.y += element.offsetTop;
-
-			if (Browser.Engine.gecko){
-				if (!borderBox(element)){
-					position.x += leftBorder(element);
-					position.y += topBorder(element);
-				}
-				var parent = element.parentNode;
-				if (parent && styleString(parent, 'overflow') != 'visible'){
-					position.x += leftBorder(parent);
-					position.y += topBorder(parent);
-				}
-			} else if (element != this && Browser.Engine.webkit){
-				position.x += leftBorder(element);
-				position.y += topBorder(element);
-			}
-
-			element = element.offsetParent;
-		}
-		if (Browser.Engine.gecko && !borderBox(this)){
-			position.x -= leftBorder(this);
-			position.y -= topBorder(this);
-		}
-		return position;
-	},
-
-	getPosition: function(relative){
-		if (isBody(this)) return {x: 0, y: 0};
-		var offset = this.getOffsets(),
-				scroll = this.getScrolls();
-		var position = {
-			x: offset.x - scroll.x,
-			y: offset.y - scroll.y
-		};
-		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
-		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
-	},
-
-	getCoordinates: function(element){
-		if (isBody(this)) return this.getWindow().getCoordinates();
-		var position = this.getPosition(element),
-				size = this.getSize();
-		var obj = {
-			left: position.x,
-			top: position.y,
-			width: size.x,
-			height: size.y
-		};
-		obj.right = obj.left + obj.width;
-		obj.bottom = obj.top + obj.height;
-		return obj;
-	},
-
-	computePosition: function(obj){
-		return {
-			left: obj.x - styleNumber(this, 'margin-left'),
-			top: obj.y - styleNumber(this, 'margin-top')
-		};
-	},
-
-	setPosition: function(obj){
-		return this.setStyles(this.computePosition(obj));
-	}
-
-});
-
-
-Native.implement([Document, Window], {
-
-	getSize: function(){
-		if (Browser.Engine.presto || Browser.Engine.webkit){
-			var win = this.getWindow();
-			return {x: win.innerWidth, y: win.innerHeight};
-		}
-		var doc = getCompatElement(this);
-		return {x: doc.clientWidth, y: doc.clientHeight};
-	},
-
-	getScroll: function(){
-		var win = this.getWindow(), doc = getCompatElement(this);
-		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
-	},
-
-	getScrollSize: function(){
-		var doc = getCompatElement(this), min = this.getSize();
-		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
-	},
-
-	getPosition: function(){
-		return {x: 0, y: 0};
-	},
-
-	getCoordinates: function(){
-		var size = this.getSize();
-		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
-	}
-
-});
-
-// private methods
-
-var styleString = Element.getComputedStyle;
-
-function styleNumber(element, style){
-	return styleString(element, style).toInt() || 0;
-};
-
-function borderBox(element){
-	return styleString(element, '-moz-box-sizing') == 'border-box';
-};
-
-function topBorder(element){
-	return styleNumber(element, 'border-top-width');
-};
-
-function leftBorder(element){
-	return styleNumber(element, 'border-left-width');
-};
-
-function isBody(element){
-	return (/^(?:body|html)$/i).test(element.tagName);
-};
-
-function getCompatElement(element){
-	var doc = element.getDocument();
-	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
-};
-
-})();
-
-//aliases
-Element.alias('setPosition', 'position'); //compatability
-
-Native.implement([Window, Document, Element], {
-
-	getHeight: function(){
-		return this.getSize().y;
-	},
-
-	getWidth: function(){
-		return this.getSize().x;
-	},
-
-	getScrollTop: function(){
-		return this.getScroll().y;
-	},
-
-	getScrollLeft: function(){
-		return this.getScroll().x;
-	},
-
-	getScrollHeight: function(){
-		return this.getScrollSize().y;
-	},
-
-	getScrollWidth: function(){
-		return this.getScrollSize().x;
-	},
-
-	getTop: function(){
-		return this.getPosition().y;
-	},
-
-	getLeft: function(){
-		return this.getPosition().x;
-	}
-
-});
-/*
----
-
-script: Selectors.js
-
-description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.
-
-license: MIT-style license.
-
-requires:
-- /Element
-
-provides: [Selectors]
-
-...
-*/
-
-Native.implement([Document, Element], {
-
-	getElements: function(expression, nocash){
-		expression = expression.split(',');
-		var items, local = {};
-		for (var i = 0, l = expression.length; i < l; i++){
-			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
-			if (i != 0 && elements.item) elements = $A(elements);
-			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
-		}
-		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
-	}
-
-});
-
-Element.implement({
-
-	match: function(selector){
-		if (!selector || (selector == this)) return true;
-		var tagid = Selectors.Utils.parseTagAndID(selector);
-		var tag = tagid[0], id = tagid[1];
-		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
-		var parsed = Selectors.Utils.parseSelector(selector);
-		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
-	}
-
-});
-
-var Selectors = {Cache: {nth: {}, parsed: {}}};
-
-Selectors.RegExps = {
-	id: (/#([\w-]+)/),
-	tag: (/^(\w+|\*)/),
-	quick: (/^(\w+|\*)$/),
-	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
-	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
-};
-
-Selectors.Utils = {
-
-	chk: function(item, uniques){
-		if (!uniques) return true;
-		var uid = $uid(item);
-		if (!uniques[uid]) return uniques[uid] = true;
-		return false;
-	},
-
-	parseNthArgument: function(argument){
-		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
-		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
-		if (!parsed) return false;
-		var inta = parseInt(parsed[1], 10);
-		var a = (inta || inta === 0) ? inta : 1;
-		var special = parsed[2] || false;
-		var b = parseInt(parsed[3], 10) || 0;
-		if (a != 0){
-			b--;
-			while (b < 1) b += a;
-			while (b >= a) b -= a;
-		} else {
-			a = b;
-			special = 'index';
-		}
-		switch (special){
-			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
-			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
-			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
-			case 'first': parsed = {a: 0, special: 'index'}; break;
-			case 'last': parsed = {special: 'last-child'}; break;
-			case 'only': parsed = {special: 'only-child'}; break;
-			default: parsed = {a: (a - 1), special: 'index'};
-		}
-
-		return Selectors.Cache.nth[argument] = parsed;
-	},
-
-	parseSelector: function(selector){
-		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
-		var m, parsed = {classes: [], pseudos: [], attributes: []};
-		while ((m = Selectors.RegExps.combined.exec(selector))){
-			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
-			if (cn){
-				parsed.classes.push(cn);
-			} else if (pn){
-				var parser = Selectors.Pseudo.get(pn);
-				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
-				else parsed.attributes.push({name: pn, operator: '=', value: pa});
-			} else if (an){
-				parsed.attributes.push({name: an, operator: ao, value: av});
-			}
-		}
-		if (!parsed.classes.length) delete parsed.classes;
-		if (!parsed.attributes.length) delete parsed.attributes;
-		if (!parsed.pseudos.length) delete parsed.pseudos;
-		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
-		return Selectors.Cache.parsed[selector] = parsed;
-	},
-
-	parseTagAndID: function(selector){
-		var tag = selector.match(Selectors.RegExps.tag);
-		var id = selector.match(Selectors.RegExps.id);
-		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
-	},
-
-	filter: function(item, parsed, local){
-		var i;
-		if (parsed.classes){
-			for (i = parsed.classes.length; i--; i){
-				var cn = parsed.classes[i];
-				if (!Selectors.Filters.byClass(item, cn)) return false;
-			}
-		}
-		if (parsed.attributes){
-			for (i = parsed.attributes.length; i--; i){
-				var att = parsed.attributes[i];
-				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
-			}
-		}
-		if (parsed.pseudos){
-			for (i = parsed.pseudos.length; i--; i){
-				var psd = parsed.pseudos[i];
-				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
-			}
-		}
-		return true;
-	},
-
-	getByTagAndID: function(ctx, tag, id){
-		if (id){
-			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
-			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
-		} else {
-			return ctx.getElementsByTagName(tag);
-		}
-	},
-
-	search: function(self, expression, local){
-		var splitters = [];
-
-		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
-			splitters.push(m1);
-			return ':)' + m2;
-		}).split(':)');
-
-		var items, filtered, item;
-
-		for (var i = 0, l = selectors.length; i < l; i++){
-
-			var selector = selectors[i];
-
-			if (i == 0 && Selectors.RegExps.quick.test(selector)){
-				items = self.getElementsByTagName(selector);
-				continue;
-			}
-
-			var splitter = splitters[i - 1];
-
-			var tagid = Selectors.Utils.parseTagAndID(selector);
-			var tag = tagid[0], id = tagid[1];
-
-			if (i == 0){
-				items = Selectors.Utils.getByTagAndID(self, tag, id);
-			} else {
-				var uniques = {}, found = [];
-				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
-				items = found;
-			}
-
-			var parsed = Selectors.Utils.parseSelector(selector);
-
-			if (parsed){
-				filtered = [];
-				for (var m = 0, n = items.length; m < n; m++){
-					item = items[m];
-					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
-				}
-				items = filtered;
-			}
-
-		}
-
-		return items;
-
-	}
-
-};
-
-Selectors.Getters = {
-
-	' ': function(found, self, tag, id, uniques){
-		var items = Selectors.Utils.getByTagAndID(self, tag, id);
-		for (var i = 0, l = items.length; i < l; i++){
-			var item = items[i];
-			if (Selectors.Utils.chk(item, uniques)) found.push(item);
-		}
-		return found;
-	},
-
-	'>': function(found, self, tag, id, uniques){
-		var children = Selectors.Utils.getByTagAndID(self, tag, id);
-		for (var i = 0, l = children.length; i < l; i++){
-			var child = children[i];
-			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
-		}
-		return found;
-	},
-
-	'+': function(found, self, tag, id, uniques){
-		while ((self = self.nextSibling)){
-			if (self.nodeType == 1){
-				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
-				break;
-			}
-		}
-		return found;
-	},
-
-	'~': function(found, self, tag, id, uniques){
-		while ((self = self.nextSibling)){
-			if (self.nodeType == 1){
-				if (!Selectors.Utils.chk(self, uniques)) break;
-				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
-			}
-		}
-		return found;
-	}
-
-};
-
-Selectors.Filters = {
-
-	byTag: function(self, tag){
-		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
-	},
-
-	byID: function(self, id){
-		return (!id || (self.id && self.id == id));
-	},
-
-	byClass: function(self, klass){
-		return (self.className && self.className.contains && self.className.contains(klass, ' '));
-	},
-
-	byPseudo: function(self, parser, argument, local){
-		return parser.call(self, argument, local);
-	},
-
-	byAttribute: function(self, name, operator, value){
-		var result = Element.prototype.getProperty.call(self, name);
-		if (!result) return (operator == '!=');
-		if (!operator || value == undefined) return true;
-		switch (operator){
-			case '=': return (result == value);
-			case '*=': return (result.contains(value));
-			case '^=': return (result.substr(0, value.length) == value);
-			case '$=': return (result.substr(result.length - value.length) == value);
-			case '!=': return (result != value);
-			case '~=': return result.contains(value, ' ');
-			case '|=': return result.contains(value, '-');
-		}
-		return false;
-	}
-
-};
-
-Selectors.Pseudo = new Hash({
-
-	// w3c pseudo selectors
-
-	checked: function(){
-		return this.checked;
-	},
-	
-	empty: function(){
-		return !(this.innerText || this.textContent || '').length;
-	},
-
-	not: function(selector){
-		return !Element.match(this, selector);
-	},
-
-	contains: function(text){
-		return (this.innerText || this.textContent || '').contains(text);
-	},
-
-	'first-child': function(){
-		return Selectors.Pseudo.index.call(this, 0);
-	},
-
-	'last-child': function(){
-		var element = this;
-		while ((element = element.nextSibling)){
-			if (element.nodeType == 1) return false;
-		}
-		return true;
-	},
-
-	'only-child': function(){
-		var prev = this;
-		while ((prev = prev.previousSibling)){
-			if (prev.nodeType == 1) return false;
-		}
-		var next = this;
-		while ((next = next.nextSibling)){
-			if (next.nodeType == 1) return false;
-		}
-		return true;
-	},
-
-	'nth-child': function(argument, local){
-		argument = (argument == undefined) ? 'n' : argument;
-		var parsed = Selectors.Utils.parseNthArgument(argument);
-		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
-		var count = 0;
-		local.positions = local.positions || {};
-		var uid = $uid(this);
-		if (!local.positions[uid]){
-			var self = this;
-			while ((self = self.previousSibling)){
-				if (self.nodeType != 1) continue;
-				count ++;
-				var position = local.positions[$uid(self)];
-				if (position != undefined){
-					count = position + count;
-					break;
-				}
-			}
-			local.positions[uid] = count;
-		}
-		return (local.positions[uid] % parsed.a == parsed.b);
-	},
-
-	// custom pseudo selectors
-
-	index: function(index){
-		var element = this, count = 0;
-		while ((element = element.previousSibling)){
-			if (element.nodeType == 1 && ++count > index) return false;
-		}
-		return (count == index);
-	},
-
-	even: function(argument, local){
-		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
-	},
-
-	odd: function(argument, local){
-		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
-	},
-	
-	selected: function(){
-		return this.selected;
-	},
-	
-	enabled: function(){
-		return (this.disabled === false);
-	}
-
-});
-/*
----
-
-script: DomReady.js
-
-description: Contains the custom event domready.
-
-license: MIT-style license.
-
-requires:
-- /Element.Event
-
-provides: [DomReady]
-
-...
-*/
-
-Element.Events.domready = {
-
-	onAdd: function(fn){
-		if (Browser.loaded) fn.call(this);
-	}
-
-};
-
-(function(){
-
-	var domready = function(){
-		if (Browser.loaded) return;
-		Browser.loaded = true;
-		window.fireEvent('domready');
-		document.fireEvent('domready');
-	};
-	
-	window.addEvent('load', domready);
-
-	if (Browser.Engine.trident){
-		var temp = document.createElement('div');
-		(function(){
-			($try(function(){
-				temp.doScroll(); // Technique by Diego Perini
-				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
-			})) ? domready() : arguments.callee.delay(50);
-		})();
-	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
-		(function(){
-			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
-		})();
-	} else {
-		document.addEvent('DOMContentLoaded', domready);
-	}
-
-})();
-/*
----
-
-script: JSON.js
-
-description: JSON encoder and decoder.
-
-license: MIT-style license.
-
-See Also: <http://www.json.org/>
-
-requires:
-- /Array
-- /String
-- /Number
-- /Function
-- /Hash
-
-provides: [JSON]
-
-...
-*/
-
-var JSON = new Hash(this.JSON && {
-	stringify: JSON.stringify,
-	parse: JSON.parse
-}).extend({
-	
-	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},
-
-	$replaceChars: function(chr){
-		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
-	},
-
-	encode: function(obj){
-		switch ($type(obj)){
-			case 'string':
-				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
-			case 'array':
-				return '[' + String(obj.map(JSON.encode).clean()) + ']';
-			case 'object': case 'hash':
-				var string = [];
-				Hash.each(obj, function(value, key){
-					var json = JSON.encode(value);
-					if (json) string.push(JSON.encode(key) + ':' + json);
-				});
-				return '{' + string + '}';
-			case 'number': case 'boolean': return String(obj);
-			case false: return 'null';
-		}
-		return null;
-	},
-
-	decode: function(string, secure){
-		if ($type(string) != 'string' || !string.length) return null;
-		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
-		return eval('(' + string + ')');
-	}
-
-});
-
-Native.implement([Hash, Array, String, Number], {
-
-	toJSON: function(){
-		return JSON.encode(this);
-	}
-
-});
-/*
----
-
-script: Cookie.js
-
-description: Class for creating, reading, and deleting browser Cookies.
-
-license: MIT-style license.
-
-credits:
-- Based on the functions by Peter-Paul Koch (http://quirksmode.org).
-
-requires:
-- /Options
-
-provides: [Cookie]
-
-...
-*/
-
-var Cookie = new Class({
-
-	Implements: Options,
-
-	options: {
-		path: false,
-		domain: false,
-		duration: false,
-		secure: false,
-		document: document
-	},
-
-	initialize: function(key, options){
-		this.key = key;
-		this.setOptions(options);
-	},
-
-	write: function(value){
-		value = encodeURIComponent(value);
-		if (this.options.domain) value += '; domain=' + this.options.domain;
-		if (this.options.path) value += '; path=' + this.options.path;
-		if (this.options.duration){
-			var date = new Date();
-			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
-			value += '; expires=' + date.toGMTString();
-		}
-		if (this.options.secure) value += '; secure';
-		this.options.document.cookie = this.key + '=' + value;
-		return this;
-	},
-
-	read: function(){
-		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
-		return (value) ? decodeURIComponent(value[1]) : null;
-	},
-
-	dispose: function(){
-		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
-		return this;
-	}
-
-});
-
-Cookie.write = function(key, value, options){
-	return new Cookie(key, options).write(value);
-};
-
-Cookie.read = function(key){
-	return new Cookie(key).read();
-};
-
-Cookie.dispose = function(key, options){
-	return new Cookie(key, options).dispose();
-};
-/*
----
-
-script: Swiff.js
-
-description: Wrapper for embedding SWF movies. Supports External Interface Communication.
-
-license: MIT-style license.
-
-credits: 
-- Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
-
-requires:
-- /Options
-- /$util
-
-provides: [Swiff]
-
-...
-*/
-
-var Swiff = new Class({
-
-	Implements: [Options],
-
-	options: {
-		id: null,
-		height: 1,
-		width: 1,
-		container: null,
-		properties: {},
-		params: {
-			quality: 'high',
-			allowScriptAccess: 'always',
-			wMode: 'transparent',
-			swLiveConnect: true
-		},
-		callBacks: {},
-		vars: {}
-	},
-
-	toElement: function(){
-		return this.object;
-	},
-
-	initialize: function(path, options){
-		this.instance = 'Swiff_' + $time();
-
-		this.setOptions(options);
-		options = this.options;
-		var id = this.id = options.id || this.instance;
-		var container = document.id(options.container);
-
-		Swiff.CallBacks[this.instance] = {};
-
-		var params = options.params, vars = options.vars, callBacks = options.callBacks;
-		var properties = $extend({height: options.height, width: options.width}, options.properties);
-
-		var self = this;
-
-		for (var callBack in callBacks){
-			Swiff.CallBacks[this.instance][callBack] = (function(option){
-				return function(){
-					return option.apply(self.object, arguments);
-				};
-			})(callBacks[callBack]);
-			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
-		}
-
-		params.flashVars = Hash.toQueryString(vars);
-		if (Browser.Engine.trident){
-			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
-			params.movie = path;
-		} else {
-			properties.type = 'application/x-shockwave-flash';
-			properties.data = path;
-		}
-		var build = '<object id="' + id + '"';
-		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
-		build += '>';
-		for (var param in params){
-			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
-		}
-		build += '</object>';
-		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
-	},
-
-	replaces: function(element){
-		element = document.id(element, true);
-		element.parentNode.replaceChild(this.toElement(), element);
-		return this;
-	},
-
-	inject: function(element){
-		document.id(element, true).appendChild(this.toElement());
-		return this;
-	},
-
-	remote: function(){
-		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
-	}
-
-});
-
-Swiff.CallBacks = {};
-
-Swiff.remote = function(obj, fn){
-	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
-	return eval(rs);
-};
-/*
----
-
-script: Fx.js
-
-description: Contains the basic animation logic to be extended by all other Fx Classes.
-
-license: MIT-style license.
-
-requires:
-- /Chain
-- /Events
-- /Options
-
-provides: [Fx]
-
-...
-*/
-
-var Fx = new Class({
-
-	Implements: [Chain, Events, Options],
-
-	options: {
-		/*
-		onStart: $empty,
-		onCancel: $empty,
-		onComplete: $empty,
-		*/
-		fps: 50,
-		unit: false,
-		duration: 500,
-		link: 'ignore'
-	},
-
-	initialize: function(options){
-		this.subject = this.subject || this;
-		this.setOptions(options);
-		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
-		var wait = this.options.wait;
-		if (wait === false) this.options.link = 'cancel';
-	},
-
-	getTransition: function(){
-		return function(p){
-			return -(Math.cos(Math.PI * p) - 1) / 2;
-		};
-	},
-
-	step: function(){
-		var time = $time();
-		if (time < this.time + this.options.duration){
-			var delta = this.transition((time - this.time) / this.options.duration);
-			this.set(this.compute(this.from, this.to, delta));
-		} else {
-			this.set(this.compute(this.from, this.to, 1));
-			this.complete();
-		}
-	},
-
-	set: function(now){
-		return now;
-	},
-
-	compute: function(from, to, delta){
-		return Fx.compute(from, to, delta);
-	},
-
-	check: function(){
-		if (!this.timer) return true;
-		switch (this.options.link){
-			case 'cancel': this.cancel(); return true;
-			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
-		}
-		return false;
-	},
-
-	start: function(from, to){
-		if (!this.check(from, to)) return this;
-		this.from = from;
-		this.to = to;
-		this.time = 0;
-		this.transition = this.getTransition();
-		this.startTimer();
-		this.onStart();
-		return this;
-	},
-
-	complete: function(){
-		if (this.stopTimer()) this.onComplete();
-		return this;
-	},
-
-	cancel: function(){
-		if (this.stopTimer()) this.onCancel();
-		return this;
-	},
-
-	onStart: function(){
-		this.fireEvent('start', this.subject);
-	},
-
-	onComplete: function(){
-		this.fireEvent('complete', this.subject);
-		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
-	},
-
-	onCancel: function(){
-		this.fireEvent('cancel', this.subject).clearChain();
-	},
-
-	pause: function(){
-		this.stopTimer();
-		return this;
-	},
-
-	resume: function(){
-		this.startTimer();
-		return this;
-	},
-
-	stopTimer: function(){
-		if (!this.timer) return false;
-		this.time = $time() - this.time;
-		this.timer = $clear(this.timer);
-		return true;
-	},
-
-	startTimer: function(){
-		if (this.timer) return false;
-		this.time = $time() - this.time;
-		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
-		return true;
-	}
-
-});
-
-Fx.compute = function(from, to, delta){
-	return (to - from) * delta + from;
-};
-
-Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
-/*
----
-
-script: Fx.CSS.js
-
-description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
-
-license: MIT-style license.
-
-requires:
-- /Fx
-- /Element.Style
-
-provides: [Fx.CSS]
-
-...
-*/
-
-Fx.CSS = new Class({
-
-	Extends: Fx,
-
-	//prepares the base from/to object
-
-	prepare: function(element, property, values){
-		values = $splat(values);
-		var values1 = values[1];
-		if (!$chk(values1)){
-			values[1] = values[0];
-			values[0] = element.getStyle(property);
-		}
-		var parsed = values.map(this.parse);
-		return {from: parsed[0], to: parsed[1]};
-	},
-
-	//parses a value into an array
-
-	parse: function(value){
-		value = $lambda(value)();
-		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
-		return value.map(function(val){
-			val = String(val);
-			var found = false;
-			Fx.CSS.Parsers.each(function(parser, key){
-				if (found) return;
-				var parsed = parser.parse(val);
-				if ($chk(parsed)) found = {value: parsed, parser: parser};
-			});
-			found = found || {value: val, parser: Fx.CSS.Parsers.String};
-			return found;
-		});
-	},
-
-	//computes by a from and to prepared objects, using their parsers.
-
-	compute: function(from, to, delta){
-		var computed = [];
-		(Math.min(from.length, to.length)).times(function(i){
-			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
-		});
-		computed.$family = {name: 'fx:css:value'};
-		return computed;
-	},
-
-	//serves the value as settable
-
-	serve: function(value, unit){
-		if ($type(value) != 'fx:css:value') value = this.parse(value);
-		var returned = [];
-		value.each(function(bit){
-			returned = returned.concat(bit.parser.serve(bit.value, unit));
-		});
-		return returned;
-	},
-
-	//renders the change to an element
-
-	render: function(element, property, value, unit){
-		element.setStyle(property, this.serve(value, unit));
-	},
-
-	//searches inside the page css to find the values for a selector
-
-	search: function(selector){
-		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
-		var to = {};
-		Array.each(document.styleSheets, function(sheet, j){
-			var href = sheet.href;
-			if (href && href.contains('://') && !href.contains(document.domain)) return;
-			var rules = sheet.rules || sheet.cssRules;
-			Array.each(rules, function(rule, i){
-				if (!rule.style) return;
-				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
-					return m.toLowerCase();
-				}) : null;
-				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
-				Element.Styles.each(function(value, style){
-					if (!rule.style[style] || Element.ShortStyles[style]) return;
-					value = String(rule.style[style]);
-					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
-				});
-			});
-		});
-		return Fx.CSS.Cache[selector] = to;
-	}
-
-});
-
-Fx.CSS.Cache = {};
-
-Fx.CSS.Parsers = new Hash({
-
-	Color: {
-		parse: function(value){
-			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
-			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
-		},
-		compute: function(from, to, delta){
-			return from.map(function(value, i){
-				return Math.round(Fx.compute(from[i], to[i], delta));
-			});
-		},
-		serve: function(value){
-			return value.map(Number);
-		}
-	},
-
-	Number: {
-		parse: parseFloat,
-		compute: Fx.compute,
-		serve: function(value, unit){
-			return (unit) ? value + unit : value;
-		}
-	},
-
-	String: {
-		parse: $lambda(false),
-		compute: $arguments(1),
-		serve: $arguments(0)
-	}
-
-});
-/*
----
-
-script: Fx.Tween.js
-
-description: Formerly Fx.Style, effect to transition any CSS property for an element.
-
-license: MIT-style license.
-
-requires: 
-- /Fx.CSS
-
-provides: [Fx.Tween, Element.fade, Element.highlight]
-
-...
-*/
-
-Fx.Tween = new Class({
-
-	Extends: Fx.CSS,
-
-	initialize: function(element, options){
-		this.element = this.subject = document.id(element);
-		this.parent(options);
-	},
-
-	set: function(property, now){
-		if (arguments.length == 1){
-			now = property;
-			property = this.property || this.options.property;
-		}
-		this.render(this.element, property, now, this.options.unit);
-		return this;
-	},
-
-	start: function(property, from, to){
-		if (!this.check(property, from, to)) return this;
-		var args = Array.flatten(arguments);
-		this.property = this.options.property || args.shift();
-		var parsed = this.prepare(this.element, this.property, args);
-		return this.parent(parsed.from, parsed.to);
-	}
-
-});
-
-Element.Properties.tween = {
-
-	set: function(options){
-		var tween = this.retrieve('tween');
-		if (tween) tween.cancel();
-		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('tween')){
-			if (options || !this.retrieve('tween:options')) this.set('tween', options);
-			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
-		}
-		return this.retrieve('tween');
-	}
-
-};
-
-Element.implement({
-
-	tween: function(property, from, to){
-		this.get('tween').start(arguments);
-		return this;
-	},
-
-	fade: function(how){
-		var fade = this.get('tween'), o = 'opacity', toggle;
-		how = $pick(how, 'toggle');
-		switch (how){
-			case 'in': fade.start(o, 1); break;
-			case 'out': fade.start(o, 0); break;
-			case 'show': fade.set(o, 1); break;
-			case 'hide': fade.set(o, 0); break;
-			case 'toggle':
-				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
-				fade.start(o, (flag) ? 0 : 1);
-				this.store('fade:flag', !flag);
-				toggle = true;
-			break;
-			default: fade.start(o, arguments);
-		}
-		if (!toggle) this.eliminate('fade:flag');
-		return this;
-	},
-
-	highlight: function(start, end){
-		if (!end){
-			end = this.retrieve('highlight:original', this.getStyle('background-color'));
-			end = (end == 'transparent') ? '#fff' : end;
-		}
-		var tween = this.get('tween');
-		tween.start('background-color', start || '#ffff88', end).chain(function(){
-			this.setStyle('background-color', this.retrieve('highlight:original'));
-			tween.callChain();
-		}.bind(this));
-		return this;
-	}
-
-});
-/*
----
-
-script: Fx.Morph.js
-
-description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
-
-license: MIT-style license.
-
-requires:
-- /Fx.CSS
-
-provides: [Fx.Morph]
-
-...
-*/
-
-Fx.Morph = new Class({
-
-	Extends: Fx.CSS,
-
-	initialize: function(element, options){
-		this.element = this.subject = document.id(element);
-		this.parent(options);
-	},
-
-	set: function(now){
-		if (typeof now == 'string') now = this.search(now);
-		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
-		return this;
-	},
-
-	compute: function(from, to, delta){
-		var now = {};
-		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
-		return now;
-	},
-
-	start: function(properties){
-		if (!this.check(properties)) return this;
-		if (typeof properties == 'string') properties = this.search(properties);
-		var from = {}, to = {};
-		for (var p in properties){
-			var parsed = this.prepare(this.element, p, properties[p]);
-			from[p] = parsed.from;
-			to[p] = parsed.to;
-		}
-		return this.parent(from, to);
-	}
-
-});
-
-Element.Properties.morph = {
-
-	set: function(options){
-		var morph = this.retrieve('morph');
-		if (morph) morph.cancel();
-		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('morph')){
-			if (options || !this.retrieve('morph:options')) this.set('morph', options);
-			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
-		}
-		return this.retrieve('morph');
-	}
-
-};
-
-Element.implement({
-
-	morph: function(props){
-		this.get('morph').start(props);
-		return this;
-	}
-
-});
-/*
----
-
-script: Fx.Transitions.js
-
-description: Contains a set of advanced transitions to be used with any of the Fx Classes.
-
-license: MIT-style license.
-
-credits:
-- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
-
-requires:
-- /Fx
-
-provides: [Fx.Transitions]
-
-...
-*/
-
-Fx.implement({
-
-	getTransition: function(){
-		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
-		if (typeof trans == 'string'){
-			var data = trans.split(':');
-			trans = Fx.Transitions;
-			trans = trans[data[0]] || trans[data[0].capitalize()];
-			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
-		}
-		return trans;
-	}
-
-});
-
-Fx.Transition = function(transition, params){
-	params = $splat(params);
-	return $extend(transition, {
-		easeIn: function(pos){
-			return transition(pos, params);
-		},
-		easeOut: function(pos){
-			return 1 - transition(1 - pos, params);
-		},
-		easeInOut: function(pos){
-			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
-		}
-	});
-};
-
-Fx.Transitions = new Hash({
-
-	linear: $arguments(0)
-
-});
-
-Fx.Transitions.extend = function(transitions){
-	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
-};
-
-Fx.Transitions.extend({
-
-	Pow: function(p, x){
-		return Math.pow(p, x[0] || 6);
-	},
-
-	Expo: function(p){
-		return Math.pow(2, 8 * (p - 1));
-	},
-
-	Circ: function(p){
-		return 1 - Math.sin(Math.acos(p));
-	},
-
-	Sine: function(p){
-		return 1 - Math.sin((1 - p) * Math.PI / 2);
-	},
-
-	Back: function(p, x){
-		x = x[0] || 1.618;
-		return Math.pow(p, 2) * ((x + 1) * p - x);
-	},
-
-	Bounce: function(p){
-		var value;
-		for (var a = 0, b = 1; 1; a += b, b /= 2){
-			if (p >= (7 - 4 * a) / 11){
-				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
-				break;
-			}
-		}
-		return value;
-	},
-
-	Elastic: function(p, x){
-		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
-	}
-
-});
-
-['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
-	Fx.Transitions[transition] = new Fx.Transition(function(p){
-		return Math.pow(p, [i + 2]);
-	});
-});
-/*
----
-
-script: Request.js
-
-description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
-
-license: MIT-style license.
-
-requires:
-- /Element
-- /Chain
-- /Events
-- /Options
-- /Browser
-
-provides: [Request]
-
-...
-*/
-
-var Request = new Class({
-
-	Implements: [Chain, Events, Options],
-
-	options: {/*
-		onRequest: $empty,
-		onComplete: $empty,
-		onCancel: $empty,
-		onSuccess: $empty,
-		onFailure: $empty,
-		onException: $empty,*/
-		url: '',
-		data: '',
-		headers: {
-			'X-Requested-With': 'XMLHttpRequest',
-			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
-		},
-		async: true,
-		format: false,
-		method: 'post',
-		link: 'ignore',
-		isSuccess: null,
-		emulation: true,
-		urlEncoded: true,
-		encoding: 'utf-8',
-		evalScripts: false,
-		evalResponse: false,
-		noCache: false
-	},
-
-	initialize: function(options){
-		this.xhr = new Browser.Request();
-		this.setOptions(options);
-		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
-		this.headers = new Hash(this.options.headers);
-	},
-
-	onStateChange: function(){
-		if (this.xhr.readyState != 4 || !this.running) return;
-		this.running = false;
-		this.status = 0;
-		$try(function(){
-			this.status = this.xhr.status;
-		}.bind(this));
-		this.xhr.onreadystatechange = $empty;
-		if (this.options.isSuccess.call(this, this.status)){
-			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
-			this.success(this.response.text, this.response.xml);
-		} else {
-			this.response = {text: null, xml: null};
-			this.failure();
-		}
-	},
-
-	isSuccess: function(){
-		return ((this.status >= 200) && (this.status < 300));
-	},
-
-	processScripts: function(text){
-		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
-		return text.stripScripts(this.options.evalScripts);
-	},
-
-	success: function(text, xml){
-		this.onSuccess(this.processScripts(text), xml);
-	},
-
-	onSuccess: function(){
-		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
-	},
-
-	failure: function(){
-		this.onFailure();
-	},
-
-	onFailure: function(){
-		this.fireEvent('complete').fireEvent('failure', this.xhr);
-	},
-
-	setHeader: function(name, value){
-		this.headers.set(name, value);
-		return this;
-	},
-
-	getHeader: function(name){
-		return $try(function(){
-			return this.xhr.getResponseHeader(name);
-		}.bind(this));
-	},
-
-	check: function(){
-		if (!this.running) return true;
-		switch (this.options.link){
-			case 'cancel': this.cancel(); return true;
-			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
-		}
-		return false;
-	},
-
-	send: function(options){
-		if (!this.check(options)) return this;
-		this.running = true;
-
-		var type = $type(options);
-		if (type == 'string' || type == 'element') options = {data: options};
-
-		var old = this.options;
-		options = $extend({data: old.data, url: old.url, method: old.method}, options);
-		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
-
-		switch ($type(data)){
-			case 'element': data = document.id(data).toQueryString(); break;
-			case 'object': case 'hash': data = Hash.toQueryString(data);
-		}
-
-		if (this.options.format){
-			var format = 'format=' + this.options.format;
-			data = (data) ? format + '&' + data : format;
-		}
-
-		if (this.options.emulation && !['get', 'post'].contains(method)){
-			var _method = '_method=' + method;
-			data = (data) ? _method + '&' + data : _method;
-			method = 'post';
-		}
-
-		if (this.options.urlEncoded && method == 'post'){
-			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
-			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
-		}
-
-		if (this.options.noCache){
-			var noCache = 'noCache=' + new Date().getTime();
-			data = (data) ? noCache + '&' + data : noCache;
-		}
-
-		var trimPosition = url.lastIndexOf('/');
-		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
-
-		if (data && method == 'get'){
-			url = url + (url.contains('?') ? '&' : '?') + data;
-			data = null;
-		}
-
-		this.xhr.open(method.toUpperCase(), url, this.options.async);
-
-		this.xhr.onreadystatechange = this.onStateChange.bind(this);
-
-		this.headers.each(function(value, key){
-			try {
-				this.xhr.setRequestHeader(key, value);
-			} catch (e){
-				this.fireEvent('exception', [key, value]);
-			}
-		}, this);
-
-		this.fireEvent('request');
-		this.xhr.send(data);
-		if (!this.options.async) this.onStateChange();
-		return this;
-	},
-
-	cancel: function(){
-		if (!this.running) return this;
-		this.running = false;
-		this.xhr.abort();
-		this.xhr.onreadystatechange = $empty;
-		this.xhr = new Browser.Request();
-		this.fireEvent('cancel');
-		return this;
-	}
-
-});
-
-(function(){
-
-var methods = {};
-['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
-	methods[method] = function(){
-		var params = Array.link(arguments, {url: String.type, data: $defined});
-		return this.send($extend(params, {method: method}));
-	};
-});
-
-Request.implement(methods);
-
-})();
-
-Element.Properties.send = {
-
-	set: function(options){
-		var send = this.retrieve('send');
-		if (send) send.cancel();
-		return this.eliminate('send').store('send:options', $extend({
-			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
-		}, options));
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('send')){
-			if (options || !this.retrieve('send:options')) this.set('send', options);
-			this.store('send', new Request(this.retrieve('send:options')));
-		}
-		return this.retrieve('send');
-	}
-
-};
-
-Element.implement({
-
-	send: function(url){
-		var sender = this.get('send');
-		sender.send({data: this, url: url || sender.options.url});
-		return this;
-	}
-
-});
-/*
----
-
-script: Request.HTML.js
-
-description: Extends the basic Request Class with additional methods for interacting with HTML responses.
-
-license: MIT-style license.
-
-requires:
-- /Request
-- /Element
-
-provides: [Request.HTML]
-
-...
-*/
-
-Request.HTML = new Class({
-
-	Extends: Request,
-
-	options: {
-		update: false,
-		append: false,
-		evalScripts: true,
-		filter: false
-	},
-
-	processHTML: function(text){
-		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
-		text = (match) ? match[1] : text;
-
-		var container = new Element('div');
-
-		return $try(function(){
-			var root = '<root>' + text + '</root>', doc;
-			if (Browser.Engine.trident){
-				doc = new ActiveXObject('Microsoft.XMLDOM');
-				doc.async = false;
-				doc.loadXML(root);
-			} else {
-				doc = new DOMParser().parseFromString(root, 'text/xml');
-			}
-			root = doc.getElementsByTagName('root')[0];
-			if (!root) return null;
-			for (var i = 0, k = root.childNodes.length; i < k; i++){
-				var child = Element.clone(root.childNodes[i], true, true);
-				if (child) container.grab(child);
-			}
-			return container;
-		}) || container.set('html', text);
-	},
-
-	success: function(text){
-		var options = this.options, response = this.response;
-
-		response.html = text.stripScripts(function(script){
-			response.javascript = script;
-		});
-
-		var temp = this.processHTML(response.html);
-
-		response.tree = temp.childNodes;
-		response.elements = temp.getElements('*');
-
-		if (options.filter) response.tree = response.elements.filter(options.filter);
-		if (options.update) document.id(options.update).empty().set('html', response.html);
-		else if (options.append) document.id(options.append).adopt(temp.getChildren());
-		if (options.evalScripts) $exec(response.javascript);
-
-		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
-	}
-
-});
-
-Element.Properties.load = {
-
-	set: function(options){
-		var load = this.retrieve('load');
-		if (load) load.cancel();
-		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
-	},
-
-	get: function(options){
-		if (options || ! this.retrieve('load')){
-			if (options || !this.retrieve('load:options')) this.set('load', options);
-			this.store('load', new Request.HTML(this.retrieve('load:options')));
-		}
-		return this.retrieve('load');
-	}
-
-};
-
-Element.implement({
-
-	load: function(){
-		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
-		return this;
-	}
-
-});
-/*
----
-
-script: Request.JSON.js
-
-description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
-
-license: MIT-style license.
-
-requires:
-- /Request JSON
-
-provides: [Request.HTML]
-
-...
-*/
-
-Request.JSON = new Class({
-
-	Extends: Request,
-
-	options: {
-		secure: true
-	},
-
-	initialize: function(options){
-		this.parent(options);
-		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
-	},
-
-	success: function(text){
-		this.response.json = JSON.decode(text, this.options.secure);
-		this.onSuccess(this.response.json, text);
-	}
-
-});
-/*
----
-
-script: More.js
-
-description: MooTools More
-
-license: MIT-style license
-
-authors:
-- Guillermo Rauch
-- Thomas Aylott
-- Scott Kyle
-
-requires:
-- core:1.2.4/MooTools
-
-provides: [MooTools.More]
-
-...
-*/
-
-MooTools.More = {
-	'version': '1.2.4.1'
-};/*
----
-
-script: Log.js
-
-description: Provides basic logging functionality for plugins to implement.
-
-license: MIT-style license
-
-authors:
-- Guillermo Rauch
-- Thomas Aylott
-- Scott Kyle
-
-requires:
-- core:1.2.4/Class
-- /MooTools.More
-
-provides: [Log]
-
-...
-*/
-
-(function(){
-
-var global = this;
-
-var log = function(){
-	if (global.console && console.log){
-		try {
-			console.log.apply(console, arguments);
-		} catch(e) {
-			console.log(Array.slice(arguments));
-		}
-	} else {
-		Log.logged.push(arguments);
-	}
-	return this;
-};
-
-var disabled = function(){
-	this.logged.push(arguments);
-	return this;
-};
-
-this.Log = new Class({
-	
-	logged: [],
-	
-	log: disabled,
-	
-	resetLog: function(){
-		this.logged.empty();
-		return this;
-	},
-
-	enableLog: function(){
-		this.log = log;
-		this.logged.each(function(args){
-			this.log.apply(this, args);
-		}, this);
-		return this.resetLog();
-	},
-
-	disableLog: function(){
-		this.log = disabled;
-		return this;
-	}
-	
-});
-
-Log.extend(new Log).enableLog();
-
-// legacy
-Log.logger = function(){
-	return this.log.apply(this, arguments);
-};
-
-})();/*
----
-
-script: Depender.js
-
-description: A stand alone dependency loader for the MooTools library.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Events
-- core:1.2.4/Request.JSON
-- /MooTools.More
-- /Log
-
-provides: Depender
-
-...
-*/
-
-var Depender = {
-
-	options: {
-		/* 
-		onRequire: $empty(options),
-		onRequirementLoaded: $empty([scripts, options]),
-		onScriptLoaded: $empty({
-			script: script, 
-			totalLoaded: percentOfTotalLoaded, 
-			loaded: scriptsState
-		}),
-		serial: false,
-		target: null,
-		noCache: false,
-		log: false,*/
-		loadedSources: [],
-		loadedScripts: ['Core', 'Browser', 'Array', 'String', 'Function', 'Number', 'Hash', 'Element', 'Event', 'Element.Event', 'Class', 'DomReady', 'Class.Extras', 'Request', 'JSON', 'Request.JSON', 'More', 'Depender', 'Log'],
-		useScriptInjection: true
-	},
-
-	loaded: [],
-
-	sources: {},
-
-	libs: {},
-
-	include: function(libs){
-		this.log('include: ', libs);
-		this.mapLoaded = false;
-		var loader = function(data){
-			this.libs = $merge(this.libs, data);
-			$each(this.libs, function(data, lib){
-				if (data.scripts) this.loadSource(lib, data.scripts);
-			}, this);
-		}.bind(this);
-		if ($type(libs) == 'string'){
-			this.log('fetching libs ', libs);
-			this.request(libs, loader);
-		} else {
-			loader(libs);
-		}
-		return this;
-	},
-
-	required: [],
-
-	require: function(options){
-		var loaded = function(){
-			var scripts = this.calculateDependencies(options.scripts);
-			if (options.sources){
-				options.sources.each(function(source){
-					scripts.combine(this.libs[source].files);
-				}, this);
-			}
-			if (options.serial) scripts.combine(this.getLoadedScripts());
-			options.scripts = scripts;
-			this.required.push(options);
-			this.fireEvent('require', options);
-			this.loadScripts(options.scripts);
-		};
-		if (this.mapLoaded){
-			loaded.call(this);
-		} else {
-			this.addEvent('mapLoaded', function(){
-				loaded.call(this);
-				this.removeEvent('mapLoaded', arguments.callee);
-			});
-		}
-		return this;
-	},
-
-	cleanDoubleSlash: function(str){
-		if (!str) return str;
-		var prefix = '';
-		if (str.test(/^http:\/\//)){
-			prefix = 'http://';
-			str = str.substring(7, str.length);
-		}
-		str = str.replace(/\/\//g, '/');
-		return prefix + str;
-	},
-
-	request: function(url, callback){
-		new Request.JSON({
-			url: url,
-			secure: false,
-			onSuccess: callback
-		}).send();
-	},
-
-	loadSource: function(lib, source){
-		if (this.libs[lib].files){
-			this.dataLoaded();
-			return;
-		}
-		this.log('loading source: ', source);
-		this.request(this.cleanDoubleSlash(source + '/scripts.json'), function(result){
-			this.log('loaded source: ', source);
-			this.libs[lib].files = result;
-			this.dataLoaded();
-		}.bind(this));
-	},
-
-	dataLoaded: function(){
-		var loaded = true;
-		$each(this.libs, function(v, k){
-			if (!this.libs[k].files) loaded = false;
-		}, this);
-		if (loaded){
-			this.mapTree();
-			this.mapLoaded = true;
-			this.calculateLoaded();
-			this.lastLoaded = this.getLoadedScripts().getLength();
-			this.fireEvent('mapLoaded');
-		}
-	},
-
-	calculateLoaded: function(){
-		var set = function(script){
-			this.scriptsState[script] = true;
-		}.bind(this);
-		if (this.options.loadedScripts) this.options.loadedScripts.each(set);
-		if (this.options.loadedSources){
-			this.options.loadedSources.each(function(lib){
-				$each(this.libs[lib].files, function(dir){
-					$each(dir, function(data, file){
-						set(file);
-					}, this);
-				}, this);
-			}, this);
-		}
-	},
-
-	deps: {},
-
-	pathMap: {},
-
-	mapTree: function(){
-		$each(this.libs, function(data, source){
-			$each(data.files, function(scripts, folder){
-				$each(scripts, function(details, script){
-					var path = source + ':' + folder + ':' + script;
-					if (this.deps[path]) return;
-					this.deps[path] = details.deps;
-					this.pathMap[script] = path;
-				}, this);
-			}, this);
-		}, this);
-	},
-
-	getDepsForScript: function(script){
-		return this.deps[this.pathMap[script]] || [];
-	},
-
-	calculateDependencies: function(scripts){
-		var reqs = [];
-		$splat(scripts).each(function(script){
-			if (script == 'None' || !script) return;
-			var deps = this.getDepsForScript(script);
-			if (!deps){
-				if (window.console && console.warn) console.warn('dependencies not mapped: script: %o, map: %o, :deps: %o', script, this.pathMap, this.deps);
-			} else {
-				deps.each(function(scr){
-					if (scr == script || scr == 'None' || !scr) return;
-					if (!reqs.contains(scr)) reqs.combine(this.calculateDependencies(scr));
-					reqs.include(scr);
-				}, this);
-			}
-			reqs.include(script);
-		}, this);
-		return reqs;
-	},
-
-	getPath: function(script){
-		try {
-			var chunks = this.pathMap[script].split(':');
-			var lib = this.libs[chunks[0]];
-			var dir = (lib.path || lib.scripts) + '/';
-			chunks.shift();
-			return this.cleanDoubleSlash(dir + chunks.join('/') + '.js');
-		} catch(e){
-			return script;
-		}
-	},
-
-	loadScripts: function(scripts){
-		scripts = scripts.filter(function(s){
-			if (!this.scriptsState[s] && s != 'None'){
-				this.scriptsState[s] = false;
-				return true;
-			}
-		}, this);
-		if (scripts.length){
-			scripts.each(function(scr){
-				this.loadScript(scr);
-			}, this);
-		} else {
-			this.check();
-		}
-	},
-
-	toLoad: [],
-
-	loadScript: function(script){
-		if (this.scriptsState[script] && this.toLoad.length){
-			this.loadScript(this.toLoad.shift());
-			return;
-		} else if (this.loading){
-			this.toLoad.push(script);
-			return;
-		}
-		var finish = function(){
-			this.loading = false;
-			this.scriptLoaded(script);
-			if (this.toLoad.length) this.loadScript(this.toLoad.shift());
-		}.bind(this);
-		var error = function(){
-			this.log('could not load: ', scriptPath);
-		}.bind(this);
-		this.loading = true;
-		var scriptPath = this.getPath(script);
-		if (this.options.useScriptInjection){
-			this.log('injecting script: ', scriptPath);
-			var loaded = function(){
-				this.log('loaded script: ', scriptPath);
-				finish();
-			}.bind(this);
-			new Element('script', {
-				src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
-				events: {
-					load: loaded,
-					readystatechange: function(){
-						if (['loaded', 'complete'].contains(this.readyState)) loaded();
-					},
-					error: error
-				}
-			}).inject(this.options.target || document.head);
-		} else {
-			this.log('requesting script: ', scriptPath);
-			new Request({
-				url: scriptPath,
-				noCache: this.options.noCache,
-				onComplete: function(js){
-					this.log('loaded script: ', scriptPath);
-					$exec(js);
-					finish();
-				}.bind(this),
-				onFailure: error,
-				onException: error
-			}).send();
-		}
-	},
-
-	scriptsState: $H(),
-	
-	getLoadedScripts: function(){
-		return this.scriptsState.filter(function(state){
-			return state;
-		});
-	},
-
-	scriptLoaded: function(script){
-		this.log('loaded script: ', script);
-		this.scriptsState[script] = true;
-		this.check();
-		var loaded = this.getLoadedScripts();
-		var loadedLength = loaded.getLength();
-		var toLoad = this.scriptsState.getLength();
-		this.fireEvent('scriptLoaded', {
-			script: script,
-			totalLoaded: (loadedLength / toLoad * 100).round(),
-			currentLoaded: ((loadedLength - this.lastLoaded) / (toLoad - this.lastLoaded) * 100).round(),
-			loaded: loaded
-		});
-		if (loadedLength == toLoad) this.lastLoaded = loadedLength;
-	},
-
-	lastLoaded: 0,
-
-	check: function(){
-		var incomplete = [];
-		this.required.each(function(required){
-			var loaded = [];
-			required.scripts.each(function(script){
-				if (this.scriptsState[script]) loaded.push(script);
-			}, this);
-			if (required.onStep){
-				required.onStep({
-					percent: loaded.length / required.scripts.length * 100,
-					scripts: loaded
-				});
-			};
-			if (required.scripts.length != loaded.length) return;
-			required.callback();
-			this.required.erase(required);
-			this.fireEvent('requirementLoaded', [loaded, required]);
-		}, this);
-	}
-
-};
-
-$extend(Depender, new Events);
-$extend(Depender, new Options);
-$extend(Depender, new Log);
-
-Depender._setOptions = Depender.setOptions;
-Depender.setOptions = function(){
-	Depender._setOptions.apply(Depender, arguments);
-	if (this.options.log) Depender.enableLog();
-	return this;
-};
-/*
----
-
-script: MooTools.Lang.js
-
-description: Provides methods for localization.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Events
-- /MooTools.More
-
-provides: [MooTools.Lang]
-
-...
-*/
-
-(function(){
-
-	var data = {
-		language: 'en-US',
-		languages: {
-			'en-US': {}
-		},
-		cascades: ['en-US']
-	};
-	
-	var cascaded;
-
-	MooTools.lang = new Events();
-
-	$extend(MooTools.lang, {
-
-		setLanguage: function(lang){
-			if (!data.languages[lang]) return this;
-			data.language = lang;
-			this.load();
-			this.fireEvent('langChange', lang);
-			return this;
-		},
-
-		load: function() {
-			var langs = this.cascade(this.getCurrentLanguage());
-			cascaded = {};
-			$each(langs, function(set, setName){
-				cascaded[setName] = this.lambda(set);
-			}, this);
-		},
-
-		getCurrentLanguage: function(){
-			return data.language;
-		},
-
-		addLanguage: function(lang){
-			data.languages[lang] = data.languages[lang] || {};
-			return this;
-		},
-
-		cascade: function(lang){
-			var cascades = (data.languages[lang] || {}).cascades || [];
-			cascades.combine(data.cascades);
-			cascades.erase(lang).push(lang);
-			var langs = cascades.map(function(lng){
-				return data.languages[lng];
-			}, this);
-			return $merge.apply(this, langs);
-		},
-
-		lambda: function(set) {
-			(set || {}).get = function(key, args){
-				return $lambda(set[key]).apply(this, $splat(args));
-			};
-			return set;
-		},
-
-		get: function(set, key, args){
-			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
-		},
-
-		set: function(lang, set, members){
-			this.addLanguage(lang);
-			langData = data.languages[lang];
-			if (!langData[set]) langData[set] = {};
-			$extend(langData[set], members);
-			if (lang == this.getCurrentLanguage()){
-				this.load();
-				this.fireEvent('langChange', lang);
-			}
-			return this;
-		},
-
-		list: function(){
-			return Hash.getKeys(data.languages);
-		}
-
-	});
-
-})();/*
----
-
-script: Class.Refactor.js
-
-description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Class
-- /MooTools.More
-
-provides: [Class.refactor]
-
-...
-*/
-
-Class.refactor = function(original, refactors){
-
-	$each(refactors, function(item, name){
-		var origin = original.prototype[name];
-		if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
-			var old = this.previous;
-			this.previous = origin;
-			var value = item.apply(this, arguments);
-			this.previous = old;
-			return value;
-		}); else original.implement(name, item);
-	});
-
-	return original;
-
-};/*
----
-
-script: Class.Binds.js
-
-description: Automagically binds specified methods in a class to the instance of the class.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Class
-- /MooTools.More
-
-provides: [Class.Binds]
-
-...
-*/
-
-Class.Mutators.Binds = function(binds){
-    return binds;
-};
-
-Class.Mutators.initialize = function(initialize){
-	return function(){
-		$splat(this.Binds).each(function(name){
-			var original = this[name];
-			if (original) this[name] = original.bind(this);
-		}, this);
-		return initialize.apply(this, arguments);
-	};
-};
-/*
----
-
-script: Class.Occlude.js
-
-description: Prevents a class from being applied to a DOM element twice.
-
-license: MIT-style license.
-
-authors:
-- Aaron Newton
-
-requires: 
-- core/1.2.4/Class
-- core:1.2.4/Element
-- /MooTools.More
-
-provides: [Class.Occlude]
-
-...
-*/
-
-Class.Occlude = new Class({
-
-	occlude: function(property, element){
-		element = document.id(element || this.element);
-		var instance = element.retrieve(property || this.property);
-		if (instance && !$defined(this.occluded))
-			return this.occluded = instance;
-
-		this.occluded = false;
-		element.store(property || this.property, this);
-		return this.occluded;
-	}
-
-});/*
----
-
-script: Chain.Wait.js
-
-description: value, Adds a method to inject pauses between chained events.
-
-license: MIT-style license.
-
-authors:
-- Aaron Newton
-
-requires: 
-- core:1.2.4/Chain 
-- core:1.2.4/Element
-- core:1.2.4/Fx
-- /MooTools.More
-
-provides: [Chain.Wait]
-
-...
-*/
-
-(function(){
-
-	var wait = {
-		wait: function(duration){
-			return this.chain(function(){
-				this.callChain.delay($pick(duration, 500), this);
-			}.bind(this));
-		}
-	};
-
-	Chain.implement(wait);
-
-	if (window.Fx){
-		Fx.implement(wait);
-		['Css', 'Tween', 'Elements'].each(function(cls){
-			if (Fx[cls]) Fx[cls].implement(wait);
-		});
-	}
-
-	Element.implement({
-		chains: function(effects){
-			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
-				effect = this.get(effect);
-				if (!effect) return;
-				effect.setOptions({
-					link:'chain'
-				});
-			}, this);
-			return this;
-		},
-		pauseFx: function(duration, effect){
-			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
-			return this;
-		}
-	});
-
-})();/*
----
-
-script: Array.Extras.js
-
-description: Extends the Array native object to include useful methods to work with arrays.
-
-license: MIT-style license
-
-authors:
-- Christoph Pojer
-
-requires:
-- core:1.2.4/Array
-
-provides: [Array.Extras]
-
-...
-*/
-Array.implement({
-
-	min: function(){
-		return Math.min.apply(null, this);
-	},
-
-	max: function(){
-		return Math.max.apply(null, this);
-	},
-
-	average: function(){
-		return this.length ? this.sum() / this.length : 0;
-	},
-
-	sum: function(){
-		var result = 0, l = this.length;
-		if (l){
-			do {
-				result += this[--l];
-			} while (l);
-		}
-		return result;
-	},
-
-	unique: function(){
-		return [].combine(this);
-	}
-
-});/*
----
-
-script: Date.English.US.js
-
-description: Date messages for US English.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.English.US]
-
-...
-*/
-
-MooTools.lang.set('en-US', 'Date', {
-
-	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
-	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
-	//culture's date order: MM/DD/YYYY
-	dateOrder: ['month', 'date', 'year'],
-	shortDate: '%m/%d/%Y',
-	shortTime: '%I:%M%p',
-	AM: 'AM',
-	PM: 'PM',
-
-	/* Date.Extras */
-	ordinal: function(dayOfMonth){
-		//1st, 2nd, 3rd, etc.
-		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
-	},
-
-	lessThanMinuteAgo: 'less than a minute ago',
-	minuteAgo: 'about a minute ago',
-	minutesAgo: '{delta} minutes ago',
-	hourAgo: 'about an hour ago',
-	hoursAgo: 'about {delta} hours ago',
-	dayAgo: '1 day ago',
-	daysAgo: '{delta} days ago',
-	weekAgo: '1 week ago',
-	weeksAgo: '{delta} weeks ago',
-	monthAgo: '1 month ago',
-	monthsAgo: '{delta} months ago',
-	yearAgo: '1 year ago',
-	yearsAgo: '{delta} years ago',
-	lessThanMinuteUntil: 'less than a minute from now',
-	minuteUntil: 'about a minute from now',
-	minutesUntil: '{delta} minutes from now',
-	hourUntil: 'about an hour from now',
-	hoursUntil: 'about {delta} hours from now',
-	dayUntil: '1 day from now',
-	daysUntil: '{delta} days from now',
-	weekUntil: '1 week from now',
-	weeksUntil: '{delta} weeks from now',
-	monthUntil: '1 month from now',
-	monthsUntil: '{delta} months from now',
-	yearUntil: '1 year from now',
-	yearsUntil: '{delta} years from now'
-
-});
-/*
----
-
-script: Date.js
-
-description: Extends the Date native object to include methods useful in managing dates.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-- Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
-- Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
-- Scott Kyle - scott [at] appden.com; http://appden.com
-
-requires:
-- core:1.2.4/Array
-- core:1.2.4/String
-- core:1.2.4/Number
-- core:1.2.4/Lang
-- core:1.2.4/Date.English.US
-- /MooTools.More
-
-provides: [Date]
-
-...
-*/
-
-(function(){
-
-var Date = this.Date;
-
-if (!Date.now) Date.now = $time;
-
-Date.Methods = {
-	ms: 'Milliseconds',
-	year: 'FullYear',
-	min: 'Minutes',
-	mo: 'Month',
-	sec: 'Seconds',
-	hr: 'Hours'
-};
-
-['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
-	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
-	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
-	Date.Methods[method.toLowerCase()] = method;
-});
-
-var pad = function(what, length){
-	return new Array(length - String(what).length + 1).join('0') + what;
-};
-
-Date.implement({
-
-	set: function(prop, value){
-		switch ($type(prop)){
-			case 'object':
-				for (var p in prop) this.set(p, prop[p]);
-				break;
-			case 'string':
-				prop = prop.toLowerCase();
-				var m = Date.Methods;
-				if (m[prop]) this['set' + m[prop]](value);
-		}
-		return this;
-	},
-
-	get: function(prop){
-		prop = prop.toLowerCase();
-		var m = Date.Methods;
-		if (m[prop]) return this['get' + m[prop]]();
-		return null;
-	},
-
-	clone: function(){
-		return new Date(this.get('time'));
-	},
-
-	increment: function(interval, times){
-		interval = interval || 'day';
-		times = $pick(times, 1);
-
-		switch (interval){
-			case 'year':
-				return this.increment('month', times * 12);
-			case 'month':
-				var d = this.get('date');
-				this.set('date', 1).set('mo', this.get('mo') + times);
-				return this.set('date', d.min(this.get('lastdayofmonth')));
-			case 'week':
-				return this.increment('day', times * 7);
-			case 'day':
-				return this.set('date', this.get('date') + times);
-		}
-
-		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
-
-		return this.set('time', this.get('time') + times * Date.units[interval]());
-	},
-
-	decrement: function(interval, times){
-		return this.increment(interval, -1 * $pick(times, 1));
-	},
-
-	isLeapYear: function(){
-		return Date.isLeapYear(this.get('year'));
-	},
-
-	clearTime: function(){
-		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
-	},
-
-	diff: function(date, resolution){
-		if ($type(date) == 'string') date = Date.parse(date);
-		
-		return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
-	},
-
-	getLastDayOfMonth: function(){
-		return Date.daysInMonth(this.get('mo'), this.get('year'));
-	},
-
-	getDayOfYear: function(){
-		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) 
-			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
-	},
-
-	getWeek: function(){
-		return (this.get('dayofyear') / 7).ceil();
-	},
-	
-	getOrdinal: function(day){
-		return Date.getMsg('ordinal', day || this.get('date'));
-	},
-
-	getTimezone: function(){
-		return this.toString()
-			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
-			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
-	},
-
-	getGMTOffset: function(){
-		var off = this.get('timezoneOffset');
-		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
-	},
-
-	setAMPM: function(ampm){
-		ampm = ampm.toUpperCase();
-		var hr = this.get('hr');
-		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
-		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
-		return this;
-	},
-
-	getAMPM: function(){
-		return (this.get('hr') < 12) ? 'AM' : 'PM';
-	},
-
-	parse: function(str){
-		this.set('time', Date.parse(str));
-		return this;
-	},
-
-	isValid: function(date) {
-		return !!(date || this).valueOf();
-	},
-
-	format: function(f){
-		if (!this.isValid()) return 'invalid date';
-		f = f || '%x %X';
-		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
-		var d = this;
-		return f.replace(/%([a-z%])/gi,
-			function($0, $1){
-				switch ($1){
-					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
-					case 'A': return Date.getMsg('days')[d.get('day')];
-					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
-					case 'B': return Date.getMsg('months')[d.get('month')];
-					case 'c': return d.toString();
-					case 'd': return pad(d.get('date'), 2);
-					case 'H': return pad(d.get('hr'), 2);
-					case 'I': return ((d.get('hr') % 12) || 12);
-					case 'j': return pad(d.get('dayofyear'), 3);
-					case 'm': return pad((d.get('mo') + 1), 2);
-					case 'M': return pad(d.get('min'), 2);
-					case 'o': return d.get('ordinal');
-					case 'p': return Date.getMsg(d.get('ampm'));
-					case 'S': return pad(d.get('seconds'), 2);
-					case 'U': return pad(d.get('week'), 2);
-					case 'w': return d.get('day');
-					case 'x': return d.format(Date.getMsg('shortDate'));
-					case 'X': return d.format(Date.getMsg('shortTime'));
-					case 'y': return d.get('year').toString().substr(2);
-					case 'Y': return d.get('year');
-					case 'T': return d.get('GMTOffset');
-					case 'Z': return d.get('Timezone');
-				}
-				return $1;
-			}
-		);
-	},
-
-	toISOString: function(){
-		return this.format('iso8601');
-	}
-
-});
-
-Date.alias('toISOString', 'toJSON');
-Date.alias('diff', 'compare');
-Date.alias('format', 'strftime');
-
-var formats = {
-	db: '%Y-%m-%d %H:%M:%S',
-	compact: '%Y%m%dT%H%M%S',
-	iso8601: '%Y-%m-%dT%H:%M:%S%T',
-	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
-	'short': '%d %b %H:%M',
-	'long': '%B %d, %Y %H:%M'
-};
-
-var parsePatterns = [];
-var nativeParse = Date.parse;
-
-var parseWord = function(type, word, num){
-	var ret = -1;
-	var translated = Date.getMsg(type + 's');
-
-	switch ($type(word)){
-		case 'object':
-			ret = translated[word.get(type)];
-			break;
-		case 'number':
-			ret = translated[month - 1];
-			if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
-			break;
-		case 'string':
-			var match = translated.filter(function(name){
-				return this.test(name);
-			}, new RegExp('^' + word, 'i'));
-			if (!match.length)    throw new Error('Invalid ' + type + ' string');
-			if (match.length > 1) throw new Error('Ambiguous ' + type);
-			ret = match[0];
-	}
-
-	return (num) ? translated.indexOf(ret) : ret;
-};
-
-Date.extend({
-
-	getMsg: function(key, args) {
-		return MooTools.lang.get('Date', key, args);
-	},
-
-	units: {
-		ms: $lambda(1),
-		second: $lambda(1000),
-		minute: $lambda(60000),
-		hour: $lambda(3600000),
-		day: $lambda(86400000),
-		week: $lambda(608400000),
-		month: function(month, year){
-			var d = new Date;
-			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
-		},
-		year: function(year){
-			year = year || new Date().get('year');
-			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
-		}
-	},
-
-	daysInMonth: function(month, year){
-		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
-	},
-
-	isLeapYear: function(year){
-		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
-	},
-
-	parse: function(from){
-		var t = $type(from);
-		if (t == 'number') return new Date(from);
-		if (t != 'string') return from;
-		from = from.clean();
-		if (!from.length) return null;
-
-		var parsed;
-		parsePatterns.some(function(pattern){
-			var bits = pattern.re.exec(from);
-			return (bits) ? (parsed = pattern.handler(bits)) : false;
-		});
-
-		return parsed || new Date(nativeParse(from));
-	},
-
-	parseDay: function(day, num){
-		return parseWord('day', day, num);
-	},
-
-	parseMonth: function(month, num){
-		return parseWord('month', month, num);
-	},
-
-	parseUTC: function(value){
-		var localDate = new Date(value);
-		var utcSeconds = Date.UTC(
-			localDate.get('year'),
-			localDate.get('mo'),
-			localDate.get('date'),
-			localDate.get('hr'),
-			localDate.get('min'),
-			localDate.get('sec')
-		);
-		return new Date(utcSeconds);
-	},
-
-	orderIndex: function(unit){
-		return Date.getMsg('dateOrder').indexOf(unit) + 1;
-	},
-
-	defineFormat: function(name, format){
-		formats[name] = format;
-	},
-
-	defineFormats: function(formats){
-		for (var name in formats) Date.defineFormat(name, formats[name]);
-	},
-
-	parsePatterns: parsePatterns, // this is deprecated
-	
-	defineParser: function(pattern){
-		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
-	},
-	
-	defineParsers: function(){
-		Array.flatten(arguments).each(Date.defineParser);
-	},
-	
-	define2DigitYearStart: function(year){
-		startYear = year % 100;
-		startCentury = year - startYear;
-	}
-
-});
-
-var startCentury = 1900;
-var startYear = 70;
-
-var regexOf = function(type){
-	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
-		return name.substr(0, 3);
-	}).join('|') + ')[a-z]*');
-};
-
-var replacers = function(key){
-	switch(key){
-		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
-			return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
-		case 'X':
-			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
-	}
-	return null;
-};
-
-var keys = {
-	d: /[0-2]?[0-9]|3[01]/,
-	H: /[01]?[0-9]|2[0-3]/,
-	I: /0?[1-9]|1[0-2]/,
-	M: /[0-5]?\d/,
-	s: /\d+/,
-	o: /[a-z]*/,
-	p: /[ap]\.?m\.?/,
-	y: /\d{2}|\d{4}/,
-	Y: /\d{4}/,
-	T: /Z|[+-]\d{2}(?::?\d{2})?/
-};
-
-keys.m = keys.I;
-keys.S = keys.M;
-
-var currentLanguage;
-
-var recompile = function(language){
-	currentLanguage = language;
-	
-	keys.a = keys.A = regexOf('days');
-	keys.b = keys.B = regexOf('months');
-	
-	parsePatterns.each(function(pattern, i){
-		if (pattern.format) parsePatterns[i] = build(pattern.format);
-	});
-};
-
-var build = function(format){
-	if (!currentLanguage) return {format: format};
-	
-	var parsed = [];
-	var re = (format.source || format) // allow format to be regex
-	 .replace(/%([a-z])/gi,
-		function($0, $1){
-			return replacers($1) || $0;
-		}
-	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
-	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
-	 .replace(/%([a-z%])/gi,
-		function($0, $1){
-			var p = keys[$1];
-			if (!p) return $1;
-			parsed.push($1);
-			return '(' + p.source + ')';
-		}
-	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words
-
-	return {
-		format: format,
-		re: new RegExp('^' + re + '$', 'i'),
-		handler: function(bits){
-			bits = bits.slice(1).associate(parsed);
-			var date = new Date().clearTime();
-			if ('d' in bits) handle.call(date, 'd', 1);
-			if ('m' in bits) handle.call(date, 'm', 1);
-			for (var key in bits) handle.call(date, key, bits[key]);
-			return date;
-		}
-	};
-};
-
-var handle = function(key, value){
-	if (!value) return this;
-
-	switch(key){
-		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
-		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
-		case 'd': return this.set('date', value);
-		case 'H': case 'I': return this.set('hr', value);
-		case 'm': return this.set('mo', value - 1);
-		case 'M': return this.set('min', value);
-		case 'p': return this.set('ampm', value.replace(/\./g, ''));
-		case 'S': return this.set('sec', value);
-		case 's': return this.set('ms', ('0.' + value) * 1000);
-		case 'w': return this.set('day', value);
-		case 'Y': return this.set('year', value);
-		case 'y':
-			value = +value;
-			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
-			return this.set('year', value);
-		case 'T':
-			if (value == 'Z') value = '+00';
-			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
-			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
-			return this.set('time', this - offset * 60000);
-	}
-
-	return this;
-};
-
-Date.defineParsers(
-	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
-	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
-	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
-	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
-	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
-	'%Y %b( %d%o( %X)?)?' // Same as above with year coming first
-);
-
-MooTools.lang.addEvent('langChange', function(language){
-	if (MooTools.lang.get('Date')) recompile(language);
-}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
-
-})();/*
----
-
-script: Date.Extras.js
-
-description: Extends the Date native object to include extra methods (on top of those in Date.js).
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-- Scott Kyle
-
-requires:
-- /Date
-
-provides: [Date.Extras]
-
-...
-*/
-
-Date.implement({
-
-	timeDiffInWords: function(relative_to){
-		return Date.distanceOfTimeInWords(this, relative_to || new Date);
-	},
-
-	timeDiff: function(to, joiner){
-		if (to == null) to = new Date;
-		var delta = ((to - this) / 1000).toInt();
-		if (!delta) return '0s';
-		
-		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
-		var duration, vals = [];
-		
-		for (var step in durations){
-			if (!delta) break;
-			if ((duration = durations[step])){
-				vals.unshift((delta % duration) + step);
-				delta = (delta / duration).toInt();
-			} else {
-				vals.unshift(delta + step);
-			}
-		}
-		
-		return vals.join(joiner || ':');
-	}
-
-});
-
-Date.alias('timeDiffInWords', 'timeAgoInWords');
-
-Date.extend({
-
-	distanceOfTimeInWords: function(from, to){
-		return Date.getTimePhrase(((to - from) / 1000).toInt());
-	},
-
-	getTimePhrase: function(delta){
-		var suffix = (delta < 0) ? 'Until' : 'Ago';
-		if (delta < 0) delta *= -1;
-		
-		var units = {
-			minute: 60,
-			hour: 60,
-			day: 24,
-			week: 7,
-			month: 52 / 12,
-			year: 12,
-			eon: Infinity
-		};
-		
-		var msg = 'lessThanMinute';
-		
-		for (var unit in units){
-			var interval = units[unit];
-			if (delta < 1.5 * interval){
-				if (delta > 0.75 * interval) msg = unit;
-				break;
-			}
-			delta /= interval;
-			msg = unit + 's';
-		}
-		
-		return Date.getMsg(msg + suffix).substitute({delta: delta.round()});
-	}
-
-});
-
-
-Date.defineParsers(
-
-	{
-		// "today", "tomorrow", "yesterday"
-		re: /^(?:tod|tom|yes)/i,
-		handler: function(bits){
-			var d = new Date().clearTime();
-			switch(bits[0]){
-				case 'tom': return d.increment();
-				case 'yes': return d.decrement();
-				default: 	return d;
-			}
-		}
-	},
-
-	{
-		// "next Wednesday", "last Thursday"
-		re: /^(next|last) ([a-z]+)$/i,
-		handler: function(bits){
-			var d = new Date().clearTime();
-			var day = d.getDay();
-			var newDay = Date.parseDay(bits[2], true);
-			var addDays = newDay - day;
-			if (newDay <= day) addDays += 7;
-			if (bits[1] == 'last') addDays -= 7;
-			return d.set('date', d.getDate() + addDays);
-		}
-	}
-
-);
-/*
----
-
-script: Hash.Extras.js
-
-description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Hash.base
-- /MooTools.More
-
-provides: [Hash.Extras]
-
-...
-*/
-
-Hash.implement({
-
-	getFromPath: function(notation){
-		var source = this.getClean();
-		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
-			if (!source) return null;
-			var prop = arguments[2] || arguments[1] || arguments[0];
-			source = (prop in source) ? source[prop] : null;
-			return match;
-		});
-		return source;
-	},
-
-	cleanValues: function(method){
-		method = method || $defined;
-		this.each(function(v, k){
-			if (!method(v)) this.erase(k);
-		}, this);
-		return this;
-	},
-
-	run: function(){
-		var args = arguments;
-		this.each(function(v, k){
-			if ($type(v) == 'function') v.run(args);
-		});
-	}
-
-});/*
----
-
-script: String.Extras.js
-
-description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-- Guillermo Rauch
-
-requires:
-- core:1.2.4/String
-- core:1.2.4/$util
-- core:1.2.4/Array
-
-provides: [String.Extras]
-
-...
-*/
-
-(function(){
-  
-var special = ['À','à','Á','á','Â','â','Ã','ã','Ä','ä','Å','å','Ă','ă','Ą','ą','Ć','ć','Č','č','Ç','ç', 'Ď','ď','Đ','đ', 'È','è','É','é','Ê','ê','Ë','ë','Ě','ě','Ę','ę', 'Ğ','ğ','Ì','ì','Í','í','Î','î','Ï','ï', 'Ĺ','ĺ','Ľ','ľ','Ł','ł', 'Ñ','ñ','Ň','ň','Ń','ń','Ò','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','ő','Ř','ř','Ŕ','ŕ','Š','š','Ş','ş','Ś','ś', 'Ť','ť','Ť','ť','Ţ','ţ','Ù','ù','Ú','ú','Û','û','Ü','ü','Ů','ů', 'Ÿ','ÿ','ý','Ý','Ž','ž','Ź','ź','Ż','ż', 'Þ','þ','Ð','ð','ß','Œ','œ','Æ','æ','µ'];
-
-var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];
-
-var tidymap = {
-	"[\xa0\u2002\u2003\u2009]": " ",
-	"\xb7": "*",
-	"[\u2018\u2019]": "'",
-	"[\u201c\u201d]": '"',
-	"\u2026": "...",
-	"\u2013": "-",
-	"\u2014": "--",
-	"\uFFFD": "&raquo;"
-};
-
-var getRegForTag = function(tag, contents) {
-	tag = tag || '';
-	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
-	reg = new RegExp(regstr, "gi");
-	return reg;
-};
-
-String.implement({
-
-	standardize: function(){
-		var text = this;
-		special.each(function(ch, i){
-			text = text.replace(new RegExp(ch, 'g'), standard[i]);
-		});
-		return text;
-	},
-
-	repeat: function(times){
-		return new Array(times + 1).join(this);
-	},
-
-	pad: function(length, str, dir){
-		if (this.length >= length) return this;
-		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
-		if (!dir || dir == 'right') return this + pad;
-		if (dir == 'left') return pad + this;
-		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
-	},
-
-	getTags: function(tag, contents){
-		return this.match(getRegForTag(tag, contents)) || [];
-	},
-
-	stripTags: function(tag, contents){
-		return this.replace(getRegForTag(tag, contents), '');
-	},
-
-	tidy: function(){
-		var txt = this.toString();
-		$each(tidymap, function(value, key){
-			txt = txt.replace(new RegExp(key, 'g'), value);
-		});
-		return txt;
-	}
-
-});
-
-})();/*
----
-
-script: String.QueryString.js
-
-description: Methods for dealing with URI query strings.
-
-license: MIT-style license
-
-authors:
-- Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
-
-requires:
-- core:1.2.4/Array
-- core:1.2.4/String
-- /MooTools.More
-
-provides: [String.QueryString]
-
-...
-*/
-
-String.implement({
-
-	parseQueryString: function(){
-		var vars = this.split(/[&;]/), res = {};
-		if (vars.length) vars.each(function(val){
-			var index = val.indexOf('='),
-				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
-				value = decodeURIComponent(val.substr(index + 1)),
-				obj = res;
-			keys.each(function(key, i){
-				var current = obj[key];
-				if(i < keys.length - 1)
-					obj = obj[key] = current || {};
-				else if($type(current) == 'array')
-					current.push(value);
-				else
-					obj[key] = $defined(current) ? [current, value] : value;
-			});
-		});
-		return res;
-	},
-
-	cleanQueryString: function(method){
-		return this.split('&').filter(function(val){
-			var index = val.indexOf('='),
-			key = index < 0 ? '' : val.substr(0, index),
-			value = val.substr(index + 1);
-			return method ? method.run([key, value]) : $chk(value);
-		}).join('&');
-	}
-
-});/*
----
-
-script: URI.js
-
-description: Provides methods useful in managing the window location and uris.
-
-license: MIT-style license
-
-authors:
-- Sebastian Markbåge
-- Aaron Newton
-
-requires:
-- core:1.2.4/Selectors
-- /String.QueryString
-
-provides: URI
-
-...
-*/
-
-var URI = new Class({
-
-	Implements: Options,
-
-	options: {
-		/*base: false*/
-	},
-
-	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
-	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
-	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
-
-	initialize: function(uri, options){
-		this.setOptions(options);
-		var base = this.options.base || URI.base;
-		if(!uri) uri = base;
-		
-		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
-		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
-	},
-
-	parse: function(value, base){
-		var bits = value.match(this.regex);
-		if (!bits) return false;
-		bits.shift();
-		return this.merge(bits.associate(this.parts), base);
-	},
-
-	merge: function(bits, base){
-		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
-		if (base){
-			this.parts.every(function(part){
-				if (bits[part]) return false;
-				bits[part] = base[part] || '';
-				return true;
-			});
-		}
-		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
-		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
-		return bits;
-	},
-
-	parseDirectory: function(directory, baseDirectory) {
-		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
-		if (!directory.test(URI.regs.directoryDot)) return directory;
-		var result = [];
-		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
-			if (dir == '..' && result.length > 0) result.pop();
-			else if (dir != '.') result.push(dir);
-		});
-		return result.join('/') + '/';
-	},
-
-	combine: function(bits){
-		return bits.value || bits.scheme + '://' +
-			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
-			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
-			(bits.directory || '/') + (bits.file || '') +
-			(bits.query ? '?' + bits.query : '') +
-			(bits.fragment ? '#' + bits.fragment : '');
-	},
-
-	set: function(part, value, base){
-		if (part == 'value'){
-			var scheme = value.match(URI.regs.scheme);
-			if (scheme) scheme = scheme[1];
-			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
-			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
-		} else if (part == 'data') {
-			this.setData(value);
-		} else {
-			this.parsed[part] = value;
-		}
-		return this;
-	},
-
-	get: function(part, base){
-		switch(part){
-			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
-			case 'data' : return this.getData();
-		}
-		return this.parsed[part] || '';
-	},
-
-	go: function(){
-		document.location.href = this.toString();
-	},
-
-	toURI: function(){
-		return this;
-	},
-
-	getData: function(key, part){
-		var qs = this.get(part || 'query');
-		if (!$chk(qs)) return key ? null : {};
-		var obj = qs.parseQueryString();
-		return key ? obj[key] : obj;
-	},
-
-	setData: function(values, merge, part){
-		if (typeof values == 'string'){
-			values = this.getData();
-			values[arguments[0]] = arguments[1];
-		} else if (merge) {
-			values = $merge(this.getData(), values);
-		}
-		return this.set(part || 'query', Hash.toQueryString(values));
-	},
-
-	clearData: function(part){
-		return this.set(part || 'query', '');
-	}
-
-});
-
-URI.prototype.toString = URI.prototype.valueOf = function(){
-	return this.get('value');
-};
-
-URI.regs = {
-	endSlash: /\/$/,
-	scheme: /^(\w+):/,
-	directoryDot: /\.\/|\.$/
-};
-
-URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
-
-String.implement({
-
-	toURI: function(options){
-		return new URI(this, options);
-	}
-
-});/*
----
-
-script: URI.Relative.js
-
-description: Extends the URI class to add methods for computing relative and absolute urls.
-
-license: MIT-style license
-
-authors:
-- Sebastian Markbåge
-
-
-requires:
-- /Class.refactor
-- /URI
-
-provides: [URI.Relative]
-
-...
-*/
-
-URI = Class.refactor(URI, {
-
-	combine: function(bits, base){
-		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
-			return this.previous.apply(this, arguments);
-		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
-
-		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
-
-		var baseDir = base.directory.split('/'),
-			relDir = bits.directory.split('/'),
-			path = '',
-			offset;
-
-		var i = 0;
-		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
-		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
-		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
-
-		return (path || (bits.file ? '' : './')) + end;
-	},
-
-	toAbsolute: function(base){
-		base = new URI(base);
-		if (base) base.set('directory', '').set('file', '');
-		return this.toRelative(base);
-	},
-
-	toRelative: function(base){
-		return this.get('value', new URI(base));
-	}
-
-});/*
----
-
-script: Element.Forms.js
-
-description: Extends the Element native object to include methods useful in managing inputs.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element
-- /MooTools.More
-
-provides: [Element.Forms]
-
-...
-*/
-
-Element.implement({
-
-	tidy: function(){
-		this.set('value', this.get('value').tidy());
-	},
-
-	getTextInRange: function(start, end){
-		return this.get('value').substring(start, end);
-	},
-
-	getSelectedText: function(){
-		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
-		return document.selection.createRange().text;
-	},
-
-	getSelectedRange: function() {
-		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
-		var pos = {start: 0, end: 0};
-		var range = this.getDocument().selection.createRange();
-		if (!range || range.parentElement() != this) return pos;
-		var dup = range.duplicate();
-		if (this.type == 'text') {
-			pos.start = 0 - dup.moveStart('character', -100000);
-			pos.end = pos.start + range.text.length;
-		} else {
-			var value = this.get('value');
-			var offset = value.length;
-			dup.moveToElementText(this);
-			dup.setEndPoint('StartToEnd', range);
-			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
-			pos.end = offset - dup.text.length;
-			dup.setEndPoint('StartToStart', range);
-			pos.start = offset - dup.text.length;
-		}
-		return pos;
-	},
-
-	getSelectionStart: function(){
-		return this.getSelectedRange().start;
-	},
-
-	getSelectionEnd: function(){
-		return this.getSelectedRange().end;
-	},
-
-	setCaretPosition: function(pos){
-		if (pos == 'end') pos = this.get('value').length;
-		this.selectRange(pos, pos);
-		return this;
-	},
-
-	getCaretPosition: function(){
-		return this.getSelectedRange().start;
-	},
-
-	selectRange: function(start, end){
-		if (this.setSelectionRange) {
-			this.focus();
-			this.setSelectionRange(start, end);
-		} else {
-			var value = this.get('value');
-			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
-			start = value.substr(0, start).replace(/\r/g, '').length;
-			var range = this.createTextRange();
-			range.collapse(true);
-			range.moveEnd('character', start + diff);
-			range.moveStart('character', start);
-			range.select();
-		}
-		return this;
-	},
-
-	insertAtCursor: function(value, select){
-		var pos = this.getSelectedRange();
-		var text = this.get('value');
-		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
-		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
-		else this.setCaretPosition(pos.start + value.length);
-		return this;
-	},
-
-	insertAroundCursor: function(options, select){
-		options = $extend({
-			before: '',
-			defaultMiddle: '',
-			after: ''
-		}, options);
-		var value = this.getSelectedText() || options.defaultMiddle;
-		var pos = this.getSelectedRange();
-		var text = this.get('value');
-		if (pos.start == pos.end){
-			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
-			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
-		} else {
-			var current = text.substring(pos.start, pos.end);
-			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
-			var selStart = pos.start + options.before.length;
-			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
-			else this.setCaretPosition(selStart + text.length);
-		}
-		return this;
-	}
-
-});/*
----
-
-script: Elements.From.js
-
-description: Returns a collection of elements from a string of html.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element
-- /MooTools.More
-
-provides: [Elements.from]
-
-...
-*/
-
-Elements.from = function(text, excludeScripts){
-	if ($pick(excludeScripts, true)) text = text.stripScripts();
-
-	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
-
-	if (match){
-		container = new Element('table');
-		var tag = match[1].toLowerCase();
-		if (['td', 'th', 'tr'].contains(tag)){
-			container = new Element('tbody').inject(container);
-			if (tag != 'tr') container = new Element('tr').inject(container);
-		}
-	}
-
-	return (container || new Element('div')).set('html', text).getChildren();
-};/*
----
-
-script: Element.Delegation.js
-
-description: Extends the Element native object to include the delegate method for more efficient event management.
-
-credits:
-- "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-- Daniel Steigerwald
-
-requires:
-- core:1.2.4/Element.Event
-- core:1.2.4/Selectors
-- /MooTools.More
-
-provides: [Element.Delegation]
-
-...
-*/
-(function(){
-	
-	var match = /(.*?):relay\(([^)]+)\)$/,
-		combinators = /[+>~\s]/,
-		splitType = function(type){
-			var bits = type.match(match);
-			return !bits ? {event: type} : {
-				event: bits[1],
-				selector: bits[2]
-			};
-		},
-		check = function(e, selector){
-			var t = e.target;
-			if (combinators.test(selector = selector.trim())){
-				var els = this.getElements(selector);
-				for (var i = els.length; i--; ){
-					var el = els[i];
-					if (t == el || el.hasChild(t)) return el;
-				}
-			} else {
-				for ( ; t && t != this; t = t.parentNode){
-					if (Element.match(t, selector)) return document.id(t);
-				}
-			}
-			return null;
-		};
-
-	var oldAddEvent = Element.prototype.addEvent,
-		oldRemoveEvent = Element.prototype.removeEvent;
-		
-	Element.implement({
-
-		addEvent: function(type, fn){
-			var splitted = splitType(type);
-			if (splitted.selector){
-				var monitors = this.retrieve('$moo:delegateMonitors', {});
-				if (!monitors[type]){
-					var monitor = function(e){
-						var el = check.call(this, e, splitted.selector);
-						if (el) this.fireEvent(type, [e, el], 0, el);
-					}.bind(this);
-					monitors[type] = monitor;
-					oldAddEvent.call(this, splitted.event, monitor);
-				}
-			}
-			return oldAddEvent.apply(this, arguments);
-		},
-
-		removeEvent: function(type, fn){
-			var splitted = splitType(type);
-			if (splitted.selector){
-				var events = this.retrieve('events');
-				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
-
-				if (fn) oldRemoveEvent.apply(this, [type, fn]);
-				else oldRemoveEvent.apply(this, type);
-
-				events = this.retrieve('events');
-				if (events && events[type] && events[type].length == 0){
-					var monitors = this.retrieve('$moo:delegateMonitors', {});
-					oldRemoveEvent.apply(this, [splitted.event, monitors[type]]);
-					delete monitors[type];
-				}
-				return this;
-			}
-
-			return oldRemoveEvent.apply(this, arguments);
-		},
-
-		fireEvent: function(type, args, delay, bind){
-			var events = this.retrieve('events');
-			if (!events || !events[type]) return this;
-			events[type].keys.each(function(fn){
-				fn.create({bind: bind || this, delay: delay, arguments: args})();
-			}, this);
-			return this;
-		}
-
-	});
-
-})();/*
----
-
-script: Element.Measure.js
-
-description: Extends the Element native object to include methods useful in measuring dimensions.
-
-credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Style
-- core:1.2.4/Element.Dimensions
-- /MooTools.More
-
-provides: [Element.Measure]
-
-...
-*/
-
-Element.implement({
-
-	measure: function(fn){
-		var vis = function(el) {
-			return !!(!el || el.offsetHeight || el.offsetWidth);
-		};
-		if (vis(this)) return fn.apply(this);
-		var parent = this.getParent(),
-			restorers = [],
-			toMeasure = []; 
-		while (!vis(parent) && parent != document.body) {
-			toMeasure.push(parent.expose());
-			parent = parent.getParent();
-		}
-		var restore = this.expose();
-		var result = fn.apply(this);
-		restore();
-		toMeasure.each(function(restore){
-			restore();
-		});
-		return result;
-	},
-
-	expose: function(){
-		if (this.getStyle('display') != 'none') return $empty;
-		var before = this.style.cssText;
-		this.setStyles({
-			display: 'block',
-			position: 'absolute',
-			visibility: 'hidden'
-		});
-		return function(){
-			this.style.cssText = before;
-		}.bind(this);
-	},
-
-	getDimensions: function(options){
-		options = $merge({computeSize: false},options);
-		var dim = {};
-		var getSize = function(el, options){
-			return (options.computeSize)?el.getComputedSize(options):el.getSize();
-		};
-		var parent = this.getParent('body');
-		if (parent && this.getStyle('display') == 'none'){
-			dim = this.measure(function(){
-				return getSize(this, options);
-			});
-		} else if (parent){
-			try { //safari sometimes crashes here, so catch it
-				dim = getSize(this, options);
-			}catch(e){}
-		} else {
-			dim = {x: 0, y: 0};
-		}
-		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
-	},
-
-	getComputedSize: function(options){
-		options = $merge({
-			styles: ['padding','border'],
-			plains: {
-				height: ['top','bottom'],
-				width: ['left','right']
-			},
-			mode: 'both'
-		}, options);
-		var size = {width: 0,height: 0};
-		switch (options.mode){
-			case 'vertical':
-				delete size.width;
-				delete options.plains.width;
-				break;
-			case 'horizontal':
-				delete size.height;
-				delete options.plains.height;
-				break;
-		}
-		var getStyles = [];
-		//this function might be useful in other places; perhaps it should be outside this function?
-		$each(options.plains, function(plain, key){
-			plain.each(function(edge){
-				options.styles.each(function(style){
-					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
-				});
-			});
-		});
-		var styles = {};
-		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
-		var subtracted = [];
-		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
-			var capitalized = key.capitalize();
-			size['total' + capitalized] = size['computed' + capitalized] = 0;
-			plain.each(function(edge){ //top, left, right, bottom
-				size['computed' + edge.capitalize()] = 0;
-				getStyles.each(function(style, i){ //padding, border, etc.
-					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
-					if (style.test(edge)){
-						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
-						size['total' + capitalized] = size['total' + capitalized] + styles[style];
-						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
-					}
-					//if width != width (so, padding-left, for instance), then subtract that from the total
-					if (style.test(edge) && key != style &&
-						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
-						subtracted.push(style);
-						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
-					}
-				});
-			});
-		});
-
-		['Width', 'Height'].each(function(value){
-			var lower = value.toLowerCase();
-			if(!$chk(size[lower])) return;
-
-			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
-			size['total' + value] = size[lower] + size['total' + value];
-			delete size['computed' + value];
-		}, this);
-
-		return $extend(styles, size);
-	}
-
-});/*
----
-
-script: Element.Pin.js
-
-description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Dimensions
-- core:1.2.4/Element.Style
-- /MooTools.More
-
-provides: [Element.Pin]
-
-...
-*/
-
-(function(){
-	var supportsPositionFixed = false;
-	window.addEvent('domready', function(){
-		var test = new Element('div').setStyles({
-			position: 'fixed',
-			top: 0,
-			right: 0
-		}).inject(document.body);
-		supportsPositionFixed = (test.offsetTop === 0);
-		test.dispose();
-	});
-
-	Element.implement({
-
-		pin: function(enable){
-			if (this.getStyle('display') == 'none') return null;
-			
-			var p,
-					scroll = window.getScroll();
-			if (enable !== false){
-				p = this.getPosition();
-				if (!this.retrieve('pinned')){
-					var pos = {
-						top: p.y - scroll.y,
-						left: p.x - scroll.x
-					};
-					if (supportsPositionFixed){
-						this.setStyle('position', 'fixed').setStyles(pos);
-					} else {
-						this.store('pinnedByJS', true);
-						this.setStyles({
-							position: 'absolute',
-							top: p.y,
-							left: p.x
-						}).addClass('isPinned');
-						this.store('scrollFixer', (function(){
-							if (this.retrieve('pinned'))
-								var scroll = window.getScroll();
-								this.setStyles({
-									top: pos.top.toInt() + scroll.y,
-									left: pos.left.toInt() + scroll.x
-								});
-						}).bind(this));
-						window.addEvent('scroll', this.retrieve('scrollFixer'));
-					}
-					this.store('pinned', true);
-				}
-			} else {
-				var op;
-				if (!Browser.Engine.trident){
-					var parent = this.getParent();
-					op = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
-				}
-				p = this.getPosition(op);
-				this.store('pinned', false);
-				var reposition;
-				if (supportsPositionFixed && !this.retrieve('pinnedByJS')){
-					reposition = {
-						top: p.y + scroll.y,
-						left: p.x + scroll.x
-					};
-				} else {
-					this.store('pinnedByJS', false);
-					window.removeEvent('scroll', this.retrieve('scrollFixer'));
-					reposition = {
-						top: p.y,
-						left: p.x
-					};
-				}
-				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
-			}
-			return this;
-		},
-
-		unpin: function(){
-			return this.pin(false);
-		},
-
-		togglepin: function(){
-			this.pin(!this.retrieve('pinned'));
-		}
-
-	});
-
-})();/*
----
-
-script: Element.Position.js
-
-description: Extends the Element native object to include methods useful positioning elements relative to others.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Dimensions
-- /Element.Measure
-
-provides: [Elements.Position]
-
-...
-*/
-
-(function(){
-
-var original = Element.prototype.position;
-
-Element.implement({
-
-	position: function(options){
-		//call original position if the options are x/y values
-		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
-		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
-		options = $merge({
-			// minimum: { x: 0, y: 0 },
-			// maximum: { x: 0, y: 0},
-			relativeTo: document.body,
-			position: {
-				x: 'center', //left, center, right
-				y: 'center' //top, center, bottom
-			},
-			edge: false,
-			offset: {x: 0, y: 0},
-			returnPos: false,
-			relFixedPosition: false,
-			ignoreMargins: false,
-			ignoreScroll: false,
-			allowNegative: false
-		}, options);
-		//compute the offset of the parent positioned element if this element is in one
-		var parentOffset = {x: 0, y: 0}, 
-				parentPositioned = false;
-		/* dollar around getOffsetParent should not be necessary, but as it does not return
-		 * a mootools extended element in IE, an error occurs on the call to expose. See:
-		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
-		var offsetParent = this.measure(function(){
-			return document.id(this.getOffsetParent());
-		});
-		if (offsetParent && offsetParent != this.getDocument().body){
-			parentOffset = offsetParent.measure(function(){
-				return this.getPosition();
-			});
-			parentPositioned = offsetParent != document.id(options.relativeTo);
-			options.offset.x = options.offset.x - parentOffset.x;
-			options.offset.y = options.offset.y - parentOffset.y;
-		}
-		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
-		//topRight, topLeft, centerTop, centerBottom, center
-		var fixValue = function(option){
-			if ($type(option) != 'string') return option;
-			option = option.toLowerCase();
-			var val = {};
-			if (option.test('left')) val.x = 'left';
-			else if (option.test('right')) val.x = 'right';
-			else val.x = 'center';
-			if (option.test('upper') || option.test('top')) val.y = 'top';
-			else if (option.test('bottom')) val.y = 'bottom';
-			else val.y = 'center';
-			return val;
-		};
-		options.edge = fixValue(options.edge);
-		options.position = fixValue(options.position);
-		if (!options.edge){
-			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
-			else options.edge = {x:'left', y:'top'};
-		}
-
-		this.setStyle('position', 'absolute');
-		var rel = document.id(options.relativeTo) || document.body,
-				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
-				top = calc.y, left = calc.x;
-
-		var scrolls = rel.getScrolls();
-		top += scrolls.y;
-		left += scrolls.x;
-
-		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
-		var pos = {},
-				prefY = options.offset.y,
-				prefX = options.offset.x,
-				winSize = window.getSize();
-		switch(options.position.x){
-			case 'left':
-				pos.x = left + prefX;
-				break;
-			case 'right':
-				pos.x = left + prefX + rel.offsetWidth;
-				break;
-			default: //center
-				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
-				break;
-		}
-		switch(options.position.y){
-			case 'top':
-				pos.y = top + prefY;
-				break;
-			case 'bottom':
-				pos.y = top + prefY + rel.offsetHeight;
-				break;
-			default: //center
-				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
-				break;
-		}
-		if (options.edge){
-			var edgeOffset = {};
-
-			switch(options.edge.x){
-				case 'left':
-					edgeOffset.x = 0;
-					break;
-				case 'right':
-					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
-					break;
-				default: //center
-					edgeOffset.x = -(dim.totalWidth/2);
-					break;
-			}
-			switch(options.edge.y){
-				case 'top':
-					edgeOffset.y = 0;
-					break;
-				case 'bottom':
-					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
-					break;
-				default: //center
-					edgeOffset.y = -(dim.totalHeight/2);
-					break;
-			}
-			pos.x += edgeOffset.x;
-			pos.y += edgeOffset.y;
-		}
-		pos = {
-			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
-			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
-		};
-		var xy = {left: 'x', top: 'y'};
-		['minimum', 'maximum'].each(function(minmax) {
-			['left', 'top'].each(function(lr) {
-				var val = options[minmax] ? options[minmax][xy[lr]] : null;
-				if (val != null && pos[lr] < val) pos[lr] = val;
-			});
-		});
-		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
-			var winScroll = window.getScroll();
-			pos.top+= winScroll.y;
-			pos.left+= winScroll.x;
-		}
-		if (options.ignoreScroll) {
-			var relScroll = rel.getScroll();
-			pos.top-= relScroll.y;
-			pos.left-= relScroll.x;
-		}
-		if (options.ignoreMargins) {
-			pos.left += (
-				options.edge.x == 'right' ? dim['margin-right'] : 
-				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) : 
-					- dim['margin-left']
-			);
-			pos.top += (
-				options.edge.y == 'bottom' ? dim['margin-bottom'] : 
-				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) : 
-					- dim['margin-top']
-			);
-		}
-		pos.left = Math.ceil(pos.left);
-		pos.top = Math.ceil(pos.top);
-		if (options.returnPos) return pos;
-		else this.setStyles(pos);
-		return this;
-	}
-
-});
-
-})();/*
----
-
-script: Element.Shortcuts.js
-
-description: Extends the Element native object to include some shortcut methods.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Style
-- /MooTools.More
-
-provides: [Element.Shortcuts]
-
-...
-*/
-
-Element.implement({
-
-	isDisplayed: function(){
-		return this.getStyle('display') != 'none';
-	},
-
-	isVisible: function(){
-		var w = this.offsetWidth,
-			h = this.offsetHeight;
-		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
-	},
-
-	toggle: function(){
-		return this[this.isDisplayed() ? 'hide' : 'show']();
-	},
-
-	hide: function(){
-		var d;
-		try {
-			// IE fails here if the element is not in the dom
-			if ((d = this.getStyle('display')) == 'none') d = null;
-		} catch(e){}
-		
-		return this.store('originalDisplay', d || 'block').setStyle('display', 'none');
-	},
-
-	show: function(display){
-		return this.setStyle('display', display || this.retrieve('originalDisplay') || 'block');
-	},
-
-	swapClass: function(remove, add){
-		return this.removeClass(remove).addClass(add);
-	}
-
-});
-/*
----
-
-script: IframeShim.js
-
-description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Style
-- core:1.2.4/Options Events
-- /Element.Position
-- /Class.Occlude
-
-provides: [IframeShim]
-
-...
-*/
-
-var IframeShim = new Class({
-
-	Implements: [Options, Events, Class.Occlude],
-
-	options: {
-		className: 'iframeShim',
-		src: 'javascript:false;document.write("");',
-		display: false,
-		zIndex: null,
-		margin: 0,
-		offset: {x: 0, y: 0},
-		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
-	},
-
-	property: 'IframeShim',
-
-	initialize: function(element, options){
-		this.element = document.id(element);
-		if (this.occlude()) return this.occluded;
-		this.setOptions(options);
-		this.makeShim();
-		return this;
-	},
-
-	makeShim: function(){
-		if(this.options.browsers){
-			var zIndex = this.element.getStyle('zIndex').toInt();
-
-			if (!zIndex){
-				zIndex = 1;
-				var pos = this.element.getStyle('position');
-				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
-				this.element.setStyle('zIndex', zIndex);
-			}
-			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
-			if (zIndex < 0) zIndex = 1;
-			this.shim = new Element('iframe', {
-				src: this.options.src,
-				scrolling: 'no',
-				frameborder: 0,
-				styles: {
-					zIndex: zIndex,
-					position: 'absolute',
-					border: 'none',
-					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
-				},
-				'class': this.options.className
-			}).store('IframeShim', this);
-			var inject = (function(){
-				this.shim.inject(this.element, 'after');
-				this[this.options.display ? 'show' : 'hide']();
-				this.fireEvent('inject');
-			}).bind(this);
-			if (IframeShim.ready) window.addEvent('load', inject);
-			else inject();
-		} else {
-			this.position = this.hide = this.show = this.dispose = $lambda(this);
-		}
-	},
-
-	position: function(){
-		if (!IframeShim.ready || !this.shim) return this;
-		var size = this.element.measure(function(){ 
-			return this.getSize(); 
-		});
-		if (this.options.margin != undefined){
-			size.x = size.x - (this.options.margin * 2);
-			size.y = size.y - (this.options.margin * 2);
-			this.options.offset.x += this.options.margin;
-			this.options.offset.y += this.options.margin;
-		}
-		this.shim.set({width: size.x, height: size.y}).position({
-			relativeTo: this.element,
-			offset: this.options.offset
-		});
-		return this;
-	},
-
-	hide: function(){
-		if (this.shim) this.shim.setStyle('display', 'none');
-		return this;
-	},
-
-	show: function(){
-		if (this.shim) this.shim.setStyle('display', 'block');
-		return this.position();
-	},
-
-	dispose: function(){
-		if (this.shim) this.shim.dispose();
-		return this;
-	},
-
-	destroy: function(){
-		if (this.shim) this.shim.destroy();
-		return this;
-	}
-
-});
-
-window.addEvent('load', function(){
-	IframeShim.ready = true;
-});/*
----
-
-script: Mask.js
-
-description: Creates a mask element to cover another.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Options
-- core:1.2.4/Events
-- core:1.2.4/Element.Event
-- /Class.Binds
-- /Element.Position
-- /IframeShim
-
-provides: [Mask]
-
-...
-*/
-
-var Mask = new Class({
-
-	Implements: [Options, Events],
-
-	Binds: ['resize'],
-
-	options: {
-		// onShow: $empty,
-		// onHide: $empty,
-		// onDestroy: $empty,
-		// onClick: $empty,
-		//inject: {
-		//  where: 'after',
-		//  target: null,
-		//},
-		// hideOnClick: false,
-		// id: null,
-		// destroyOnHide: false,
-		style: {},
-		'class': 'mask',
-		maskMargins: false,
-		useIframeShim: true
-	},
-
-	initialize: function(target, options){
-		this.target = document.id(target) || document.body;
-		this.target.store('mask', this);
-		this.setOptions(options);
-		this.render();
-		this.inject();
-	},
-	
-	render: function() {
-		this.element = new Element('div', {
-			'class': this.options['class'],
-			id: this.options.id || 'mask-' + $time(),
-			styles: $merge(this.options.style, {
-				display: 'none'
-			}),
-			events: {
-				click: function(){
-					this.fireEvent('click');
-					if (this.options.hideOnClick) this.hide();
-				}.bind(this)
-			}
-		});
-		this.hidden = true;
-	},
-
-	toElement: function(){
-		return this.element;
-	},
-
-	inject: function(target, where){
-		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
-		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
-		this.element.inject(target, where);
-		if (this.options.useIframeShim) {
-			this.shim = new IframeShim(this.element);
-			this.addEvents({
-				show: this.shim.show.bind(this.shim),
-				hide: this.shim.hide.bind(this.shim),
-				destroy: this.shim.destroy.bind(this.shim)
-			});
-		}
-	},
-
-	position: function(){
-		this.resize(this.options.width, this.options.height);
-		this.element.position({
-			relativeTo: this.target,
-			position: 'topLeft',
-			ignoreMargins: !this.options.maskMargins,
-			ignoreScroll: this.target == document.body
-		});
-		return this;
-	},
-
-	resize: function(x, y){
-		var opt = {
-			styles: ['padding', 'border']
-		};
-		if (this.options.maskMargins) opt.styles.push('margin');
-		var dim = this.target.getComputedSize(opt);
-		if (this.target == document.body) {
-			var win = window.getSize();
-			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
-			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
-		}
-		this.element.setStyles({
-			width: $pick(x, dim.totalWidth, dim.x),
-			height: $pick(y, dim.totalHeight, dim.y)
-		});
-		return this;
-	},
-
-	show: function(){
-		if (!this.hidden) return this;
-		this.target.addEvent('resize', this.resize);
-		if (this.target != document.body) document.id(document.body).addEvent('resize', this.resize);
-		this.position();
-		this.showMask.apply(this, arguments);
-		return this;
-	},
-
-	showMask: function(){
-		this.element.setStyle('display', 'block');
-		this.hidden = false;
-		this.fireEvent('show');
-	},
-
-	hide: function(){
-		if (this.hidden) return this;
-		this.target.removeEvent('resize', this.resize);
-		this.hideMask.apply(this, arguments);
-		if (this.options.destroyOnHide) return this.destroy();
-		return this;
-	},
-
-	hideMask: function(){
-		this.element.setStyle('display', 'none');
-		this.hidden = true;
-		this.fireEvent('hide');
-	},
-
-	toggle: function(){
-		this[this.hidden ? 'show' : 'hide']();
-	},
-
-	destroy: function(){
-		this.hide();
-		this.element.destroy();
-		this.fireEvent('destroy');
-		this.target.eliminate('mask');
-	}
-
-});
-
-Element.Properties.mask = {
-
-	set: function(options){
-		var mask = this.retrieve('mask');
-		return this.eliminate('mask').store('mask:options', options);
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('mask')){
-			if (this.retrieve('mask')) this.retrieve('mask').destroy();
-			if (options || !this.retrieve('mask:options')) this.set('mask', options);
-			this.store('mask', new Mask(this, this.retrieve('mask:options')));
-		}
-		return this.retrieve('mask');
-	}
-
-};
-
-Element.implement({
-
-	mask: function(options){
-		this.get('mask', options).show();
-		return this;
-	},
-
-	unmask: function(){
-		this.get('mask').hide();
-		return this;
-	}
-
-});/*
----
-
-script: Spinner.js
-
-description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Fx.Tween
-- /Class.refactor
-- /Mask
-
-provides: [Spinner]
-
-...
-*/
-
-var Spinner = new Class({
-
-	Extends: Mask,
-
-	options: {
-		/*message: false,*/
-		'class':'spinner',
-		containerPosition: {},
-		content: {
-			'class':'spinner-content'
-		},
-		messageContainer: {
-			'class':'spinner-msg'
-		},
-		img: {
-			'class':'spinner-img'
-		},
-		fxOptions: {
-			link: 'chain'
-		}
-	},
-
-	initialize: function(){
-		this.parent.apply(this, arguments);
-		this.target.store('spinner', this);
-
-		//add this to events for when noFx is true; parent methods handle hide/show
-		var deactivate = function(){ this.active = false; }.bind(this);
-		this.addEvents({
-			hide: deactivate,
-			show: deactivate
-		});
-	},
-
-	render: function(){
-		this.parent();
-		this.element.set('id', this.options.id || 'spinner-'+$time());
-		this.content = document.id(this.options.content) || new Element('div', this.options.content);
-		this.content.inject(this.element);
-		if (this.options.message) {
-			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
-			this.msg.inject(this.content);
-		}
-		if (this.options.img) {
-			this.img = document.id(this.options.img) || new Element('div', this.options.img);
-			this.img.inject(this.content);
-		}
-		this.element.set('tween', this.options.fxOptions);
-	},
-
-	show: function(noFx){
-		if (this.active) return this.chain(this.show.bind(this));
-		if (!this.hidden) {
-			this.callChain.delay(20, this);
-			return this;
-		}
-		this.active = true;
-		return this.parent(noFx);
-	},
-
-	showMask: function(noFx){
-		var pos = function(){
-			this.content.position($merge({
-				relativeTo: this.element
-			}, this.options.containerPosition));
-		}.bind(this);
-		if (noFx) {
-			this.parent();
-			pos();
-		} else {
-			this.element.setStyles({
-				display: 'block',
-				opacity: 0
-			}).tween('opacity', this.options.style.opacity || 0.9);
-			pos();
-			this.hidden = false;
-			this.fireEvent('show');
-			this.callChain();
-		}
-	},
-
-	hide: function(noFx){
-		if (this.active) return this.chain(this.hide.bind(this));
-		if (this.hidden) {
-			this.callChain.delay(20, this);
-			return this;
-		}
-		this.active = true;
-		return this.parent();
-	},
-
-	hideMask: function(noFx){
-		if (noFx) return this.parent();
-		this.element.tween('opacity', 0).get('tween').chain(function(){
-			this.element.setStyle('display', 'none');
-			this.hidden = true;
-			this.fireEvent('hide');
-			this.callChain();
-		}.bind(this));
-	},
-
-	destroy: function(){
-		this.content.destroy();
-		this.parent();
-		this.target.eliminate('spinner');
-	}
-
-});
-
-Spinner.implement(new Chain);
-
-if (window.Request) {
-	Request = Class.refactor(Request, {
-		options: {
-			useSpinner: false,
-			spinnerOptions: {},
-			spinnerTarget: false
-		},
-		initialize: function(options){
-			this._send = this.send;
-			this.send = function(options){
-				if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
-				else this._send(options);
-				return this;
-			};
-			this.previous(options);
-			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
-			if (this.options.useSpinner && update) {
-				this.spinner = update.get('spinner', this.options.spinnerOptions);
-				['onComplete', 'onException', 'onCancel'].each(function(event){
-					this.addEvent(event, this.spinner.hide.bind(this.spinner));
-				}, this);
-			}
-		}
-	});
-}
-
-Element.Properties.spinner = {
-
-	set: function(options){
-		var spinner = this.retrieve('spinner');
-		return this.eliminate('spinner').store('spinner:options', options);
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('spinner')){
-			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
-			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
-			new Spinner(this, this.retrieve('spinner:options'));
-		}
-		return this.retrieve('spinner');
-	}
-
-};
-
-Element.implement({
-
-	spin: function(options){
-		this.get('spinner', options).show();
-		return this;
-	},
-
-	unspin: function(){
-		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
-		this.get('spinner', opt.options).hide(opt.callback);
-		return this;
-	}
-
-});/*
----
-
-script: Form.Request.js
-
-description: Handles the basic functionality of submitting a form and updating a dom element with the result.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Event
-- core:1.2.4/Request.HTML
-- /Class.Binds
-- /Class.Occlude
-- /Spinner
-- /String.QueryString
-
-provides: [Form.Request]
-
-...
-*/
-
-if (!window.Form) window.Form = {};
-
-(function(){
-
-	Form.Request = new Class({
-
-		Binds: ['onSubmit', 'onFormValidate'],
-
-		Implements: [Options, Events, Class.Occlude],
-
-		options: {
-			//onFailure: $empty,
-			//onSuccess: #empty, //aliased to onComplete,
-			//onSend: $empty
-			requestOptions: {
-				evalScripts: true,
-				useSpinner: true,
-				emulation: false,
-				link: 'ignore'
-			},
-			extraData: {},
-			resetForm: true
-		},
-
-		property: 'form.request',
-
-		initialize: function(form, update, options) {
-			this.element = document.id(form);
-			if (this.occlude()) return this.occluded;
-			this.update = document.id(update);
-			this.setOptions(options);
-			this.makeRequest();
-			if (this.options.resetForm) {
-				this.request.addEvent('success', function(){
-					$try(function(){ this.element.reset(); }.bind(this));
-					if (window.OverText) OverText.update();
-				}.bind(this));
-			}
-			this.attach();
-		},
-
-		toElement: function() {
-			return this.element;
-		},
-
-		makeRequest: function(){
-			this.request = new Request.HTML($merge({
-					url: this.element.get('action'),
-					update: this.update,
-					emulation: false,
-					spinnerTarget: this.element,
-					method: this.element.get('method') || 'post'
-			}, this.options.requestOptions)).addEvents({
-				success: function(text, xml){
-					['success', 'complete'].each(function(evt){
-						this.fireEvent(evt, [this.update, text, xml]);
-					}, this);
-				}.bind(this),
-				failure: function(xhr){
-					this.fireEvent('failure', xhr);
-				}.bind(this),
-				exception: function(){
-					this.fireEvent('failure', xhr);
-				}.bind(this)
-			});
-		},
-
-		attach: function(attach){
-			attach = $pick(attach, true);
-			method = attach ? 'addEvent' : 'removeEvent';
-			
-			var fv = this.element.retrieve('validator');
-			if (fv) fv[method]('onFormValidate', this.onFormValidate);
-			if (!fv || !attach) this.element[method]('submit', this.onSubmit);
-		},
-
-		detach: function(){
-			this.attach(false);
-		},
-
-		//public method
-		enable: function(){
-			this.attach();
-		},
-
-		//public method
-		disable: function(){
-			this.detach();
-		},
-
-		onFormValidate: function(valid, form, e) {
-			if (valid || !fv.options.stopOnFailure) {
-				if (e && e.stop) e.stop();
-				this.send();
-			}
-		},
-
-		onSubmit: function(e){
-			if (this.element.retrieve('validator')) {
-				//form validator was created after Form.Request
-				this.detach();
-				this.addFormEvent();
-				return;
-			}
-			e.stop();
-			this.send();
-		},
-
-		send: function(){
-			var str = this.element.toQueryString().trim();
-			var data = $H(this.options.extraData).toQueryString();
-			if (str) str += "&" + data;
-			else str = data;
-			this.fireEvent('send', [this.element, str]);
-			this.request.send({data: str});
-			return this;
-		}
-
-	});
-
-	Element.Properties.formRequest = {
-
-		set: function(){
-			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
-			var update = opt.update || opt.updateId;
-			var updater = this.retrieve('form.request');
-			if (update) {
-				if (updater) updater.update = document.id(update);
-				this.store('form.request:update', update);
-			}
-			if (opt.options) {
-				if (updater) updater.setOptions(opt.options);
-				this.store('form.request:options', opt.options);
-			}
-			return this;
-		},
-
-		get: function(){
-			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
-			var update = opt.update || opt.updateId;
-			if (opt.options || update || !this.retrieve('form.request')){
-				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
-				if (update) this.set('form.request', update);
-				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
-			}
-			return this.retrieve('form.request');
-		}
-
-	};
-
-	Element.implement({
-
-		formUpdate: function(update, options){
-			this.get('form.request', update, options).send();
-			return this;
-		}
-
-	});
-
-})();/*
----
-
-script: Fx.Reveal.js
-
-description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Fx.Morph
-- /Element.Shortcuts
-- /Element.Measure
-
-provides: [Fx.Reveal]
-
-...
-*/
-
-Fx.Reveal = new Class({
-
-	Extends: Fx.Morph,
-
-	options: {/*	  
-		onShow: $empty(thisElement),
-		onHide: $empty(thisElement),
-		onComplete: $empty(thisElement),
-		heightOverride: null,
-		widthOverride: null, */
-		link: 'cancel',
-		styles: ['padding', 'border', 'margin'],
-		transitionOpacity: !Browser.Engine.trident4,
-		mode: 'vertical',
-		display: 'block',
-		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
-	},
-
-	dissolve: function(){
-		try {
-			if (!this.hiding && !this.showing){
-				if (this.element.getStyle('display') != 'none'){
-					this.hiding = true;
-					this.showing = false;
-					this.hidden = true;
-					var startStyles = this.element.getComputedSize({
-						styles: this.options.styles,
-						mode: this.options.mode
-					});
-					var setToAuto = (this.element.style.height === ''||this.element.style.height == 'auto');
-					this.element.setStyle('display', 'block');
-					if (this.options.transitionOpacity) startStyles.opacity = 1;
-					var zero = {};
-					$each(startStyles, function(style, name){
-						zero[name] = [style, 0];
-					}, this);
-					var overflowBefore = this.element.getStyle('overflow');
-					this.element.setStyle('overflow', 'hidden');
-					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
-					this.$chain.unshift(function(){
-						if (this.hidden){
-							this.hiding = false;
-							$each(startStyles, function(style, name){
-								startStyles[name] = style;
-							}, this);
-							this.element.setStyles($merge({display: 'none', overflow: overflowBefore}, startStyles));
-							if (setToAuto){
-								if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
-								if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
-							}
-							if (hideThese) hideThese.setStyle('visibility', 'visible');
-						}
-						this.fireEvent('hide', this.element);
-						this.callChain();
-					}.bind(this));
-					if (hideThese) hideThese.setStyle('visibility', 'hidden');
-					this.start(zero);
-				} else {
-					this.callChain.delay(10, this);
-					this.fireEvent('complete', this.element);
-					this.fireEvent('hide', this.element);
-				}
-			} else if (this.options.link == 'chain'){
-				this.chain(this.dissolve.bind(this));
-			} else if (this.options.link == 'cancel' && !this.hiding){
-				this.cancel();
-				this.dissolve();
-			}
-		} catch(e){
-			this.hiding = false;
-			this.element.setStyle('display', 'none');
-			this.callChain.delay(10, this);
-			this.fireEvent('complete', this.element);
-			this.fireEvent('hide', this.element);
-		}
-		return this;
-	},
-
-	reveal: function(){
-		try {
-			if (!this.showing && !this.hiding){
-				if (this.element.getStyle('display') == 'none' ||
-					 this.element.getStyle('visiblity') == 'hidden' ||
-					 this.element.getStyle('opacity') == 0){
-					this.showing = true;
-					this.hiding = this.hidden =  false;
-					var setToAuto, startStyles;
-					//toggle display, but hide it
-					this.element.measure(function(){
-						setToAuto = (this.element.style.height === '' || this.element.style.height == 'auto');
-						//create the styles for the opened/visible state
-						startStyles = this.element.getComputedSize({
-							styles: this.options.styles,
-							mode: this.options.mode
-						});
-					}.bind(this));
-					$each(startStyles, function(style, name){
-						startStyles[name] = style;
-					});
-					//if we're overridding height/width
-					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
-					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
-					if (this.options.transitionOpacity) {
-						this.element.setStyle('opacity', 0);
-						startStyles.opacity = 1;
-					}
-					//create the zero state for the beginning of the transition
-					var zero = {
-						height: 0,
-						display: this.options.display
-					};
-					$each(startStyles, function(style, name){ zero[name] = 0; });
-					var overflowBefore = this.element.getStyle('overflow');
-					//set to zero
-					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
-					//hide inputs
-					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
-					if (hideThese) hideThese.setStyle('visibility', 'hidden');
-					//start the effect
-					this.start(startStyles);
-					this.$chain.unshift(function(){
-						this.element.setStyle('overflow', overflowBefore);
-						if (!this.options.heightOverride && setToAuto){
-							if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
-							if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
-						}
-						if (!this.hidden) this.showing = false;
-						if (hideThese) hideThese.setStyle('visibility', 'visible');
-						this.callChain();
-						this.fireEvent('show', this.element);
-					}.bind(this));
-				} else {
-					this.callChain();
-					this.fireEvent('complete', this.element);
-					this.fireEvent('show', this.element);
-				}
-			} else if (this.options.link == 'chain'){
-				this.chain(this.reveal.bind(this));
-			} else if (this.options.link == 'cancel' && !this.showing){
-				this.cancel();
-				this.reveal();
-			}
-		} catch(e){
-			this.element.setStyles({
-				display: this.options.display,
-				visiblity: 'visible',
-				opacity: 1
-			});
-			this.showing = false;
-			this.callChain.delay(10, this);
-			this.fireEvent('complete', this.element);
-			this.fireEvent('show', this.element);
-		}
-		return this;
-	},
-
-	toggle: function(){
-		if (this.element.getStyle('display') == 'none' ||
-			 this.element.getStyle('visiblity') == 'hidden' ||
-			 this.element.getStyle('opacity') == 0){
-			this.reveal();
-		} else {
-			this.dissolve();
-		}
-		return this;
-	},
-
-	cancel: function(){
-		this.parent.apply(this, arguments);
-		this.hidding = false;
-		this.showing = false;
-	}
-
-});
-
-Element.Properties.reveal = {
-
-	set: function(options){
-		var reveal = this.retrieve('reveal');
-		if (reveal) reveal.cancel();
-		return this.eliminate('reveal').store('reveal:options', options);
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('reveal')){
-			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
-			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
-		}
-		return this.retrieve('reveal');
-	}
-
-};
-
-Element.Properties.dissolve = Element.Properties.reveal;
-
-Element.implement({
-
-	reveal: function(options){
-		this.get('reveal', options).reveal();
-		return this;
-	},
-
-	dissolve: function(options){
-		this.get('reveal', options).dissolve();
-		return this;
-	},
-
-	nix: function(){
-		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
-		this.get('reveal', params.options).dissolve().chain(function(){
-			this[params.destroy ? 'destroy' : 'dispose']();
-		}.bind(this));
-		return this;
-	},
-
-	wink: function(){
-		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
-		var reveal = this.get('reveal', params.options);
-		reveal.reveal().chain(function(){
-			(function(){
-				reveal.dissolve();
-			}).delay(params.duration || 2000);
-		});
-	}
-
-
-});/*
----
-
-script: Form.Request.Append.js
-
-description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Form.Request
-- /Fx.Reveal
-- /Elements.from
-
-provides: [Form.Request.Append]
-
-...
-*/
-
-Form.Request.Append = new Class({
-
-	Extends: Form.Request,
-
-	options: {
-		//onBeforeEffect: $empty,
-		useReveal: true,
-		revealOptions: {},
-		inject: 'bottom'
-	},
-
-	makeRequest: function(){
-		this.request = new Request.HTML($merge({
-				url: this.element.get('action'),
-				method: this.element.get('method') || 'post',
-				spinnerTarget: this.element
-			}, this.options.requestOptions, {
-				evalScripts: false
-			})
-		).addEvents({
-			success: function(tree, elements, html, javascript){
-				var container;
-				var kids = Elements.from(html);
-				if (kids.length == 1) {
-					container = kids[0];
-				} else {
-					 container = new Element('div', {
-						styles: {
-							display: 'none'
-						}
-					}).adopt(kids);
-				}
-				container.inject(this.update, this.options.inject);
-				if (this.options.requestOptions.evalScripts) $exec(javascript);
-				this.fireEvent('beforeEffect', container);
-				var finish = function(){
-					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
-				}.bind(this);
-				if (this.options.useReveal) {
-					container.get('reveal', this.options.revealOptions).chain(finish);
-					container.reveal();
-				} else {
-					finish();
-				}
-			}.bind(this),
-			failure: function(xhr){
-				this.fireEvent('failure', xhr);
-			}.bind(this)
-		});
-	}
-
-});/*
----
-
-script: Form.Validator.English.js
-
-description: Date messages for English.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.English]
-
-...
-*/
-
-MooTools.lang.set('en-US', 'Form.Validator', {
-
-	required:'This field is required.',
-	minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
-	maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
-	integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
-	numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
-	digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
-	alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
-	alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
-	dateSuchAs:'Please enter a valid date such as {date}',
-	dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
-	email:'Please enter a valid email address. For example "fred at domain.com".',
-	url:'Please enter a valid URL such as http://www.google.com.',
-	currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
-	oneRequired:'Please enter something for at least one of these inputs.',
-	errorPrefix: 'Error: ',
-	warningPrefix: 'Warning: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'There can be no spaces in this input.',
-	reqChkByNode: 'No items are selected.',
-	requiredChk: 'This field is required.',
-	reqChkByName: 'Please select a {label}.',
-	match: 'This field needs to match the {matchName} field',
-	startDate: 'the start date',
-	endDate: 'the end date',
-	currendDate: 'the current date',
-	afterDate: 'The date should be the same or after {label}.',
-	beforeDate: 'The date should be the same or before {label}.',
-	startMonth: 'Please select a start month',
-	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
-	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
-
-});/*
----
-
-script: Form.Validator.js
-
-description: A css-class based form validation system.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Options
-- core:1.2.4/Events
-- core:1.2.4/Selectors
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Style
-- core:1.2.4/JSON
-- /Lang- /Class.Binds
-- /Date Element.Forms
-- /Form.Validator.English
-- /Element.Shortcuts
-
-provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
-
-...
-*/
-if (!window.Form) window.Form = {};
-
-var InputValidator = new Class({
-
-	Implements: [Options],
-
-	options: {
-		errorMsg: 'Validation failed.',
-		test: function(field){return true;}
-	},
-
-	initialize: function(className, options){
-		this.setOptions(options);
-		this.className = className;
-	},
-
-	test: function(field, props){
-		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
-		else return false;
-	},
-
-	getError: function(field, props){
-		var err = this.options.errorMsg;
-		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
-		return err;
-	},
-
-	getProps: function(field){
-		if (!document.id(field)) return {};
-		return field.get('validatorProps');
-	}
-
-});
-
-Element.Properties.validatorProps = {
-
-	set: function(props){
-		return this.eliminate('validatorProps').store('validatorProps', props);
-	},
-
-	get: function(props){
-		if (props) this.set(props);
-		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
-		if (this.getProperty('validatorProps')){
-			try {
-				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
-			}catch(e){
-				return {};
-			}
-		} else {
-			var vals = this.get('class').split(' ').filter(function(cls){
-				return cls.test(':');
-			});
-			if (!vals.length){
-				this.store('validatorProps', {});
-			} else {
-				props = {};
-				vals.each(function(cls){
-					var split = cls.split(':');
-					if (split[1]) {
-						try {
-							props[split[0]] = JSON.decode(split[1]);
-						} catch(e) {}
-					}
-				});
-				this.store('validatorProps', props);
-			}
-		}
-		return this.retrieve('validatorProps');
-	}
-
-};
-
-Form.Validator = new Class({
-
-	Implements:[Options, Events],
-
-	Binds: ['onSubmit'],
-
-	options: {/*
-		onFormValidate: $empty(isValid, form, event),
-		onElementValidate: $empty(isValid, field, className, warn),
-		onElementPass: $empty(field),
-		onElementFail: $empty(field, validatorsFailed) */
-		fieldSelectors: 'input, select, textarea',
-		ignoreHidden: true,
-		ignoreDisabled: true,
-		useTitles: false,
-		evaluateOnSubmit: true,
-		evaluateFieldsOnBlur: true,
-		evaluateFieldsOnChange: true,
-		serial: true,
-		stopOnFailure: true,
-		warningPrefix: function(){
-			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
-		},
-		errorPrefix: function(){
-			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
-		}
-	},
-
-	initialize: function(form, options){
-		this.setOptions(options);
-		this.element = document.id(form);
-		this.element.store('validator', this);
-		this.warningPrefix = $lambda(this.options.warningPrefix)();
-		this.errorPrefix = $lambda(this.options.errorPrefix)();
-		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
-		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
-	},
-
-	toElement: function(){
-		return this.element;
-	},
-
-	getFields: function(){
-		return (this.fields = this.element.getElements(this.options.fieldSelectors));
-	},
-
-	watchFields: function(fields){
-		fields.each(function(el){
-			if (this.options.evaluateFieldsOnBlur)
-				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
-			if (this.options.evaluateFieldsOnChange)
-				el.addEvent('change', this.validationMonitor.pass([el, true], this));
-		}, this);
-	},
-
-	validationMonitor: function(){
-		$clear(this.timer);
-		this.timer = this.validateField.delay(50, this, arguments);
-	},
-
-	onSubmit: function(event){
-		if (!this.validate(event) && event) event.preventDefault();
-		else this.reset();
-	},
-
-	reset: function(){
-		this.getFields().each(this.resetField, this);
-		return this;
-	},
-
-	validate: function(event){
-		var result = this.getFields().map(function(field){
-			return this.validateField(field, true);
-		}, this).every(function(v){ return v;});
-		this.fireEvent('formValidate', [result, this.element, event]);
-		if (this.options.stopOnFailure && !result && event) event.preventDefault();
-		return result;
-	},
-
-	validateField: function(field, force){
-		if (this.paused) return true;
-		field = document.id(field);
-		var passed = !field.hasClass('validation-failed');
-		var failed, warned;
-		if (this.options.serial && !force){
-			failed = this.element.getElement('.validation-failed');
-			warned = this.element.getElement('.warning');
-		}
-		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
-			var validators = field.className.split(' ').some(function(cn){
-				return this.getValidator(cn);
-			}, this);
-			var validatorsFailed = [];
-			field.className.split(' ').each(function(className){
-				if (className && !this.test(className, field)) validatorsFailed.include(className);
-			}, this);
-			passed = validatorsFailed.length === 0;
-			if (validators && !field.hasClass('warnOnly')){
-				if (passed){
-					field.addClass('validation-passed').removeClass('validation-failed');
-					this.fireEvent('elementPass', field);
-				} else {
-					field.addClass('validation-failed').removeClass('validation-passed');
-					this.fireEvent('elementFail', [field, validatorsFailed]);
-				}
-			}
-			if (!warned){
-				var warnings = field.className.split(' ').some(function(cn){
-					if (cn.test('^warn-') || field.hasClass('warnOnly'))
-						return this.getValidator(cn.replace(/^warn-/,''));
-					else return null;
-				}, this);
-				field.removeClass('warning');
-				var warnResult = field.className.split(' ').map(function(cn){
-					if (cn.test('^warn-') || field.hasClass('warnOnly'))
-						return this.test(cn.replace(/^warn-/,''), field, true);
-					else return null;
-				}, this);
-			}
-		}
-		return passed;
-	},
-
-	test: function(className, field, warn){
-		field = document.id(field);
-		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
-		var validator = this.getValidator(className);
-		if (field.hasClass('ignoreValidation')) return true;
-		warn = $pick(warn, false);
-		if (field.hasClass('warnOnly')) warn = true;
-		var isValid = validator ? validator.test(field) : true;
-		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
-		if (warn) return true;
-		return isValid;
-	},
-
-	resetField: function(field){
-		field = document.id(field);
-		if (field){
-			field.className.split(' ').each(function(className){
-				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
-				field.removeClass('validation-failed');
-				field.removeClass('warning');
-				field.removeClass('validation-passed');
-			}, this);
-		}
-		return this;
-	},
-
-	stop: function(){
-		this.paused = true;
-		return this;
-	},
-
-	start: function(){
-		this.paused = false;
-		return this;
-	},
-
-	ignoreField: function(field, warn){
-		field = document.id(field);
-		if (field){
-			this.enforceField(field);
-			if (warn) field.addClass('warnOnly');
-			else field.addClass('ignoreValidation');
-		}
-		return this;
-	},
-
-	enforceField: function(field){
-		field = document.id(field);
-		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
-		return this;
-	}
-
-});
-
-Form.Validator.getMsg = function(key){
-	return MooTools.lang.get('Form.Validator', key);
-};
-
-Form.Validator.adders = {
-
-	validators:{},
-
-	add : function(className, options){
-		this.validators[className] = new InputValidator(className, options);
-		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
-		//extend these validators into it
-		//this allows validators to be global and/or per instance
-		if (!this.initialize){
-			this.implement({
-				validators: this.validators
-			});
-		}
-	},
-
-	addAllThese : function(validators){
-		$A(validators).each(function(validator){
-			this.add(validator[0], validator[1]);
-		}, this);
-	},
-
-	getValidator: function(className){
-		return this.validators[className.split(':')[0]];
-	}
-
-};
-
-$extend(Form.Validator, Form.Validator.adders);
-
-Form.Validator.implement(Form.Validator.adders);
-
-Form.Validator.add('IsEmpty', {
-
-	errorMsg: false,
-	test: function(element){
-		if (element.type == 'select-one' || element.type == 'select')
-			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
-		else
-			return ((element.get('value') == null) || (element.get('value').length == 0));
-	}
-
-});
-
-Form.Validator.addAllThese([
-
-	['required', {
-		errorMsg: function(){
-			return Form.Validator.getMsg('required');
-		},
-		test: function(element){
-			return !Form.Validator.getValidator('IsEmpty').test(element);
-		}
-	}],
-
-	['minLength', {
-		errorMsg: function(element, props){
-			if ($type(props.minLength))
-				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
-			else return '';
-		},
-		test: function(element, props){
-			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
-			else return true;
-		}
-	}],
-
-	['maxLength', {
-		errorMsg: function(element, props){
-			//props is {maxLength:10}
-			if ($type(props.maxLength))
-				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
-			else return '';
-		},
-		test: function(element, props){
-			//if the value is <= than the maxLength value, element passes test
-			return (element.get('value').length <= $pick(props.maxLength, 10000));
-		}
-	}],
-
-	['validate-integer', {
-		errorMsg: Form.Validator.getMsg.pass('integer'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
-		}
-	}],
-
-	['validate-numeric', {
-		errorMsg: Form.Validator.getMsg.pass('numeric'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) ||
-				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
-		}
-	}],
-
-	['validate-digits', {
-		errorMsg: Form.Validator.getMsg.pass('digits'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
-		}
-	}],
-
-	['validate-alpha', {
-		errorMsg: Form.Validator.getMsg.pass('alpha'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
-		}
-	}],
-
-	['validate-alphanum', {
-		errorMsg: Form.Validator.getMsg.pass('alphanum'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
-		}
-	}],
-
-	['validate-date', {
-		errorMsg: function(element, props){
-			if (Date.parse){
-				var format = props.dateFormat || '%x';
-				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
-			} else {
-				return Form.Validator.getMsg('dateInFormatMDY');
-			}
-		},
-		test: function(element, props){
-			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
-			var d;
-			if (Date.parse){
-				var format = props.dateFormat || '%x';
-				d = Date.parse(element.get('value'));
-				var formatted = d.format(format);
-				if (formatted != 'invalid date') element.set('value', formatted);
-				return !isNaN(d);
-			} else {
-				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
-				if (!regex.test(element.get('value'))) return false;
-				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
-				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
-					(parseInt(RegExp.$2, 10) == d.getDate()) &&
-					(parseInt(RegExp.$3, 10) == d.getFullYear());
-			}
-		}
-	}],
-
-	['validate-email', {
-		errorMsg: Form.Validator.getMsg.pass('email'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
-		}
-	}],
-
-	['validate-url', {
-		errorMsg: Form.Validator.getMsg.pass('url'),
-		test: function(element){
-			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
-		}
-	}],
-
-	['validate-currency-dollar', {
-		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
-		test: function(element){
-			// [$]1[##][,###]+[.##]
-			// [$]1###+[.##]
-			// [$]0.##
-			// [$].##
-			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
-		}
-	}],
-
-	['validate-one-required', {
-		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
-		test: function(element, props){
-			var p = document.id(props['validate-one-required']) || element.getParent();
-			return p.getElements('input').some(function(el){
-				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
-				return el.get('value');
-			});
-		}
-	}]
-
-]);
-
-Element.Properties.validator = {
-
-	set: function(options){
-		var validator = this.retrieve('validator');
-		if (validator) validator.setOptions(options);
-		return this.store('validator:options');
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('validator')){
-			if (options || !this.retrieve('validator:options')) this.set('validator', options);
-			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
-		}
-		return this.retrieve('validator');
-	}
-
-};
-
-Element.implement({
-
-	validate: function(options){
-		this.set('validator', options);
-		return this.get('validator', options).validate();
-	}
-
-});
-//legacy
-var FormValidator = Form.Validator;/*
----
-
-script: Form.Validator.Inline.js
-
-description: Extends Form.Validator to add inline messages.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Form.Validator
-
-provides: [Form.Validator.Inline]
-
-...
-*/
-
-Form.Validator.Inline = new Class({
-
-	Extends: Form.Validator,
-
-	options: {
-		scrollToErrorsOnSubmit: true,
-		scrollFxOptions: {
-			transition: 'quad:out',
-			offset: {
-				y: -20
-			}
-		}
-	},
-
-	initialize: function(form, options){
-		this.parent(form, options);
-		this.addEvent('onElementValidate', function(isValid, field, className, warn){
-			var validator = this.getValidator(className);
-			if (!isValid && validator.getError(field)){
-				if (warn) field.addClass('warning');
-				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
-				this.insertAdvice(advice, field);
-				this.showAdvice(className, field);
-			} else {
-				this.hideAdvice(className, field);
-			}
-		});
-	},
-
-	makeAdvice: function(className, field, error, warn){
-		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
-			errorMsg += (this.options.useTitles) ? field.title || error:error;
-		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
-		var advice = this.getAdvice(className, field);
-		if(advice) {
-			advice = advice.clone(true, true).set('html', errorMsg).replaces(advice);
-		} else {
-			advice = new Element('div', {
-				html: errorMsg,
-				styles: { display: 'none' },
-				id: 'advice-' + className + '-' + this.getFieldId(field)
-			}).addClass(cssClass);
-		}
-		field.store('advice-' + className, advice);
-		return advice;
-	},
-
-	getFieldId : function(field){
-		return field.id ? field.id : field.id = 'input_' + field.name;
-	},
-
-	showAdvice: function(className, field){
-		var advice = this.getAdvice(className, field);
-		if (advice && !field.retrieve(this.getPropName(className))
-				&& (advice.getStyle('display') == 'none'
-				|| advice.getStyle('visiblity') == 'hidden'
-				|| advice.getStyle('opacity') == 0)){
-			field.store(this.getPropName(className), true);
-			if (advice.reveal) advice.reveal();
-			else advice.setStyle('display', 'block');
-		}
-	},
-
-	hideAdvice: function(className, field){
-		var advice = this.getAdvice(className, field);
-		if (advice && field.retrieve(this.getPropName(className))){
-			field.store(this.getPropName(className), false);
-			//if Fx.Reveal.js is present, transition the advice out
-			if (advice.dissolve) advice.dissolve();
-			else advice.setStyle('display', 'none');
-		}
-	},
-
-	getPropName: function(className){
-		return 'advice' + className;
-	},
-
-	resetField: function(field){
-		field = document.id(field);
-		if (!field) return this;
-		this.parent(field);
-		field.className.split(' ').each(function(className){
-			this.hideAdvice(className, field);
-		}, this);
-		return this;
-	},
-
-	getAllAdviceMessages: function(field, force){
-		var advice = [];
-		if (field.hasClass('ignoreValidation') && !force) return advice;
-		var validators = field.className.split(' ').some(function(cn){
-			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
-			if (warner) cn = cn.replace(/^warn-/, '');
-			var validator = this.getValidator(cn);
-			if (!validator) return;
-			advice.push({
-				message: validator.getError(field),
-				warnOnly: warner,
-				passed: validator.test(),
-				validator: validator
-			});
-		}, this);
-		return advice;
-	},
-
-	getAdvice: function(className, field){
-		return field.retrieve('advice-' + className);
-	},
-
-	insertAdvice: function(advice, field){
-		//Check for error position prop
-		var props = field.get('validatorProps');
-		//Build advice
-		if (!props.msgPos || !document.id(props.msgPos)){
-			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
-			else advice.inject(document.id(field), 'after');
-		} else {
-			document.id(props.msgPos).grab(advice);
-		}
-	},
-
-	validateField: function(field, force){
-		var result = this.parent(field, force);
-		if (this.options.scrollToErrorsOnSubmit && !result){
-			var failed = document.id(this).getElement('.validation-failed');
-			var par = document.id(this).getParent();
-			while (par != document.body && par.getScrollSize().y == par.getSize().y){
-				par = par.getParent();
-			}
-			var fx = par.retrieve('fvScroller');
-			if (!fx && window.Fx && Fx.Scroll){
-				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
-				par.store('fvScroller', fx);
-			}
-			if (failed){
-				if (fx) fx.toElement(failed);
-				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
-			}
-		}
-		return result;
-	}
-
-});
-/*
----
-
-script: Form.Validator.Extras.js
-
-description: Additional validators for the Form.Validator class.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Form.Validator
-
-provides: [Form.Validator.Extras]
-
-...
-*/
-Form.Validator.addAllThese([
-
-	['validate-enforce-oncheck', {
-		test: function(element, props){
-			if (element.checked){
-				var fv = element.getParent('form').retrieve('validator');
-				if (!fv) return true;
-				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
-					fv.enforceField(item);
-				});
-			}
-			return true;
-		}
-	}],
-
-	['validate-ignore-oncheck', {
-		test: function(element, props){
-			if (element.checked){
-				var fv = element.getParent('form').retrieve('validator');
-				if (!fv) return true;
-				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
-					fv.ignoreField(item);
-					fv.resetField(item);
-				});
-			}
-			return true;
-		}
-	}],
-
-	['validate-nospace', {
-		errorMsg: function(){
-			return Form.Validator.getMsg('noSpace');
-		},
-		test: function(element, props){
-			return !element.get('value').test(/\s/);
-		}
-	}],
-
-	['validate-toggle-oncheck', {
-		test: function(element, props){
-			var fv = element.getParent('form').retrieve('validator');
-			if (!fv) return true;
-			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
-			if (!element.checked){
-				eleArr.each(function(item){
-					fv.ignoreField(item);
-					fv.resetField(item);
-				});
-			} else {
-				eleArr.each(function(item){
-					fv.enforceField(item);
-				});
-			}
-			return true;
-		}
-	}],
-
-	['validate-reqchk-bynode', {
-		errorMsg: function(){
-			return Form.Validator.getMsg('reqChkByNode');
-		},
-		test: function(element, props){
-			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
-				return item.checked;
-			});
-		}
-	}],
-
-	['validate-required-check', {
-		errorMsg: function(element, props){
-			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
-		},
-		test: function(element, props){
-			return !!element.checked;
-		}
-	}],
-
-	['validate-reqchk-byname', {
-		errorMsg: function(element, props){
-			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
-		},
-		test: function(element, props){
-			var grpName = props.groupName || element.get('name');
-			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
-				return item.checked;
-			});
-			var fv = element.getParent('form').retrieve('validator');
-			if (oneCheckedItem && fv) fv.resetField(element);
-			return oneCheckedItem;
-		}
-	}],
-
-	['validate-match', {
-		errorMsg: function(element, props){
-			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
-		},
-		test: function(element, props){
-			var eleVal = element.get('value');
-			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
-			return eleVal && matchVal ? eleVal == matchVal : true;
-		}
-	}],
-
-	['validate-after-date', {
-		errorMsg: function(element, props){
-			return Form.Validator.getMsg('afterDate').substitute({
-				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
-			});
-		},
-		test: function(element, props){
-			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
-			var end = Date.parse(element.get('value'));
-			return end && start ? end >= start : true;
-		}
-	}],
-
-	['validate-before-date', {
-		errorMsg: function(element, props){
-			return Form.Validator.getMsg('beforeDate').substitute({
-				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
-			});
-		},
-		test: function(element, props){
-			var start = Date.parse(element.get('value'));
-			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
-			return end && start ? end >= start : true;
-		}
-	}],
-
-	['validate-custom-required', {
-		errorMsg: function(){
-			return Form.Validator.getMsg('required');
-		},
-		test: function(element, props){
-			return element.get('value') != props.emptyValue;
-		}
-	}],
-
-	['validate-same-month', {
-		errorMsg: function(element, props){
-			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
-			var eleVal = element.get('value');
-			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
-		},
-		test: function(element, props){
-			var d1 = Date.parse(element.get('value'));
-			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
-			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
-		}
-	}],
-
-
-	['validate-cc-num', {
-		errorMsg: function(element){
-			var ccNum = element.get('value').ccNum.replace(/[^0-9]/g, '');
-			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
-		},
-		test: function(element){
-			// required is a different test
-			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }
-
-			// Clean number value
-			var ccNum = element.get('value');
-			ccNum = ccNum.replace(/[^0-9]/g, '');
-
-			var valid_type = false;
-
-			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
-			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
-			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
-			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
-
-			if (valid_type) {
-				var sum = 0;
-				var cur = 0;
-
-				for(var i=ccNum.length-1; i>=0; --i) {
-					cur = ccNum.charAt(i).toInt();
-					if (cur == 0) { continue; }
-
-					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
-					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }
-
-					sum += cur;
-				}
-				if ((sum % 10) == 0) { return true; }
-			}
-
-			var chunks = '';
-			while (ccNum != '') {
-				chunks += ' ' + ccNum.substr(0,4);
-				ccNum = ccNum.substr(4);
-			}
-
-			element.getParent('form').retrieve('validator').ignoreField(element);
-			element.set('value', chunks.clean());
-			element.getParent('form').retrieve('validator').enforceField(element);
-			return false;
-		}
-	}]
-
-
-]);/*
----
-
-script: OverText.js
-
-description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Options
-- core:1.2.4/Events
-- core:1.2.4/Element.Event
-- /Class.Binds
-- /Class.Occlude
-- /Element.Position
-- /Element.Shortcuts
-
-provides: [OverText]
-
-...
-*/
-
-var OverText = new Class({
-
-	Implements: [Options, Events, Class.Occlude],
-
-	Binds: ['reposition', 'assert', 'focus', 'hide'],
-
-	options: {/*
-		textOverride: null,
-		onFocus: $empty()
-		onTextHide: $empty(textEl, inputEl),
-		onTextShow: $empty(textEl, inputEl), */
-		element: 'label',
-		positionOptions: {
-			position: 'upperLeft',
-			edge: 'upperLeft',
-			offset: {
-				x: 4,
-				y: 2
-			}
-		},
-		poll: false,
-		pollInterval: 250,
-		wrap: false
-	},
-
-	property: 'OverText',
-
-	initialize: function(element, options){
-		this.element = document.id(element);
-		if (this.occlude()) return this.occluded;
-		this.setOptions(options);
-		this.attach(this.element);
-		OverText.instances.push(this);
-		if (this.options.poll) this.poll();
-		return this;
-	},
-
-	toElement: function(){
-		return this.element;
-	},
-
-	attach: function(){
-		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
-		if (!val) return;
-		this.text = new Element(this.options.element, {
-			'class': 'overTxtLabel',
-			styles: {
-				lineHeight: 'normal',
-				position: 'absolute',
-				cursor: 'text'
-			},
-			html: val,
-			events: {
-				click: this.hide.pass(this.options.element == 'label', this)
-			}
-		}).inject(this.element, 'after');
-		if (this.options.element == 'label') {
-			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
-			this.text.set('for', this.element.get('id'));
-		}
-
-		if (this.options.wrap) {
-			this.textHolder = new Element('div', {
-				styles: {
-					lineHeight: 'normal',
-					position: 'relative'
-				},
-				'class':'overTxtWrapper'
-			}).adopt(this.text).inject(this.element, 'before');
-		}
-
-		this.element.addEvents({
-			focus: this.focus,
-			blur: this.assert,
-			change: this.assert
-		}).store('OverTextDiv', this.text);
-		window.addEvent('resize', this.reposition.bind(this));
-		this.assert(true);
-		this.reposition();
-	},
-
-	wrap: function(){
-		if (this.options.element == 'label') {
-			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
-			this.text.set('for', this.element.get('id'));
-		}
-	},
-
-	startPolling: function(){
-		this.pollingPaused = false;
-		return this.poll();
-	},
-
-	poll: function(stop){
-		//start immediately
-		//pause on focus
-		//resumeon blur
-		if (this.poller && !stop) return this;
-		var test = function(){
-			if (!this.pollingPaused) this.assert(true);
-		}.bind(this);
-		if (stop) $clear(this.poller);
-		else this.poller = test.periodical(this.options.pollInterval, this);
-		return this;
-	},
-
-	stopPolling: function(){
-		this.pollingPaused = true;
-		return this.poll(true);
-	},
-
-	focus: function(){
-		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
-		this.hide();
-	},
-
-	hide: function(suppressFocus, force){
-		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
-			this.text.hide();
-			this.fireEvent('textHide', [this.text, this.element]);
-			this.pollingPaused = true;
-			try {
-				if (!suppressFocus) this.element.fireEvent('focus');
-				this.element.focus();
-			} catch(e){} //IE barfs if you call focus on hidden elements
-		}
-		return this;
-	},
-
-	show: function(){
-		if (this.text && !this.text.isDisplayed()){
-			this.text.show();
-			this.reposition();
-			this.fireEvent('textShow', [this.text, this.element]);
-			this.pollingPaused = false;
-		}
-		return this;
-	},
-
-	assert: function(suppressFocus){
-		this[this.test() ? 'show' : 'hide'](suppressFocus);
-	},
-
-	test: function(){
-		var v = this.element.get('value');
-		return !v;
-	},
-
-	reposition: function(){
-		this.assert(true);
-		if (!this.element.isVisible()) return this.stopPolling().hide();
-		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
-		return this;
-	}
-
-});
-
-OverText.instances = [];
-
-$extend(OverText, {
-
-	each: function(fn) {
-		return OverText.instances.map(function(ot, i){
-			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
-			return null; //the input or the text was destroyed
-		});
-	},
-	
-	update: function(){
-
-		return OverText.each(function(ot){
-			return ot.reposition();
-		});
-
-	},
-
-	hideAll: function(){
-
-		return OverText.each(function(ot){
-			return ot.hide(true, true);
-		});
-
-	},
-
-	showAll: function(){
-		return OverText.each(function(ot) {
-			return ot.show();
-		});
-	}
-
-});
-
-if (window.Fx && Fx.Reveal) {
-	Fx.Reveal.implement({
-		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
-	});
-}/*
----
-
-script: Fx.Elements.js
-
-description: Effect to change any number of CSS properties of any number of Elements.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Fx.CSS
-- /MooTools.More
-
-provides: [Fx.Elements]
-
-...
-*/
-
-Fx.Elements = new Class({
-
-	Extends: Fx.CSS,
-
-	initialize: function(elements, options){
-		this.elements = this.subject = $$(elements);
-		this.parent(options);
-	},
-
-	compute: function(from, to, delta){
-		var now = {};
-		for (var i in from){
-			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
-			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
-		}
-		return now;
-	},
-
-	set: function(now){
-		for (var i in now){
-			var iNow = now[i];
-			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
-		}
-		return this;
-	},
-
-	start: function(obj){
-		if (!this.check(obj)) return this;
-		var from = {}, to = {};
-		for (var i in obj){
-			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
-			for (var p in iProps){
-				var parsed = this.prepare(this.elements[i], p, iProps[p]);
-				iFrom[p] = parsed.from;
-				iTo[p] = parsed.to;
-			}
-		}
-		return this.parent(from, to);
-	}
-
-});/*
----
-
-script: Fx.Accordion.js
-
-description: An Fx.Elements extension which allows you to easily create accordion type controls.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Element.Event
-- /Fx.Elements
-
-provides: [Fx.Accordion]
-
-...
-*/
-
-var Accordion = Fx.Accordion = new Class({
-
-	Extends: Fx.Elements,
-
-	options: {/*
-		onActive: $empty(toggler, section),
-		onBackground: $empty(toggler, section),
-		fixedHeight: false,
-		fixedWidth: false,
-		*/
-		display: 0,
-		show: false,
-		height: true,
-		width: false,
-		opacity: true,
-		alwaysHide: false,
-		trigger: 'click',
-		initialDisplayFx: true,
-		returnHeightToAuto: true
-	},
-
-	initialize: function(){
-		var params = Array.link(arguments, {'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined});
-		this.parent(params.elements, params.options);
-		this.togglers = $$(params.togglers);
-		this.container = document.id(params.container);
-		this.previous = -1;
-		this.internalChain = new Chain();
-		if (this.options.alwaysHide) this.options.wait = true;
-		if ($chk(this.options.show)){
-			this.options.display = false;
-			this.previous = this.options.show;
-		}
-		if (this.options.start){
-			this.options.display = false;
-			this.options.show = false;
-		}
-		this.effects = {};
-		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
-		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
-		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
-		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
-		this.elements.each(function(el, i){
-			if (this.options.show === i){
-				this.fireEvent('active', [this.togglers[i], el]);
-			} else {
-				for (var fx in this.effects) el.setStyle(fx, 0);
-			}
-		}, this);
-		if ($chk(this.options.display)) this.display(this.options.display, this.options.initialDisplayFx);
-		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
-	},
-
-	addSection: function(toggler, element){
-		toggler = document.id(toggler);
-		element = document.id(element);
-		var test = this.togglers.contains(toggler);
-		this.togglers.include(toggler);
-		this.elements.include(element);
-		var idx = this.togglers.indexOf(toggler);
-		var displayer = this.display.bind(this, idx);
-		toggler.store('accordion:display', displayer);
-		toggler.addEvent(this.options.trigger, displayer);
-		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
-		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
-		element.fullOpacity = 1;
-		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
-		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
-		element.setStyle('overflow', 'hidden');
-		if (!test){
-			for (var fx in this.effects) element.setStyle(fx, 0);
-		}
-		return this;
-	},
-
-	detach: function(){
-		this.togglers.each(function(toggler) {
-			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
-		}, this);
-	},
-
-	display: function(index, useFx){
-		if (!this.check(index, useFx)) return this;
-		useFx = $pick(useFx, true);
-		if (this.options.returnHeightToAuto) {
-			var prev = this.elements[this.previous];
-			if (prev) {
-				for (var fx in this.effects) {
-					prev.setStyle(fx, prev[this.effects[fx]]);
-				}
-			}
-		}
-		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
-		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
-		this.previous = index;
-		var obj = {};
-		this.elements.each(function(el, i){
-			obj[i] = {};
-			var hide = (i != index) || 
-						(this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || 
-							el.offsetWidth > 0 && this.options.width));
-			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
-			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
-		}, this);
-		this.internalChain.chain(function(){
-			if (this.options.returnHeightToAuto) {
-				var el = this.elements[index];
-				el.setStyle('height', 'auto');
-			};
-		}.bind(this));
-		return useFx ? this.start(obj) : this.set(obj);
-	}
-
-});/*
----
-
-script: Fx.Move.js
-
-description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Fx.Morph
-- /Element.Position
-
-provides: [Fx.Move]
-
-...
-*/
-
-Fx.Move = new Class({
-
-	Extends: Fx.Morph,
-
-	options: {
-		relativeTo: document.body,
-		position: 'center',
-		edge: false,
-		offset: {x: 0, y: 0}
-	},
-
-	start: function(destination){
-		return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
-	}
-
-});
-
-Element.Properties.move = {
-
-	set: function(options){
-		var morph = this.retrieve('move');
-		if (morph) morph.cancel();
-		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('move')){
-			if (options || !this.retrieve('move:options')) this.set('move', options);
-			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
-		}
-		return this.retrieve('move');
-	}
-
-};
-
-Element.implement({
-
-	move: function(options){
-		this.get('move').start(options);
-		return this;
-	}
-
-});
-/*
----
-
-script: Fx.Scroll.js
-
-description: Effect to smoothly scroll any element, including the window.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Fx
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Dimensions
-- /MooTools.More
-
-provides: [Fx.Scroll]
-
-...
-*/
-
-Fx.Scroll = new Class({
-
-	Extends: Fx,
-
-	options: {
-		offset: {x: 0, y: 0},
-		wheelStops: true
-	},
-
-	initialize: function(element, options){
-		this.element = this.subject = document.id(element);
-		this.parent(options);
-		var cancel = this.cancel.bind(this, false);
-
-		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
-
-		var stopper = this.element;
-
-		if (this.options.wheelStops){
-			this.addEvent('start', function(){
-				stopper.addEvent('mousewheel', cancel);
-			}, true);
-			this.addEvent('complete', function(){
-				stopper.removeEvent('mousewheel', cancel);
-			}, true);
-		}
-	},
-
-	set: function(){
-		var now = Array.flatten(arguments);
-		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
-		this.element.scrollTo(now[0], now[1]);
-	},
-
-	compute: function(from, to, delta){
-		return [0, 1].map(function(i){
-			return Fx.compute(from[i], to[i], delta);
-		});
-	},
-
-	start: function(x, y){
-		if (!this.check(x, y)) return this;
-		var scrollSize = this.element.getScrollSize(),
-			scroll = this.element.getScroll(), 
-			values = {x: x, y: y};
-		for (var z in values){
-			var max = scrollSize[z];
-			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
-			else values[z] = scroll[z];
-			values[z] += this.options.offset[z];
-		}
-		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
-	},
-
-	toTop: function(){
-		return this.start(false, 0);
-	},
-
-	toLeft: function(){
-		return this.start(0, false);
-	},
-
-	toRight: function(){
-		return this.start('right', false);
-	},
-
-	toBottom: function(){
-		return this.start(false, 'bottom');
-	},
-
-	toElement: function(el){
-		var position = document.id(el).getPosition(this.element);
-		return this.start(position.x, position.y);
-	},
-
-	scrollIntoView: function(el, axes, offset){
-		axes = axes ? $splat(axes) : ['x','y'];
-		var to = {};
-		el = document.id(el);
-		var pos = el.getPosition(this.element);
-		var size = el.getSize();
-		var scroll = this.element.getScroll();
-		var containerSize = this.element.getSize();
-		var edge = {
-			x: pos.x + size.x,
-			y: pos.y + size.y
-		};
-		['x','y'].each(function(axis) {
-			if (axes.contains(axis)) {
-				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
-				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
-			}
-			if (to[axis] == null) to[axis] = scroll[axis];
-			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
-		}, this);
-		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
-		return this;
-	},
-
-	scrollToCenter: function(el, axes, offset){
-		axes = axes ? $splat(axes) : ['x', 'y'];
-		el = $(el);
-		var to = {},
-			pos = el.getPosition(this.element),
-			size = el.getSize(),
-			scroll = this.element.getScroll(),
-			containerSize = this.element.getSize(),
-			edge = {
-				x: pos.x + size.x,
-				y: pos.y + size.y
-			};
-
-		['x','y'].each(function(axis){
-			if(axes.contains(axis)){
-				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
-			}
-			if(to[axis] == null) to[axis] = scroll[axis];
-			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
-		}, this);
-		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
-		return this;
-	}
-
-});
-/*
----
-
-script: Fx.Slide.js
-
-description: Effect to slide an element in and out of view.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Fx Element.Style
-- /MooTools.More
-
-provides: [Fx.Slide]
-
-...
-*/
-
-Fx.Slide = new Class({
-
-	Extends: Fx,
-
-	options: {
-		mode: 'vertical'
-	},
-
-	initialize: function(element, options){
-		this.addEvent('complete', function(){
-			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
-			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
-		}, true);
-		this.element = this.subject = document.id(element);
-		this.parent(options);
-		var wrapper = this.element.retrieve('wrapper');
-		this.wrapper = wrapper || new Element('div', {
-			styles: this.element.getStyles('margin', 'position', 'overflow')
-		}).wraps(this.element);
-		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
-		this.now = [];
-		this.open = true;
-	},
-
-	vertical: function(){
-		this.margin = 'margin-top';
-		this.layout = 'height';
-		this.offset = this.element.offsetHeight;
-	},
-
-	horizontal: function(){
-		this.margin = 'margin-left';
-		this.layout = 'width';
-		this.offset = this.element.offsetWidth;
-	},
-
-	set: function(now){
-		this.element.setStyle(this.margin, now[0]);
-		this.wrapper.setStyle(this.layout, now[1]);
-		return this;
-	},
-
-	compute: function(from, to, delta){
-		return [0, 1].map(function(i){
-			return Fx.compute(from[i], to[i], delta);
-		});
-	},
-
-	start: function(how, mode){
-		if (!this.check(how, mode)) return this;
-		this[mode || this.options.mode]();
-		var margin = this.element.getStyle(this.margin).toInt();
-		var layout = this.wrapper.getStyle(this.layout).toInt();
-		var caseIn = [[margin, layout], [0, this.offset]];
-		var caseOut = [[margin, layout], [-this.offset, 0]];
-		var start;
-		switch (how){
-			case 'in': start = caseIn; break;
-			case 'out': start = caseOut; break;
-			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
-		}
-		return this.parent(start[0], start[1]);
-	},
-
-	slideIn: function(mode){
-		return this.start('in', mode);
-	},
-
-	slideOut: function(mode){
-		return this.start('out', mode);
-	},
-
-	hide: function(mode){
-		this[mode || this.options.mode]();
-		this.open = false;
-		return this.set([-this.offset, 0]);
-	},
-
-	show: function(mode){
-		this[mode || this.options.mode]();
-		this.open = true;
-		return this.set([0, this.offset]);
-	},
-
-	toggle: function(mode){
-		return this.start('toggle', mode);
-	}
-
-});
-
-Element.Properties.slide = {
-
-	set: function(options){
-		var slide = this.retrieve('slide');
-		if (slide) slide.cancel();
-		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
-	},
-
-	get: function(options){
-		if (options || !this.retrieve('slide')){
-			if (options || !this.retrieve('slide:options')) this.set('slide', options);
-			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
-		}
-		return this.retrieve('slide');
-	}
-
-};
-
-Element.implement({
-
-	slide: function(how, mode){
-		how = how || 'toggle';
-		var slide = this.get('slide'), toggle;
-		switch (how){
-			case 'hide': slide.hide(mode); break;
-			case 'show': slide.show(mode); break;
-			case 'toggle':
-				var flag = this.retrieve('slide:flag', slide.open);
-				slide[flag ? 'slideOut' : 'slideIn'](mode);
-				this.store('slide:flag', !flag);
-				toggle = true;
-			break;
-			default: slide.start(how, mode);
-		}
-		if (!toggle) this.eliminate('slide:flag');
-		return this;
-	}
-
-});
-/*
----
-
-script: Fx.SmoothScroll.js
-
-description: Class for creating a smooth scrolling effect to all internal links on the page.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Selectors
-- /Fx.Scroll
-
-provides: [Fx.SmoothScroll]
-
-...
-*/
-
-var SmoothScroll = Fx.SmoothScroll = new Class({
-
-	Extends: Fx.Scroll,
-
-	initialize: function(options, context){
-		context = context || document;
-		this.doc = context.getDocument();
-		var win = context.getWindow();
-		this.parent(this.doc, options);
-		this.links = $$(this.options.links || this.doc.links);
-		var location = win.location.href.match(/^[^#]*/)[0] + '#';
-		this.links.each(function(link){
-			if (link.href.indexOf(location) != 0) {return;}
-			var anchor = link.href.substr(location.length);
-			if (anchor) this.useLink(link, anchor);
-		}, this);
-		if (!Browser.Engine.webkit419) {
-			this.addEvent('complete', function(){
-				win.location.hash = this.anchor;
-			}, true);
-		}
-	},
-
-	useLink: function(link, anchor){
-		var el;
-		link.addEvent('click', function(event){
-			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
-			if (el) {
-				event.preventDefault();
-				this.anchor = anchor;
-				this.toElement(el);
-				link.blur();
-			}
-		}.bind(this));
-	}
-});/*
----
-
-script: Fx.Sort.js
-
-description: Defines Fx.Sort, a class that reorders lists with a transition.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element.Dimensions
-- /Fx.Elements
-- /Element.Measure
-
-provides: [Fx.Sort]
-
-...
-*/
-
-Fx.Sort = new Class({
-
-	Extends: Fx.Elements,
-
-	options: {
-		mode: 'vertical'
-	},
-
-	initialize: function(elements, options){
-		this.parent(elements, options);
-		this.elements.each(function(el){
-			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
-		});
-		this.setDefaultOrder();
-	},
-
-	setDefaultOrder: function(){
-		this.currentOrder = this.elements.map(function(el, index){
-			return index;
-		});
-	},
-
-	sort: function(newOrder){
-		if ($type(newOrder) != 'array') return false;
-		var top = 0,
-			left = 0,
-			next = {},
-			zero = {},
-			vert = this.options.mode == 'vertical';
-		var current = this.elements.map(function(el, index){
-			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
-			var val;
-			if (vert){
-				val = {
-					top: top,
-					margin: size['margin-top'],
-					height: size.totalHeight
-				};
-				top += val.height - size['margin-top'];
-			} else {
-				val = {
-					left: left,
-					margin: size['margin-left'],
-					width: size.totalWidth
-				};
-				left += val.width;
-			}
-			var plain = vert ? 'top' : 'left';
-			zero[index] = {};
-			var start = el.getStyle(plain).toInt();
-			zero[index][plain] = start || 0;
-			return val;
-		}, this);
-		this.set(zero);
-		newOrder = newOrder.map(function(i){ return i.toInt(); });
-		if (newOrder.length != this.elements.length){
-			this.currentOrder.each(function(index){
-				if (!newOrder.contains(index)) newOrder.push(index);
-			});
-			if (newOrder.length > this.elements.length)
-				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
-		}
-		var margin = top = left = 0;
-		newOrder.each(function(item, index){
-			var newPos = {};
-			if (vert){
-				newPos.top = top - current[item].top - margin;
-				top += current[item].height;
-			} else {
-				newPos.left = left - current[item].left;
-				left += current[item].width;
-			}
-			margin = margin + current[item].margin;
-			next[item]=newPos;
-		}, this);
-		var mapped = {};
-		$A(newOrder).sort().each(function(index){
-			mapped[index] = next[index];
-		});
-		this.start(mapped);
-		this.currentOrder = newOrder;
-		return this;
-	},
-
-	rearrangeDOM: function(newOrder){
-		newOrder = newOrder || this.currentOrder;
-		var parent = this.elements[0].getParent();
-		var rearranged = [];
-		this.elements.setStyle('opacity', 0);
-		//move each element and store the new default order
-		newOrder.each(function(index){
-			rearranged.push(this.elements[index].inject(parent).setStyles({
-				top: 0,
-				left: 0
-			}));
-		}, this);
-		this.elements.setStyle('opacity', 1);
-		this.elements = $$(rearranged);
-		this.setDefaultOrder();
-		return this;
-	},
-
-	getDefaultOrder: function(){
-		return this.elements.map(function(el, index){
-			return index;
-		});
-	},
-
-	forward: function(){
-		return this.sort(this.getDefaultOrder());
-	},
-
-	backward: function(){
-		return this.sort(this.getDefaultOrder().reverse());
-	},
-
-	reverse: function(){
-		return this.sort(this.currentOrder.reverse());
-	},
-
-	sortByElements: function(elements){
-		return this.sort(elements.map(function(el){
-			return this.elements.indexOf(el);
-		}, this));
-	},
-
-	swap: function(one, two){
-		if ($type(one) == 'element') one = this.elements.indexOf(one);
-		if ($type(two) == 'element') two = this.elements.indexOf(two);
-		
-		var newOrder = $A(this.currentOrder);
-		newOrder[this.currentOrder.indexOf(one)] = two;
-		newOrder[this.currentOrder.indexOf(two)] = one;
-		return this.sort(newOrder);
-	}
-
-});/*
----
-
-script: Drag.js
-
-description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-- Tom Occhinno
-- Jan Kassens
-
-requires:
-- core:1.2.4/Events
-- core:1.2.4/Options
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Style
-- /MooTools.More
-
-provides: [Drag]
-
-*/
-
-var Drag = new Class({
-
-	Implements: [Events, Options],
-
-	options: {/*
-		onBeforeStart: $empty(thisElement),
-		onStart: $empty(thisElement, event),
-		onSnap: $empty(thisElement)
-		onDrag: $empty(thisElement, event),
-		onCancel: $empty(thisElement),
-		onComplete: $empty(thisElement, event),*/
-		snap: 6,
-		unit: 'px',
-		grid: false,
-		style: true,
-		limit: false,
-		handle: false,
-		invert: false,
-		preventDefault: false,
-		modifiers: {x: 'left', y: 'top'}
-	},
-
-	initialize: function(){
-		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
-		this.element = document.id(params.element);
-		this.document = this.element.getDocument();
-		this.setOptions(params.options || {});
-		var htype = $type(this.options.handle);
-		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
-		this.mouse = {'now': {}, 'pos': {}};
-		this.value = {'start': {}, 'now': {}};
-
-		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
-
-		this.bound = {
-			start: this.start.bind(this),
-			check: this.check.bind(this),
-			drag: this.drag.bind(this),
-			stop: this.stop.bind(this),
-			cancel: this.cancel.bind(this),
-			eventStop: $lambda(false)
-		};
-		this.attach();
-	},
-
-	attach: function(){
-		this.handles.addEvent('mousedown', this.bound.start);
-		return this;
-	},
-
-	detach: function(){
-		this.handles.removeEvent('mousedown', this.bound.start);
-		return this;
-	},
-
-	start: function(event){
-		if (event.rightClick) return;
-		if (this.options.preventDefault) event.preventDefault();
-		this.mouse.start = event.page;
-		this.fireEvent('beforeStart', this.element);
-		var limit = this.options.limit;
-		this.limit = {x: [], y: []};
-		for (var z in this.options.modifiers){
-			if (!this.options.modifiers[z]) continue;
-			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
-			else this.value.now[z] = this.element[this.options.modifiers[z]];
-			if (this.options.invert) this.value.now[z] *= -1;
-			this.mouse.pos[z] = event.page[z] - this.value.now[z];
-			if (limit && limit[z]){
-				for (var i = 2; i--; i){
-					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
-				}
-			}
-		}
-		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
-		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
-		this.document.addEvent(this.selection, this.bound.eventStop);
-	},
-
-	check: function(event){
-		if (this.options.preventDefault) event.preventDefault();
-		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
-		if (distance > this.options.snap){
-			this.cancel();
-			this.document.addEvents({
-				mousemove: this.bound.drag,
-				mouseup: this.bound.stop
-			});
-			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
-		}
-	},
-
-	drag: function(event){
-		if (this.options.preventDefault) event.preventDefault();
-		this.mouse.now = event.page;
-		for (var z in this.options.modifiers){
-			if (!this.options.modifiers[z]) continue;
-			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
-			if (this.options.invert) this.value.now[z] *= -1;
-			if (this.options.limit && this.limit[z]){
-				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
-					this.value.now[z] = this.limit[z][1];
-				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
-					this.value.now[z] = this.limit[z][0];
-				}
-			}
-			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
-			if (this.options.style) {
-				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
-			} else {
-				this.element[this.options.modifiers[z]] = this.value.now[z];
-			}
-		}
-		this.fireEvent('drag', [this.element, event]);
-	},
-
-	cancel: function(event){
-		this.document.removeEvent('mousemove', this.bound.check);
-		this.document.removeEvent('mouseup', this.bound.cancel);
-		if (event){
-			this.document.removeEvent(this.selection, this.bound.eventStop);
-			this.fireEvent('cancel', this.element);
-		}
-	},
-
-	stop: function(event){
-		this.document.removeEvent(this.selection, this.bound.eventStop);
-		this.document.removeEvent('mousemove', this.bound.drag);
-		this.document.removeEvent('mouseup', this.bound.stop);
-		if (event) this.fireEvent('complete', [this.element, event]);
-	}
-
-});
-
-Element.implement({
-
-	makeResizable: function(options){
-		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
-		this.store('resizer', drag);
-		return drag.addEvent('drag', function(){
-			this.fireEvent('resize', drag);
-		}.bind(this));
-	}
-
-});
-/*
----
-
-script: Drag.Move.js
-
-description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-- Tom Occhinno
-- Jan Kassens
-- Aaron Newton
-- Scott Kyle
-
-requires:
-- core:1.2.4/Element.Dimensions
-- /Drag
-
-provides: [Drag.Move]
-
-...
-*/
-
-Drag.Move = new Class({
-
-	Extends: Drag,
-
-	options: {/*
-		onEnter: $empty(thisElement, overed),
-		onLeave: $empty(thisElement, overed),
-		onDrop: $empty(thisElement, overed, event),*/
-		droppables: [],
-		container: false,
-		precalculate: false,
-		includeMargins: true,
-		checkDroppables: true
-	},
-
-	initialize: function(element, options){
-		this.parent(element, options);
-		element = this.element;
-		
-		this.droppables = $$(this.options.droppables);
-		this.container = document.id(this.options.container);
-		
-		if (this.container && $type(this.container) != 'element')
-			this.container = document.id(this.container.getDocument().body);
-		
-		var styles = element.getStyles('left', 'right', 'position');
-		if (styles.left == 'auto' || styles.top == 'auto')
-			element.setPosition(element.getPosition(element.getOffsetParent()));
-		
-		if (styles.position == 'static')
-			element.setStyle('position', 'absolute');
-
-		this.addEvent('start', this.checkDroppables, true);
-
-		this.overed = null;
-	},
-
-	start: function(event){
-		if (this.container) this.options.limit = this.calculateLimit();
-		
-		if (this.options.precalculate){
-			this.positions = this.droppables.map(function(el){
-				return el.getCoordinates();
-			});
-		}
-		
-		this.parent(event);
-	},
-	
-	calculateLimit: function(){
-		var offsetParent = this.element.getOffsetParent(),
-			containerCoordinates = this.container.getCoordinates(offsetParent),
-			containerBorder = {},
-			elementMargin = {},
-			elementBorder = {},
-			containerMargin = {},
-			offsetParentPadding = {};
-
-		['top', 'right', 'bottom', 'left'].each(function(pad){
-			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
-			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
-			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
-			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
-			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
-		}, this);
-
-		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
-			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
-			left = 0,
-			top = 0,
-			right = containerCoordinates.right - containerBorder.right - width,
-			bottom = containerCoordinates.bottom - containerBorder.bottom - height;
-
-		if (this.options.includeMargins){
-			left += elementMargin.left;
-			top += elementMargin.top;
-		} else {
-			right += elementMargin.right;
-			bottom += elementMargin.bottom;
-		}
-		
-		if (this.element.getStyle('position') == 'relative'){
-			var coords = this.element.getCoordinates(offsetParent);
-			coords.left -= this.element.getStyle('left').toInt();
-			coords.top -= this.element.getStyle('top').toInt();
-			
-			left += containerBorder.left - coords.left;
-			top += containerBorder.top - coords.top;
-			right += elementMargin.left - coords.left;
-			bottom += elementMargin.top - coords.top;
-			
-			if (this.container != offsetParent){
-				left += containerMargin.left + offsetParentPadding.left;
-				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
-			}
-		} else {
-			left -= elementMargin.left;
-			top -= elementMargin.top;
-			
-			if (this.container == offsetParent){
-				right -= containerBorder.left;
-				bottom -= containerBorder.top;
-			} else {
-				left += containerCoordinates.left + containerBorder.left;
-				top += containerCoordinates.top + containerBorder.top;
-			}
-		}
-		
-		return {
-			x: [left, right],
-			y: [top, bottom]
-		};
-	},
-
-	checkAgainst: function(el, i){
-		el = (this.positions) ? this.positions[i] : el.getCoordinates();
-		var now = this.mouse.now;
-		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
-	},
-
-	checkDroppables: function(){
-		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
-		if (this.overed != overed){
-			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
-			if (overed) this.fireEvent('enter', [this.element, overed]);
-			this.overed = overed;
-		}
-	},
-
-	drag: function(event){
-		this.parent(event);
-		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
-	},
-
-	stop: function(event){
-		this.checkDroppables();
-		this.fireEvent('drop', [this.element, this.overed, event]);
-		this.overed = null;
-		return this.parent(event);
-	}
-
-});
-
-Element.implement({
-
-	makeDraggable: function(options){
-		var drag = new Drag.Move(this, options);
-		this.store('dragger', drag);
-		return drag;
-	}
-
-});
-/*
----
-
-script: Slider.js
-
-description: Class for creating horizontal and vertical slider controls.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Element.Dimensions
-- /Class.Binds
-- /Drag
-- /Element.Dimensions
-- /Element.Measure
-
-provides: [Slider]
-
-...
-*/
-
-var Slider = new Class({
-
-	Implements: [Events, Options],
-
-	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
-
-	options: {/*
-		onTick: $empty(intPosition),
-		onChange: $empty(intStep),
-		onComplete: $empty(strStep),*/
-		onTick: function(position){
-			if (this.options.snap) position = this.toPosition(this.step);
-			this.knob.setStyle(this.property, position);
-		},
-		initialStep: 0,
-		snap: false,
-		offset: 0,
-		range: false,
-		wheel: false,
-		steps: 100,
-		mode: 'horizontal'
-	},
-
-	initialize: function(element, knob, options){
-		this.setOptions(options);
-		this.element = document.id(element);
-		this.knob = document.id(knob);
-		this.previousChange = this.previousEnd = this.step = -1;
-		var offset, limit = {}, modifiers = {'x': false, 'y': false};
-		switch (this.options.mode){
-			case 'vertical':
-				this.axis = 'y';
-				this.property = 'top';
-				offset = 'offsetHeight';
-				break;
-			case 'horizontal':
-				this.axis = 'x';
-				this.property = 'left';
-				offset = 'offsetWidth';
-		}
-		
-		this.full = this.element.measure(function(){ 
-			this.half = this.knob[offset] / 2; 
-			return this.element[offset] - this.knob[offset] + (this.options.offset * 2); 
-		}.bind(this));
-		
-		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
-		this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
-		this.range = this.max - this.min;
-		this.steps = this.options.steps || this.full;
-		this.stepSize = Math.abs(this.range) / this.steps;
-		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
-
-		this.knob.setStyle('position', 'relative').setStyle(this.property, this.options.initialStep ? this.toPosition(this.options.initialStep) : - this.options.offset);
-		modifiers[this.axis] = this.property;
-		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];
-
-		var dragOptions = {
-			snap: 0,
-			limit: limit,
-			modifiers: modifiers,
-			onDrag: this.draggedKnob,
-			onStart: this.draggedKnob,
-			onBeforeStart: (function(){
-				this.isDragging = true;
-			}).bind(this),
-			onCancel: function() {
-				this.isDragging = false;
-			}.bind(this),
-			onComplete: function(){
-				this.isDragging = false;
-				this.draggedKnob();
-				this.end();
-			}.bind(this)
-		};
-		if (this.options.snap){
-			dragOptions.grid = Math.ceil(this.stepWidth);
-			dragOptions.limit[this.axis][1] = this.full;
-		}
-
-		this.drag = new Drag(this.knob, dragOptions);
-		this.attach();
-	},
-
-	attach: function(){
-		this.element.addEvent('mousedown', this.clickedElement);
-		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
-		this.drag.attach();
-		return this;
-	},
-
-	detach: function(){
-		this.element.removeEvent('mousedown', this.clickedElement);
-		this.element.removeEvent('mousewheel', this.scrolledElement);
-		this.drag.detach();
-		return this;
-	},
-
-	set: function(step){
-		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
-		if (!((this.range > 0) ^ (step > this.max))) step = this.max;
-
-		this.step = Math.round(step);
-		this.checkStep();
-		this.fireEvent('tick', this.toPosition(this.step));
-		this.end();
-		return this;
-	},
-
-	clickedElement: function(event){
-		if (this.isDragging || event.target == this.knob) return;
-
-		var dir = this.range < 0 ? -1 : 1;
-		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
-		position = position.limit(-this.options.offset, this.full -this.options.offset);
-
-		this.step = Math.round(this.min + dir * this.toStep(position));
-		this.checkStep();
-		this.fireEvent('tick', position);
-		this.end();
-	},
-
-	scrolledElement: function(event){
-		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
-		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
-		event.stop();
-	},
-
-	draggedKnob: function(){
-		var dir = this.range < 0 ? -1 : 1;
-		var position = this.drag.value.now[this.axis];
-		position = position.limit(-this.options.offset, this.full -this.options.offset);
-		this.step = Math.round(this.min + dir * this.toStep(position));
-		this.checkStep();
-	},
-
-	checkStep: function(){
-		if (this.previousChange != this.step){
-			this.previousChange = this.step;
-			this.fireEvent('change', this.step);
-		}
-	},
-
-	end: function(){
-		if (this.previousEnd !== this.step){
-			this.previousEnd = this.step;
-			this.fireEvent('complete', this.step + '');
-		}
-	},
-
-	toStep: function(position){
-		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
-		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
-	},
-
-	toPosition: function(step){
-		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
-	}
-
-});/*
----
-
-script: Sortables.js
-
-description: Class for creating a drag and drop sorting interface for lists of items.
-
-license: MIT-style license
-
-authors:
-- Tom Occhino
-
-requires:
-- /Drag.Move
-
-provides: [Slider]
-
-...
-*/
-
-var Sortables = new Class({
-
-	Implements: [Events, Options],
-
-	options: {/*
-		onSort: $empty(element, clone),
-		onStart: $empty(element, clone),
-		onComplete: $empty(element),*/
-		snap: 4,
-		opacity: 1,
-		clone: false,
-		revert: false,
-		handle: false,
-		constrain: false
-	},
-
-	initialize: function(lists, options){
-		this.setOptions(options);
-		this.elements = [];
-		this.lists = [];
-		this.idle = true;
-
-		this.addLists($$(document.id(lists) || lists));
-		if (!this.options.clone) this.options.revert = false;
-		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
-	},
-
-	attach: function(){
-		this.addLists(this.lists);
-		return this;
-	},
-
-	detach: function(){
-		this.lists = this.removeLists(this.lists);
-		return this;
-	},
-
-	addItems: function(){
-		Array.flatten(arguments).each(function(element){
-			this.elements.push(element);
-			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
-			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
-		}, this);
-		return this;
-	},
-
-	addLists: function(){
-		Array.flatten(arguments).each(function(list){
-			this.lists.push(list);
-			this.addItems(list.getChildren());
-		}, this);
-		return this;
-	},
-
-	removeItems: function(){
-		return $$(Array.flatten(arguments).map(function(element){
-			this.elements.erase(element);
-			var start = element.retrieve('sortables:start');
-			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
-			
-			return element;
-		}, this));
-	},
-
-	removeLists: function(){
-		return $$(Array.flatten(arguments).map(function(list){
-			this.lists.erase(list);
-			this.removeItems(list.getChildren());
-			
-			return list;
-		}, this));
-	},
-
-	getClone: function(event, element){
-		if (!this.options.clone) return new Element('div').inject(document.body);
-		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
-		return element.clone(true).setStyles({
-			margin: '0px',
-			position: 'absolute',
-			visibility: 'hidden',
-			'width': element.getStyle('width')
-		}).inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
-	},
-
-	getDroppables: function(){
-		var droppables = this.list.getChildren();
-		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
-		return droppables.erase(this.clone).erase(this.element);
-	},
-
-	insert: function(dragging, element){
-		var where = 'inside';
-		if (this.lists.contains(element)){
-			this.list = element;
-			this.drag.droppables = this.getDroppables();
-		} else {
-			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
-		}
-		this.element.inject(element, where);
-		this.fireEvent('sort', [this.element, this.clone]);
-	},
-
-	start: function(event, element){
-		if (!this.idle) return;
-		this.idle = false;
-		this.element = element;
-		this.opacity = element.get('opacity');
-		this.list = element.getParent();
-		this.clone = this.getClone(event, element);
-
-		this.drag = new Drag.Move(this.clone, {
-			snap: this.options.snap,
-			container: this.options.constrain && this.element.getParent(),
-			droppables: this.getDroppables(),
-			onSnap: function(){
-				event.stop();
-				this.clone.setStyle('visibility', 'visible');
-				this.element.set('opacity', this.options.opacity || 0);
-				this.fireEvent('start', [this.element, this.clone]);
-			}.bind(this),
-			onEnter: this.insert.bind(this),
-			onCancel: this.reset.bind(this),
-			onComplete: this.end.bind(this)
-		});
-
-		this.clone.inject(this.element, 'before');
-		this.drag.start(event);
-	},
-
-	end: function(){
-		this.drag.detach();
-		this.element.set('opacity', this.opacity);
-		if (this.effect){
-			var dim = this.element.getStyles('width', 'height');
-			var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
-			this.effect.element = this.clone;
-			this.effect.start({
-				top: pos.top,
-				left: pos.left,
-				width: dim.width,
-				height: dim.height,
-				opacity: 0.25
-			}).chain(this.reset.bind(this));
-		} else {
-			this.reset();
-		}
-	},
-
-	reset: function(){
-		this.idle = true;
-		this.clone.destroy();
-		this.fireEvent('complete', this.element);
-	},
-
-	serialize: function(){
-		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
-		var serial = this.lists.map(function(list){
-			return list.getChildren().map(params.modifier || function(element){
-				return element.get('id');
-			}, this);
-		}, this);
-
-		var index = params.index;
-		if (this.lists.length == 1) index = 0;
-		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
-	}
-
-});
-/*
----
-
-script: Request.JSONP.js
-
-description: Defines Request.JSONP, a class for cross domain javascript via script injection.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-- Guillermo Rauch
-
-requires:
-- core:1.2.4/Element
-- core:1.2.4/Request
-- /Log
-
-provides: [Request.JSONP]
-
-...
-*/
-
-Request.JSONP = new Class({
-
-	Implements: [Chain, Events, Options, Log],
-
-	options: {/*
-		onRetry: $empty(intRetries),
-		onRequest: $empty(scriptElement),
-		onComplete: $empty(data),
-		onSuccess: $empty(data),
-		onCancel: $empty(),
-		log: false,
-		*/
-		url: '',
-		data: {},
-		retries: 0,
-		timeout: 0,
-		link: 'ignore',
-		callbackKey: 'callback',
-		injectScript: document.head
-	},
-
-	initialize: function(options){
-		this.setOptions(options);
-		if (this.options.log) this.enableLog();
-		this.running = false;
-		this.requests = 0;
-		this.triesRemaining = [];
-	},
-
-	check: function(){
-		if (!this.running) return true;
-		switch (this.options.link){
-			case 'cancel': this.cancel(); return true;
-			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
-		}
-		return false;
-	},
-
-	send: function(options){
-		if (!$chk(arguments[1]) && !this.check(options)) return this;
-
-		var type = $type(options), 
-				old = this.options, 
-				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
-		if (type == 'string' || type == 'element') options = {data: options};
-
-		options = $extend({data: old.data, url: old.url}, options);
-
-		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
-		var remaining = this.triesRemaining[index];
-
-		(function(){
-			var script = this.getScript(options);
-			this.log('JSONP retrieving script with url: ' + script.get('src'));
-			this.fireEvent('request', script);
-			this.running = true;
-
-			(function(){
-				if (remaining){
-					this.triesRemaining[index] = remaining - 1;
-					if (script){
-						script.destroy();
-						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
-					}
-				} else if(script && this.options.timeout){
-					script.destroy();
-					this.cancel().fireEvent('failure');
-				}
-			}).delay(this.options.timeout, this);
-		}).delay(Browser.Engine.trident ? 50 : 0, this);
-		return this;
-	},
-
-	cancel: function(){
-		if (!this.running) return this;
-		this.running = false;
-		this.fireEvent('cancel');
-		return this;
-	},
-
-	getScript: function(options){
-		var index = Request.JSONP.counter,
-				data;
-		Request.JSONP.counter++;
-
-		switch ($type(options.data)){
-			case 'element': data = document.id(options.data).toQueryString(); break;
-			case 'object': case 'hash': data = Hash.toQueryString(options.data);
-		}
-
-		var src = options.url + 
-			 (options.url.test('\\?') ? '&' :'?') + 
-			 (options.callbackKey || this.options.callbackKey) + 
-			 '=Request.JSONP.request_map.request_'+ index + 
-			 (data ? '&' + data : '');
-		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
-
-		var script = new Element('script', {type: 'text/javascript', src: src});
-		Request.JSONP.request_map['request_' + index] = function(data){ this.success(data, script); }.bind(this);
-		return script.inject(this.options.injectScript);
-	},
-
-	success: function(data, script){
-		if (script) script.destroy();
-		this.running = false;
-		this.log('JSONP successfully retrieved: ', data);
-		this.fireEvent('complete', [data]).fireEvent('success', [data]).callChain();
-	}
-
-});
-
-Request.JSONP.counter = 0;
-Request.JSONP.request_map = {};/*
----
-
-script: Request.Queue.js
-
-description: Controls several instances of Request and its variants to run only one request at a time.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Element
-- core:1.2.4/Request
-- /Log
-
-provides: [Request.Queue]
-
-...
-*/
-
-Request.Queue = new Class({
-
-	Implements: [Options, Events],
-
-	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
-
-	options: {/*
-		onRequest: $empty(argsPassedToOnRequest),
-		onSuccess: $empty(argsPassedToOnSuccess),
-		onComplete: $empty(argsPassedToOnComplete),
-		onCancel: $empty(argsPassedToOnCancel),
-		onException: $empty(argsPassedToOnException),
-		onFailure: $empty(argsPassedToOnFailure),
-		onEnd: $empty,
-		*/
-		stopOnFailure: true,
-		autoAdvance: true,
-		concurrent: 1,
-		requests: {}
-	},
-
-	initialize: function(options){
-		if(options){
-			var requests = options.requests;
-			delete options.requests;	
-		}
-		this.setOptions(options);
-		this.requests = new Hash;
-		this.queue = [];
-		this.reqBinders = {};
-		
-		if(requests) this.addRequests(requests);
-	},
-
-	addRequest: function(name, request){
-		this.requests.set(name, request);
-		this.attach(name, request);
-		return this;
-	},
-
-	addRequests: function(obj){
-		$each(obj, function(req, name){
-			this.addRequest(name, req);
-		}, this);
-		return this;
-	},
-
-	getName: function(req){
-		return this.requests.keyOf(req);
-	},
-
-	attach: function(name, req){
-		if (req._groupSend) return this;
-		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
-			if(!this.reqBinders[name]) this.reqBinders[name] = {};
-			this.reqBinders[name][evt] = function(){
-				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
-			}.bind(this);
-			req.addEvent(evt, this.reqBinders[name][evt]);
-		}, this);
-		req._groupSend = req.send;
-		req.send = function(options){
-			this.send(name, options);
-			return req;
-		}.bind(this);
-		return this;
-	},
-
-	removeRequest: function(req){
-		var name = $type(req) == 'object' ? this.getName(req) : req;
-		if (!name && $type(name) != 'string') return this;
-		req = this.requests.get(name);
-		if (!req) return this;
-		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
-			req.removeEvent(evt, this.reqBinders[name][evt]);
-		}, this);
-		req.send = req._groupSend;
-		delete req._groupSend;
-		return this;
-	},
-
-	getRunning: function(){
-		return this.requests.filter(function(r){
-			return r.running;
-		});
-	},
-
-	isRunning: function(){
-		return !!(this.getRunning().getKeys().length);
-	},
-
-	send: function(name, options){
-		var q = function(){
-			this.requests.get(name)._groupSend(options);
-			this.queue.erase(q);
-		}.bind(this);
-		q.name = name;
-		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
-		else q();
-		return this;
-	},
-
-	hasNext: function(name){
-		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
-	},
-
-	resume: function(){
-		this.error = false;
-		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
-		return this;
-	},
-
-	runNext: function(name){
-		if (!this.queue.length) return this;
-		if (!name){
-			this.queue[0]();
-		} else {
-			var found;
-			this.queue.each(function(q){
-				if (!found && q.name == name){
-					found = true;
-					q();
-				}
-			});
-		}
-		return this;
-	},
-
-	runAll: function() {
-		this.queue.each(function(q) {
-			q();
-		});
-		return this;
-	},
-
-	clear: function(name){
-		if (!name){
-			this.queue.empty();
-		} else {
-			this.queue = this.queue.map(function(q){
-				if (q.name != name) return q;
-				else return false;
-			}).filter(function(q){ return q; });
-		}
-		return this;
-	},
-
-	cancel: function(name){
-		this.requests.get(name).cancel();
-		return this;
-	},
-
-	onRequest: function(){
-		this.fireEvent('request', arguments);
-	},
-
-	onComplete: function(){
-		this.fireEvent('complete', arguments);
-		if (!this.queue.length) this.fireEvent('end');
-	},
-
-	onCancel: function(){
-		if (this.options.autoAdvance && !this.error) this.runNext();
-		this.fireEvent('cancel', arguments);
-	},
-
-	onSuccess: function(){
-		if (this.options.autoAdvance && !this.error) this.runNext();
-		this.fireEvent('success', arguments);
-	},
-
-	onFailure: function(){
-		this.error = true;
-		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
-		this.fireEvent('failure', arguments);
-	},
-
-	onException: function(){
-		this.error = true;
-		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
-		this.fireEvent('exception', arguments);
-	}
-
-});
-/*
----
-
-script: Request.Periodical.js
-
-description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
-
-license: MIT-style license
-
-authors:
-- Christoph Pojer
-
-requires:
-- core:1.2.4/Request
-- /MooTools.More
-
-provides: [Request.Periodical]
-
-...
-*/
-
-Request.implement({
-
-	options: {
-		initialDelay: 5000,
-		delay: 5000,
-		limit: 60000
-	},
-
-	startTimer: function(data){
-		var fn = function(){
-			if (!this.running) this.send({data: data});
-		};
-		this.timer = fn.delay(this.options.initialDelay, this);
-		this.lastDelay = this.options.initialDelay;
-		this.completeCheck = function(response){
-			$clear(this.timer);
-			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
-			this.timer = fn.delay(this.lastDelay, this);
-		};
-		return this.addEvent('complete', this.completeCheck);
-	},
-
-	stopTimer: function(){
-		$clear(this.timer);
-		return this.removeEvent('complete', this.completeCheck);
-	}
-
-});/*
----
-
-script: Assets.js
-
-description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Element.Event
-- /MooTools.More
-
-provides: [Assets]
-
-...
-*/
-
-var Asset = {
-
-	javascript: function(source, properties){
-		properties = $extend({
-			onload: $empty,
-			document: document,
-			check: $lambda(true)
-		}, properties);
-
-		var script = new Element('script', {src: source, type: 'text/javascript'});
-
-		var load = properties.onload.bind(script), 
-			check = properties.check, 
-			doc = properties.document;
-		delete properties.onload;
-		delete properties.check;
-		delete properties.document;
-
-		script.addEvents({
-			load: load,
-			readystatechange: function(){
-				if (['loaded', 'complete'].contains(this.readyState)) load();
-			}
-		}).set(properties);
-
-		if (Browser.Engine.webkit419) var checker = (function(){
-			if (!$try(check)) return;
-			$clear(checker);
-			load();
-		}).periodical(50);
-
-		return script.inject(doc.head);
-	},
-
-	css: function(source, properties){
-		return new Element('link', $merge({
-			rel: 'stylesheet',
-			media: 'screen',
-			type: 'text/css',
-			href: source
-		}, properties)).inject(document.head);
-	},
-
-	image: function(source, properties){
-		properties = $merge({
-			onload: $empty,
-			onabort: $empty,
-			onerror: $empty
-		}, properties);
-		var image = new Image();
-		var element = document.id(image) || new Element('img');
-		['load', 'abort', 'error'].each(function(name){
-			var type = 'on' + name;
-			var event = properties[type];
-			delete properties[type];
-			image[type] = function(){
-				if (!image) return;
-				if (!element.parentNode){
-					element.width = image.width;
-					element.height = image.height;
-				}
-				image = image.onload = image.onabort = image.onerror = null;
-				event.delay(1, element, element);
-				element.fireEvent(name, element, 1);
-			};
-		});
-		image.src = element.src = source;
-		if (image && image.complete) image.onload.delay(1);
-		return element.set(properties);
-	},
-
-	images: function(sources, options){
-		options = $merge({
-			onComplete: $empty,
-			onProgress: $empty,
-			onError: $empty,
-			properties: {}
-		}, options);
-		sources = $splat(sources);
-		var images = [];
-		var counter = 0;
-		return new Elements(sources.map(function(source){
-			return Asset.image(source, $extend(options.properties, {
-				onload: function(){
-					options.onProgress.call(this, counter, sources.indexOf(source));
-					counter++;
-					if (counter == sources.length) options.onComplete();
-				},
-				onerror: function(){
-					options.onError.call(this, counter, sources.indexOf(source));
-					counter++;
-					if (counter == sources.length) options.onComplete();
-				}
-			}));
-		}));
-	}
-
-};/*
----
-
-script: Color.js
-
-description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Array
-- core:1.2.4/String
-- core:1.2.4/Number
-- core:1.2.4/Hash
-- core:1.2.4/Function
-- core:1.2.4/$util
-
-provides: [Color]
-
-...
-*/
-
-var Color = new Native({
-
-	initialize: function(color, type){
-		if (arguments.length >= 3){
-			type = 'rgb'; color = Array.slice(arguments, 0, 3);
-		} else if (typeof color == 'string'){
-			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
-			else if (color.match(/hsb/)) color = color.hsbToRgb();
-			else color = color.hexToRgb(true);
-		}
-		type = type || 'rgb';
-		switch (type){
-			case 'hsb':
-				var old = color;
-				color = color.hsbToRgb();
-				color.hsb = old;
-			break;
-			case 'hex': color = color.hexToRgb(true); break;
-		}
-		color.rgb = color.slice(0, 3);
-		color.hsb = color.hsb || color.rgbToHsb();
-		color.hex = color.rgbToHex();
-		return $extend(color, this);
-	}
-
-});
-
-Color.implement({
-
-	mix: function(){
-		var colors = Array.slice(arguments);
-		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
-		var rgb = this.slice();
-		colors.each(function(color){
-			color = new Color(color);
-			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
-		});
-		return new Color(rgb, 'rgb');
-	},
-
-	invert: function(){
-		return new Color(this.map(function(value){
-			return 255 - value;
-		}));
-	},
-
-	setHue: function(value){
-		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
-	},
-
-	setSaturation: function(percent){
-		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
-	},
-
-	setBrightness: function(percent){
-		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
-	}
-
-});
-
-var $RGB = function(r, g, b){
-	return new Color([r, g, b], 'rgb');
-};
-
-var $HSB = function(h, s, b){
-	return new Color([h, s, b], 'hsb');
-};
-
-var $HEX = function(hex){
-	return new Color(hex, 'hex');
-};
-
-Array.implement({
-
-	rgbToHsb: function(){
-		var red = this[0],
-				green = this[1],
-				blue = this[2],
-				hue = 0;
-		var max = Math.max(red, green, blue),
-				min = Math.min(red, green, blue);
-		var delta = max - min;
-		var brightness = max / 255,
-				saturation = (max != 0) ? delta / max : 0;
-		if(saturation != 0) {
-			var rr = (max - red) / delta;
-			var gr = (max - green) / delta;
-			var br = (max - blue) / delta;
-			if (red == max) hue = br - gr;
-			else if (green == max) hue = 2 + rr - br;
-			else hue = 4 + gr - rr;
-			hue /= 6;
-			if (hue < 0) hue++;
-		}
-		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
-	},
-
-	hsbToRgb: function(){
-		var br = Math.round(this[2] / 100 * 255);
-		if (this[1] == 0){
-			return [br, br, br];
-		} else {
-			var hue = this[0] % 360;
-			var f = hue % 60;
-			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
-			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
-			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
-			switch (Math.floor(hue / 60)){
-				case 0: return [br, t, p];
-				case 1: return [q, br, p];
-				case 2: return [p, br, t];
-				case 3: return [p, q, br];
-				case 4: return [t, p, br];
-				case 5: return [br, p, q];
-			}
-		}
-		return false;
-	}
-
-});
-
-String.implement({
-
-	rgbToHsb: function(){
-		var rgb = this.match(/\d{1,3}/g);
-		return (rgb) ? rgb.rgbToHsb() : null;
-	},
-
-	hsbToRgb: function(){
-		var hsb = this.match(/\d{1,3}/g);
-		return (hsb) ? hsb.hsbToRgb() : null;
-	}
-
-});
-/*
----
-
-script: Group.js
-
-description: Class for monitoring collections of events
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Events
-- /MooTools.More
-
-provides: [Group]
-
-...
-*/
-
-var Group = new Class({
-
-	initialize: function(){
-		this.instances = Array.flatten(arguments);
-		this.events = {};
-		this.checker = {};
-	},
-
-	addEvent: function(type, fn){
-		this.checker[type] = this.checker[type] || {};
-		this.events[type] = this.events[type] || [];
-		if (this.events[type].contains(fn)) return false;
-		else this.events[type].push(fn);
-		this.instances.each(function(instance, i){
-			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
-		}, this);
-		return this;
-	},
-
-	check: function(type, instance, i){
-		this.checker[type][i] = true;
-		var every = this.instances.every(function(current, j){
-			return this.checker[type][j] || false;
-		}, this);
-		if (!every) return;
-		this.checker[type] = {};
-		this.events[type].each(function(event){
-			event.call(this, this.instances, instance);
-		}, this);
-	}
-
-});
-/*
----
-
-script: Hash.Cookie.js
-
-description: Class for creating, reading, and deleting Cookies in JSON format.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-- Aaron Newton
-
-requires:
-- core:1.2.4/Cookie
-- core:1.2.4/JSON
-- /MooTools.More
-
-provides: [Hash.Cookie]
-
-...
-*/
-
-Hash.Cookie = new Class({
-
-	Extends: Cookie,
-
-	options: {
-		autoSave: true
-	},
-
-	initialize: function(name, options){
-		this.parent(name, options);
-		this.load();
-	},
-
-	save: function(){
-		var value = JSON.encode(this.hash);
-		if (!value || value.length > 4096) return false; //cookie would be truncated!
-		if (value == '{}') this.dispose();
-		else this.write(value);
-		return true;
-	},
-
-	load: function(){
-		this.hash = new Hash(JSON.decode(this.read(), true));
-		return this;
-	}
-
-});
-
-Hash.each(Hash.prototype, function(method, name){
-	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
-		var value = method.apply(this.hash, arguments);
-		if (this.options.autoSave) this.save();
-		return value;
-	});
-});/*
----
-
-script: HtmlTable.js
-
-description: Builds table elements with methods to add rows.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- core:1.2.4/Options
-- core:1.2.4/Events
-- /Class.Occlude
-
-provides: [HtmlTable]
-
-...
-*/
-
-var HtmlTable = new Class({
-
-	Implements: [Options, Events, Class.Occlude],
-
-	options: {
-		properties: {
-			cellpadding: 0,
-			cellspacing: 0,
-			border: 0
-		},
-		rows: [],
-		headers: [],
-		footers: []
-	},
-
-	property: 'HtmlTable',
-
-	initialize: function(){
-		var params = Array.link(arguments, {options: Object.type, table: Element.type});
-		this.setOptions(params.options);
-		this.element = params.table || new Element('table', this.options.properties);
-		if (this.occlude()) return this.occluded;
-		this.build();
-	},
-
-	build: function(){
-		this.element.store('HtmlTable', this);
-
-		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
-		$$(this.body.rows);
-
-		if (this.options.headers.length) this.setHeaders(this.options.headers);
-		else this.thead = document.id(this.element.tHead);
-		if (this.thead) this.head = document.id(this.thead.rows[0]);
-
-		if (this.options.footers.length) this.setFooters(this.options.footers);
-		this.tfoot = document.id(this.element.tFoot);
-		if (this.tfoot) this.foot = document.id(this.thead.rows[0]);
-
-		this.options.rows.each(this.push.bind(this));
-
-		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
-				this[method] = this.element[method].bind(this.element);
-		}, this);
-	},
-
-	toElement: function(){
-		return this.element;
-	},
-
-	empty: function(){
-		this.body.empty();
-		return this;
-	},
-
-	setHeaders: function(headers){
-		this.thead = (document.id(this.element.tHead) || new Element('thead').inject(this.element, 'top')).empty();
-		this.push(headers, this.thead, 'th');
-		this.head = document.id(this.thead.rows[0]);
-		return this;
-	},
-
-	setFooters: function(footers){
-		this.tfoot = (document.id(this.element.tFoot) || new Element('tfoot').inject(this.element, 'top')).empty();
-		this.push(footers, this.tfoot);
-		this.foot = document.id(this.thead.rows[0]);
-		return this;
-	},
-
-	push: function(row, target, tag){
-		var tds = row.map(function(data){
-			var td = new Element(tag || 'td', data.properties),
-				type = data.content || data || '',
-				element = document.id(type);
-
-			if(element) td.adopt(element);
-			else td.set('html', type);
-
-			return td;
-		});
-
-		return {
-			tr: new Element('tr').inject(target || this.body).adopt(tds),
-			tds: tds
-		};
-	}
-
-});/*
----
-
-script: HtmlTable.Zebra.js
-
-description: Builds a stripy table with methods to add rows.
-
-license: MIT-style license
-
-authors:
-- Harald Kirschner
-- Aaron Newton
-
-requires:
-- /HtmlTable
-- /Class.refactor
-
-provides: [HtmlTable.Zebra]
-
-...
-*/
-
-HtmlTable = Class.refactor(HtmlTable, {
-
-	options: {
-		classZebra: 'table-tr-odd',
-		zebra: true
-	},
-
-	initialize: function(){
-		this.previous.apply(this, arguments);
-		if (this.occluded) return this.occluded;
-		if (this.options.zebra) this.updateZebras();
-	},
-
-	updateZebras: function(){
-		Array.each(this.body.rows, this.zebra, this);
-	},
-
-	zebra: function(row, i){
-		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
-	},
-
-	push: function(){
-		var pushed = this.previous.apply(this, arguments);
-		if (this.options.zebra) this.updateZebras();
-		return pushed;
-	}
-
-});/*
----
-
-script: HtmlTable.Sort.js
-
-description: Builds a stripy, sortable table with methods to add rows.
-
-license: MIT-style license
-
-authors:
-- Harald Kirschner
-- Aaron Newton
-
-requires:
-- core:1.2.4/Hash
-- /HtmlTable
-- /Class.refactor
-- /Element.Delegation
-- /Date
-
-provides: [HtmlTable.Sort]
-
-...
-*/
-
-HtmlTable = Class.refactor(HtmlTable, {
-
-	options: {/*
-		onSort: $empty, */
-		sortIndex: 0,
-		sortReverse: false,
-		parsers: [],
-		defaultParser: 'string',
-		classSortable: 'table-sortable',
-		classHeadSort: 'table-th-sort',
-		classHeadSortRev: 'table-th-sort-rev',
-		classNoSort: 'table-th-nosort',
-		classGroupHead: 'table-tr-group-head',
-		classGroup: 'table-tr-group',
-		classCellSort: 'table-td-sort',
-		classSortSpan: 'table-th-sort-span',
-		sortable: false
-	},
-
-	initialize: function () {
-		this.previous.apply(this, arguments);
-		if (this.occluded) return this.occluded;
-		this.sorted = {index: null, dir: 1};
-		this.bound = {
-			headClick: this.headClick.bind(this)
-		};
-		this.sortSpans = new Elements();
-		if (this.options.sortable) {
-			this.enableSort();
-			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
-		}
-	},
-
-	attachSorts: function(attach){
-		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
-	},
-
-	setHeaders: function(){
-		this.previous.apply(this, arguments);
-		if (this.sortEnabled) this.detectParsers();
-	},
-	
-	detectParsers: function(force){
-		if (!this.head) return;
-		var parsers = this.options.parsers, 
-				rows = this.body.rows;
-
-		// auto-detect
-		this.parsers = $$(this.head.cells).map(function(cell, index) {
-			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-sort'))) return cell.retrieve('htmltable-sort');
-			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(cell, 'top');
-			this.sortSpans.push(sortSpan);
-
-			var parser = parsers[index], 
-					cancel;
-			switch ($type(parser)) {
-				case 'function': parser = {convert: parser}; cancel = true; break;
-				case 'string': parser = parser; cancel = true; break;
-			}
-			if (!cancel) {
-				HtmlTable.Parsers.some(function(current) {
-					var match = current.match;
-					if (!match) return false;
-					if (Browser.Engine.trident) return false;
-					for (var i = 0, j = rows.length; i < j; i++) {
-						var text = rows[i].cells[index].get('html').clean();
-						if (text && match.test(text)) {
-							parser = current;
-							return true;
-						}
-					}
-				});
-			}
-
-			if (!parser) parser = this.options.defaultParser;
-			cell.store('htmltable-parser', parser);
-			return parser;
-		}, this);
-	},
-
-	headClick: function(event, el) {
-		if (!this.head) return;
-		var index = Array.indexOf(this.head.cells, el);
-		this.sort(index);
-		return false;
-	},
-
-	sort: function(index, reverse, pre) {
-		if (!this.head) return;
-		pre = !!(pre);
-		var classCellSort = this.options.classCellSort;
-		var classGroup = this.options.classGroup, 
-				classGroupHead = this.options.classGroupHead;
-
-		if (!pre) {
-			if (index != null) {
-				if (this.sorted.index == index) {
-					this.sorted.reverse = !(this.sorted.reverse);
-				} else {
-					if (this.sorted.index != null) {
-						this.sorted.reverse = false;
-						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
-					} else {
-						this.sorted.reverse = true;
-					}
-					this.sorted.index = index;
-				}
-			} else {
-				index = this.sorted.index;
-			}
-
-			if (reverse != null) this.sorted.reverse = reverse;
-
-			var head = document.id(this.head.cells[index]);
-			if (head) {
-				head.addClass(this.options.classHeadSort);
-				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
-				else head.removeClass(this.options.classHeadSortRev);
-			}
-
-			this.body.getElements('td').removeClass(this.options.classCellSort);
-		}
-
-		var parser = this.parsers[index];
-		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
-		if (!parser) return;
-
-		if (!Browser.Engine.trident) {
-			var rel = this.body.getParent();
-			this.body.dispose();
-		}
-
-		var data = Array.map(this.body.rows, function(row, i) {
-			var value = parser.convert.call(document.id(row.cells[index]));
-
-			if (parser.number || $type(value) == 'number') {
-				value = String(value).replace(/[^\d]/, '');
-				value = '00000000000000000000000000000000'.substr(0, 32 - value.length).concat(value);
-			}
-
-			return {
-				position: i,
-				value: value,
-				toString:  function() {
-					return value;
-				}
-			};
-		}, this);
-
-		data.reverse(true);
-		data.sort();
-
-		if (!this.sorted.reverse) data.reverse(true);
-
-		var i = data.length, body = this.body;
-		var j, position, entry, group;
-
-		while (i) {
-			var item = data[--i];
-			position = item.position;
-			var row = body.rows[position];
-			if (row.disabled) continue;
-
-			if (!pre) {
-				if (group === item.value) {
-					row.removeClass(classGroupHead).addClass(classGroup);
-				} else {
-					group = item.value;
-					row.removeClass(classGroup).addClass(classGroupHead);
-				}
-				if (this.zebra) this.zebra(row, i);
-
-				row.cells[index].addClass(classCellSort);
-			}
-
-			body.appendChild(row);
-			for (j = 0; j < i; j++) {
-				if (data[j].position > position) data[j].position--;
-			}
-		};
-		data = null;
-		if (rel) rel.grab(body);
-
-		return this.fireEvent('sort', [body, index]);
-	},
-
-	reSort: function(){
-		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
-		return this;
-	},
-
-	enableSort: function(){
-		this.element.addClass(this.options.classSortable);
-		this.attachSorts(true);
-		this.detectParsers();
-		this.sortEnabled = true;
-		return this;
-	},
-
-	disableSort: function(){
-		this.element.remove(this.options.classSortable);
-		this.attachSorts(false);
-		this.sortSpans.each(function(span) { span.destroy(); });
-		this.sortSpans.empty();
-		this.sortEnabled = false;
-		return this;
-	}
-
-});
-
-HtmlTable.Parsers = new Hash({
-
-	'date': {
-		match: /^\d{4}[^\d]|[^\d]\d{4}$/,
-		convert: function() {
-			return Date.parse(this.get('text'));
-		},
-		type: 'date'
-	},
-	'input-checked': {
-		match: / type="(radio|checkbox)" /,
-		convert: function() {
-			return this.getElement('input').checked;
-		}
-	},
-	'input-value': {
-		match: /<input/,
-		convert: function() {
-			return this.getElement('input').value;
-		}
-	},
-	'number': {
-		match: /^\d+[^\d.,]*$/,
-		convert: function() {
-			return this.get('text').toInt();
-		},
-		number: true
-	},
-	'numberLax': {
-		match: /^[^\d]+\d+$/,
-		convert: function() {
-			return this.get('text').replace(/[^0-9]/, '').toInt();
-		},
-		number: true
-	},
-	'float': {
-		match: /^[\d]+\.[\d]+/,
-		convert: function() {
-			return this.get('text').replace(/[^\d.]/, '').toFloat();
-		},
-		number: true
-	},
-	'floatLax': {
-		match: /^[^\d]+[\d]+\.[\d]+$/,
-		convert: function() {
-			return this.get('text').replace(/[^\d.]/, '');
-		},
-		number: true
-	},
-	'string': {
-		match: null,
-		convert: function() {
-			return this.get('text');
-		}
-	},
-	'title': {
-		match: null,
-		convert: function() {
-			return this.title;
-		}
-	}
-
-});/*
----
-
-script: Keyboard.js
-
-description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
-
-license: MIT-style license
-
-authors:
-- Perrin Westrich
-- Aaron Newton
-- Scott Kyle
-
-requires:
-- core:1.2.4/Events
-- core:1.2.4/Options
-- core:1.2.4/Element.Event
-- /Log
-
-provides: [Keyboard]
-
-...
-*/
-
-(function(){
-
-	var parsed = {};
-	var modifiers = ['shift', 'control', 'alt', 'meta'];
-	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
-	
-	var parse = function(type, eventType){
-		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
-			eventType = $1;
-			return '';
-		});
-		
-		if (!parsed[type]){
-			var key = '', mods = {};
-			type.split('+').each(function(part){
-				if (regex.test(part)) mods[part] = true;
-				else key = part;
-			});
-		
-			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
-			var match = '';
-			modifiers.each(function(mod){
-				if (mods[mod]) match += mod + '+';
-			});
-			
-			parsed[type] = match + key;
-		}
-		
-		return eventType + ':' + parsed[type];
-	};
-
-	this.Keyboard = new Class({
-
-		Extends: Events,
-
-		Implements: [Options, Log],
-
-		options: {
-			/*
-			onActivate: $empty,
-			onDeactivate: $empty,
-			*/
-			defaultEventType: 'keydown',
-			active: false,
-			events: {}
-		},
-
-		initialize: function(options){
-			this.setOptions(options);
-			//if this is the root manager, nothing manages it
-			if (Keyboard.manager) Keyboard.manager.manage(this);
-			this.setup();
-		},
-
-		setup: function(){
-			this.addEvents(this.options.events);
-			if (this.options.active) this.activate();
-		},
-
-		handle: function(event, type){
-			//Keyboard.stop(event) prevents key propagation
-			if (!this.active || event.preventKeyboardPropagation) return;
-			
-			var bubbles = !!this.manager;
-			if (bubbles && this.activeKB){
-				this.activeKB.handle(event, type);
-				if (event.preventKeyboardPropagation) return;
-			}
-			this.fireEvent(type, event);
-			
-			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
-		},
-
-		addEvent: function(type, fn, internal) {
-			return this.parent(parse(type, this.options.defaultEventType), fn, internal);
-		},
-
-		removeEvent: function(type, fn) {
-			return this.parent(parse(type, this.options.defaultEventType), fn);
-		},
-
-		activate: function(){
-			this.active = true;
-			return this.enable();
-		},
-
-		deactivate: function(){
-			this.active = false;
-			return this.fireEvent('deactivate');
-		},
-
-		toggleActive: function(){
-			return this[this.active ? 'deactivate' : 'activate']();
-		},
-
-		enable: function(instance){
-			if (instance) {
-				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
-				if (instance != this.activeKB) this.previous = this.activeKB;
-				//if we're enabling a child, assign it so that events are now passed to it
-				this.activeKB = instance.fireEvent('activate');
-			} else if (this.manager) {
-				//else we're enabling ourselves, we must ask our parent to do it for us
-				this.manager.enable(this);
-			}
-			return this;
-		},
-
-		relenquish: function(){
-			if (this.previous) this.enable(this.previous);
-		},
-
-		//management logic
-		manage: function(instance) {
-			if (instance.manager) instance.manager.drop(instance);
-			this.instances.push(instance);
-			instance.manager = this;
-			if (!this.activeKB) this.enable(instance);
-			else this._disable(instance);
-		},
-
-		_disable: function(instance) {
-			if (this.activeKB == instance) this.activeKB = null;
-		},
-
-		drop: function(instance) {
-			this._disable(instance);
-			this.instances.erase(instance);
-		},
-
-		instances: [],
-
-		trace: function(){
-			this.enableLog();
-			var item = this;
-			this.log('the following items have focus: ');
-			while (item) {
-				this.log(document.id(item.widget) || item.widget || item, 'active: ' + this.active);
-				item = item.activeKB;
-			}
-		}
-
-	});
-
-	Keyboard.stop = function(event) {
-		event.preventKeyboardPropagation = true;
-	};
-
-	Keyboard.manager = new this.Keyboard({
-		active: true
-	});
-	
-	Keyboard.trace = function(){
-		Keyboard.manager.trace();
-	};
-	
-	var handler = function(event){
-		var mods = '';
-		modifiers.each(function(mod){
-			if (event[mod]) mods += mod + '+';
-		});
-		Keyboard.manager.handle(event, event.type + ':' + mods + event.key);
-	};
-	
-	document.addEvents({
-		'keyup': handler,
-		'keydown': handler
-	});
-
-	Event.Keys.extend({
-		'pageup': 33,
-		'pagedown': 34,
-		'end': 35,
-		'home': 36,
-		'capslock': 20,
-		'numlock': 144,
-		'scrolllock': 145
-	});
-
-})();
-/*
----
-
-script: HtmlTable.Select.js
-
-description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
-
-license: MIT-style license
-
-authors:
-- Harald Kirschner
-- Aaron Newton
-
-requires:
-- /Keyboard
-- /HtmlTable
-- /Class.refactor
-- /Element.Delegation
-
-provides: [HtmlTable.Select]
-
-...
-*/
-
-HtmlTable = Class.refactor(HtmlTable, {
-
-	options: {
-		/*onRowSelect: $empty,
-		onRowUnselect: $empty,*/
-		useKeyboard: true,
-		classRowSelected: 'table-tr-selected',
-		classRowHovered: 'table-tr-hovered',
-		classSelectable: 'table-selectable',
-		allowMultiSelect: true,
-		selectable: false
-	},
-
-	initialize: function(){
-		this.previous.apply(this, arguments);
-		if (this.occluded) return this.occluded;
-		this.selectedRows = new Elements();
-		this.bound = {
-			mouseleave: this.mouseleave.bind(this),
-			focusRow: this.focusRow.bind(this)
-		};
-		if (this.options.selectable) this.enableSelect();
-	},
-
-	enableSelect: function(){
-		this.selectEnabled = true;
-		this.attachSelects();
-		this.element.addClass(this.options.classSelectable);
-	},
-
-	disableSelect: function(){
-		this.selectEnabled = false;
-		this.attach(false);
-		this.element.removeClass(this.options.classSelectable);
-	},
-
-	attachSelects: function(attach){
-		attach = $pick(attach, true);
-		var method = attach ? 'addEvents' : 'removeEvents';
-		this.element[method]({
-			mouseleave: this.bound.mouseleave
-		});
-		this.body[method]({
-			'click:relay(tr)': this.bound.focusRow
-		});
-		if (this.options.useKeyboard || this.keyboard){
-			if (!this.keyboard) this.keyboard = new Keyboard({
-				events: {
-					down: function(e) {
-						e.preventDefault();
-						this.shiftFocus(1);
-					}.bind(this),
-					up: function(e) {
-						e.preventDefault();
-						this.shiftFocus(-1);
-					}.bind(this),
-					enter: function(e) {
-						e.preventDefault();
-						if (this.hover) this.focusRow(this.hover);
-					}.bind(this)
-				},
-				active: true
-			});
-			this.keyboard[attach ? 'activate' : 'deactivate']();
-		}
-		this.updateSelects();
-	},
-
-	mouseleave: function(){
-		if (this.hover) this.leaveRow(this.hover);
-	},
-
-	focus: function(){
-		if (this.keyboard) this.keyboard.activate();
-	},
-
-	blur: function(){
-		if (this.keyboard) this.keyboard.deactivate();
-	},
-
-	push: function(){
-		var ret = this.previous.apply(this, arguments);
-		this.updateSelects();
-		return ret;
-	},
-
-	updateSelects: function(){
-		Array.each(this.body.rows, function(row){
-			var binders = row.retrieve('binders');
-			if ((binders && this.selectEnabled) || (!binders && !this.selectEnabled)) return;
-			if (!binders){
-				binders = {
-					mouseenter: this.enterRow.bind(this, [row]),
-					mouseleave: this.leaveRow.bind(this, [row])
-				};
-				row.store('binders', binders).addEvents(binders);
-			} else {
-				row.removeEvents(binders);
-			}
-		}, this);
-	},
-
-	enterRow: function(row){
-		if (this.hover) this.hover = this.leaveRow(this.hover);
-		this.hover = row.addClass(this.options.classRowHovered);
-	},
-
-	shiftFocus: function(offset){
-		if (!this.hover) return this.enterRow(this.body.rows[0]);
-		var to = Array.indexOf(this.body.rows, this.hover) + offset;
-		if (to < 0) to = 0;
-		if (to >= this.body.rows.length) to = this.body.rows.length - 1;
-		if (this.hover == this.body.rows[to]) return this;
-		this.enterRow(this.body.rows[to]);
-	},
-
-	leaveRow: function(row){
-		row.removeClass(this.options.classRowHovered);
-	},
-
-	focusRow: function(){
-		var row = arguments[1] || arguments[0]; //delegation passes the event first
-		var unfocus = function(row){
-			this.selectedRows.erase(row);
-			row.removeClass(this.options.classRowSelected);
-			this.fireEvent('rowUnfocus', [row, this.selectedRows]);
-		}.bind(this);
-		if (!this.options.allowMultiSelect) this.selectedRows.each(unfocus);
-		if (!this.selectedRows.contains(row)) {
-			this.selectedRows.push(row);
-			row.addClass(this.options.classRowSelected);
-			this.fireEvent('rowFocus', [row, this.selectedRows]);
-		} else {
-			unfocus(row);
-		}
-		return false;
-	},
-
-	selectAll: function(status){
-		status = $pick(status, true);
-		if (!this.options.allowMultiSelect && status) return;
-		if (!status) this.selectedRows.removeClass(this.options.classRowSelected).empty();
-		else this.selectedRows.combine(this.body.rows).addClass(this.options.classRowSelected);
-		return this;
-	},
-
-	selectNone: function(){
-		return this.selectAll(false);
-	}
-
-});/*
----
-
-script: Scroller.js
-
-description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-
-requires:
-- core:1.2.4/Events
-- core:1.2.4/Options
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Dimensions
-
-provides: [Scroller]
-
-...
-*/
-
-var Scroller = new Class({
-
-	Implements: [Events, Options],
-
-	options: {
-		area: 20,
-		velocity: 1,
-		onChange: function(x, y){
-			this.element.scrollTo(x, y);
-		},
-		fps: 50
-	},
-
-	initialize: function(element, options){
-		this.setOptions(options);
-		this.element = document.id(element);
-		this.listener = ($type(this.element) != 'element') ? document.id(this.element.getDocument().body) : this.element;
-		this.timer = null;
-		this.bound = {
-			attach: this.attach.bind(this),
-			detach: this.detach.bind(this),
-			getCoords: this.getCoords.bind(this)
-		};
-	},
-
-	start: function(){
-		this.listener.addEvents({
-			mouseover: this.bound.attach,
-			mouseout: this.bound.detach
-		});
-	},
-
-	stop: function(){
-		this.listener.removeEvents({
-			mouseover: this.bound.attach,
-			mouseout: this.bound.detach
-		});
-		this.detach();
-		this.timer = $clear(this.timer);
-	},
-
-	attach: function(){
-		this.listener.addEvent('mousemove', this.bound.getCoords);
-	},
-
-	detach: function(){
-		this.listener.removeEvent('mousemove', this.bound.getCoords);
-		this.timer = $clear(this.timer);
-	},
-
-	getCoords: function(event){
-		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
-		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
-	},
-
-	scroll: function(){
-		var size = this.element.getSize(), 
-			scroll = this.element.getScroll(), 
-			pos = this.element.getOffsets(), 
-			scrollSize = this.element.getScrollSize(), 
-			change = {x: 0, y: 0};
-		for (var z in this.page){
-			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
-				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
-			else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
-				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
-		}
-		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
-	}
-
-});/*
----
-
-script: Tips.js
-
-description: Class for creating nice tips that follow the mouse cursor when hovering an element.
-
-license: MIT-style license
-
-authors:
-- Valerio Proietti
-- Christoph Pojer
-
-requires:
-- core:1.2.4/Options
-- core:1.2.4/Events
-- core:1.2.4/Element.Event
-- core:1.2.4/Element.Style
-- core:1.2.4/Element.Dimensions
-- /MooTools.More
-
-provides: [Tips]
-
-...
-*/
-
-(function(){
-
-var read = function(option, element){
-	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
-};
-
-this.Tips = new Class({
-
-	Implements: [Events, Options],
-
-	options: {
-		/*
-		onAttach: $empty(element),
-		onDetach: $empty(element),
-		*/
-		onShow: function(){
-			this.tip.setStyle('display', 'block');
-		},
-		onHide: function(){
-			this.tip.setStyle('display', 'none');
-		},
-		title: 'title',
-		text: function(element){
-			return element.get('rel') || element.get('href');
-		},
-		showDelay: 100,
-		hideDelay: 100,
-		className: 'tip-wrap',
-		offset: {x: 16, y: 16},
-		fixed: false
-	},
-
-	initialize: function(){
-		var params = Array.link(arguments, {options: Object.type, elements: $defined});
-		this.setOptions(params.options);
-		document.id(this);
-		
-		if (params.elements) this.attach(params.elements);
-	},
-
-	toElement: function(){
-		if (this.tip) return this.tip;
-		
-		this.container = new Element('div', {'class': 'tip'});
-		return this.tip = new Element('div', {
-			'class': this.options.className,
-			styles: {
-				display: 'none',
-				position: 'absolute',
-				top: 0,
-				left: 0
-			}
-		}).adopt(
-			new Element('div', {'class': 'tip-top'}),
-			this.container,
-			new Element('div', {'class': 'tip-bottom'})
-		).inject(document.body);
-	},
-
-	attach: function(elements){
-		$$(elements).each(function(element){
-			var title = read(this.options.title, element),
-				text = read(this.options.text, element);
-			
-			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
-			element.retrieve('tip:text', text);
-			this.fireEvent('attach', [element]);
-			
-			var events = ['enter', 'leave'];
-			if (!this.options.fixed) events.push('move');
-			
-			events.each(function(value){
-				var event = element.retrieve('tip:' + value);
-				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
-				
-				element.store('tip:' + value, event).addEvent('mouse' + value, event);
-			}, this);
-		}, this);
-		
-		return this;
-	},
-
-	detach: function(elements){
-		$$(elements).each(function(element){
-			['enter', 'leave', 'move'].each(function(value){
-				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
-			});
-			
-			this.fireEvent('detach', [element]);
-			
-			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
-				var original = element.retrieve('tip:native');
-				if (original) element.set('title', original);
-			}
-		}, this);
-		
-		return this;
-	},
-
-	elementEnter: function(event, element){
-		this.container.empty();
-		
-		['title', 'text'].each(function(value){
-			var content = element.retrieve('tip:' + value);
-			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
-		}, this);
-		
-		$clear(this.timer);
-		this.timer = this.show.delay(this.options.showDelay, this, element);
-		this.position((this.options.fixed) ? {page: element.getPosition()} : event);
-	},
-
-	elementLeave: function(event, element){
-		$clear(this.timer);
-		this.timer = this.hide.delay(this.options.hideDelay, this, element);
-		this.fireForParent(event, element);
-	},
-
-	fireForParent: function(event, element) {
-			parentNode = element.getParent();
-			if (parentNode == document.body) return;
-			if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
-			else return this.fireForParent(parentNode, event);
-	},
-
-	elementMove: function(event, element){
-		this.position(event);
-	},
-
-	position: function(event){
-		var size = window.getSize(), scroll = window.getScroll(),
-			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
-			props = {x: 'left', y: 'top'},
-			obj = {};
-		
-		for (var z in props){
-			obj[props[z]] = event.page[z] + this.options.offset[z];
-			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
-		}
-		
-		this.tip.setStyles(obj);
-	},
-
-	fill: function(element, contents){
-		if(typeof contents == 'string') element.set('html', contents);
-		else element.adopt(contents);
-	},
-
-	show: function(element){
-		this.fireEvent('show', [element]);
-	},
-
-	hide: function(element){
-		this.fireEvent('hide', [element]);
-	}
-
-});
-
-})();/*
----
-
-script: Date.Catalan.US.js
-
-description: Date messages for Catalan.
-
-license: MIT-style license
-
-authors:
-- Alfons Sanchez
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Catalan]
-
-...
-*/
-
-MooTools.lang.set('ca-CA', 'Date', {
-
-	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
-	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
-	//culture's date order: MM/DD/YYYY
-	dateOrder: ['date', 'month', 'year'],
-
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M',
-
-	AM: 'AM',
-	PM: 'PM',
-
-	/* Date.Extras */
-	ordinal: '',
-
-	lessThanMinuteAgo: 'fa menys d`un minut',
-	minuteAgo: 'fa un minut',
-	minutesAgo: 'fa {delta} minuts',
-	hourAgo: 'fa un hora',
-	hoursAgo: 'fa unes {delta} hores',
-	dayAgo: 'fa un dia',
-	daysAgo: 'fa {delta} dies',
-	lessThanMinuteUntil: 'menys d`un minut des d`ara',
-	minuteUntil: 'un minut des d`ara',
-	minutesUntil: '{delta} minuts des d`ara',
-	hourUntil: 'un hora des d`ara',
-	hoursUntil: 'unes {delta} hores des d`ara',
-	dayUntil: '1 dia des d`ara',
-	daysUntil: '{delta} dies des d`ara'
-
-});/*
----
-
-script: Date.Danish.js
-
-description: Date messages for Danish.
-
-license: MIT-style license
-
-authors:
-- Martin Overgaard
-- Henrik Hansen
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Danish]
-
-...
-*/
- 
-MooTools.lang.set('da-DK', 'Date', {
-
-	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
-	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
-	//culture's date order: DD/MM/YYYY
-	dateOrder: ['date', 'month', 'year'],
-
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%d-%m-%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: function(dayOfMonth){
-	  //1st, 2nd, 3rd, etc.
-	  return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
-	},
-
-	lessThanMinuteAgo: 'mindre end et minut siden',
-	minuteAgo: 'omkring et minut siden',
-	minutesAgo: '{delta} minutter siden',
-	hourAgo: 'omkring en time siden',
-	hoursAgo: 'omkring {delta} timer siden',
-	dayAgo: '1 dag siden',
-	daysAgo: '{delta} dage siden',
-	weekAgo: '1 uge siden',
-	weeksAgo: '{delta} uger siden',
-	monthAgo: '1 måned siden',
-	monthsAgo: '{delta} måneder siden',
-	yearthAgo: '1 år siden',
-	yearsAgo: '{delta} år siden',
-	lessThanMinuteUntil: 'mindre end et minut fra nu',
-	minuteUntil: 'omkring et minut fra nu',
-	minutesUntil: '{delta} minutter fra nu',
-	hourUntil: 'omkring en time fra nu',
-	hoursUntil: 'omkring {delta} timer fra nu',
-	dayUntil: '1 dag fra nu',
-	daysUntil: '{delta} dage fra nu',
-	weekUntil: '1 uge fra nu',
-	weeksUntil: '{delta} uger fra nu',
-	monthUntil: '1 måned fra nu',
-	monthsUntil: '{delta} måneder fra nu',
-	yearUntil: '1 år fra nu',
-	yearsUntil: '{delta} år fra nu'
-
-});
-/*
----
-
-script: Date.Dutch.js
-
-description: Date messages in Dutch.
-
-license: MIT-style license
-
-authors:
-- Lennart Pilon
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Dutch]
-
-...
-*/
-
-MooTools.lang.set('nl-NL', 'Date', {
-
-	months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
-	days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
-	//culture's date order: DD/MM/YYYY
-	dateOrder: ['date', 'month', 'year'],
-
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: 'e',
-
-	lessThanMinuteAgo: 'minder dan een minuut geleden',
-	minuteAgo: 'ongeveer een minuut geleden',
-	minutesAgo: 'minuten geleden',
-	hourAgo: 'ongeveer een uur geleden',
-	hoursAgo: 'ongeveer {delta} uur geleden',
-	dayAgo: '{delta} dag geleden',
-	daysAgo: 'dagen geleden',
-	weekAgo: 'een week geleden',
-	weeksAgo: '{delta} weken geleden',
-	monthAgo: 'een maand geleden',
-	monthsAgo: '{delta} maanden geleden',
-	yearAgo: 'een jaar geleden',
-	yearsAgo: '{delta} jaar geleden',
-	lessThanMinuteUntil: 'minder dan een minuut vanaf nu',
-	minuteUntil: 'ongeveer een minuut vanaf nu',
-	minutesUntil: '{delta} minuten vanaf nu',
-	hourUntil: 'ongeveer een uur vanaf nu',
-	hoursUntil: 'ongeveer {delta} uur vanaf nu',
-	dayUntil: '1 dag vanaf nu',
-	daysUntil: '{delta} dagen vanaf nu',
-	weekAgo: 'een week geleden',
-	weeksAgo: '{delta} weken geleden',
-	monthAgo: 'een maand geleden',
-	monthsAgo: '{delta} maanden geleden',
-	yearthAgo: 'een jaar geleden',
-	yearsAgo: '{delta} jaar geleden',
-
-	weekUntil: 'over een week',
-	weeksUntil: 'over {delta} weken',
-	monthUntil: 'over een maand',
-	monthsUntil: 'over {delta} maanden',
-	yearUntil: 'over een jaar',
-	yearsUntil: 'over {delta} jaar' 
-
-});/*
----
-
-script: Date.English.GB.js
-
-description: Date messages for British English.
-
-license: MIT-style license
-
-authors:
-- Aaron Newton
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.English.GB]
-
-...
-*/
-
-MooTools.lang.set('en-GB', 'Date', {
-
-	dateOrder: ['date', 'month', 'year'],
-	
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M'
-
-}).set('cascade', ['en-US']);/*
----
-
-script: Date.Estonian.js
-
-description: Date messages for Estonian.
-
-license: MIT-style license
-
-authors:
-- Kevin Valdek
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Estonian]
-
-...
-*/
-
-MooTools.lang.set('et-EE', 'Date', {
-
-	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
-	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],
-	//culture's date order: MM.DD.YYYY
-	dateOrder: ['month', 'date', 'year'],
-
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%m.%d.%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: '',
-
-	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
-	minuteAgo: 'umbes minut aega tagasi',
-	minutesAgo: '{delta} minutit tagasi',
-	hourAgo: 'umbes tund aega tagasi',
-	hoursAgo: 'umbes {delta} tundi tagasi',
-	dayAgo: '1 päev tagasi',
-	daysAgo: '{delta} päeva tagasi',
-	weekAgo: '1 nädal tagasi',
-	weeksAgo: '{delta} nädalat tagasi',
-	monthAgo: '1 kuu tagasi',
-	monthsAgo: '{delta} kuud tagasi',
-	yearAgo: '1 aasta tagasi',
-	yearsAgo: '{delta} aastat tagasi',
-	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
-	minuteUntil: 'umbes minuti aja pärast',
-	minutesUntil: '{delta} minuti pärast',
-	hourUntil: 'umbes tunni aja pärast',
-	hoursUntil: 'umbes {delta} tunni pärast',
-	dayUntil: '1 päeva pärast',
-	daysUntil: '{delta} päeva pärast',
-	weekUntil: '1 nädala pärast',
-	weeksUntil: '{delta} nädala pärast',
-	monthUntil: '1 kuu pärast',
-	monthsUntil: '{delta} kuu pärast',
-	yearUntil: '1 aasta pärast',
-	yearsUntil: '{delta} aasta pärast'
-
-});/*
----
-
-script: Date.French.js
-
-description: Date messages in French.
-
-license: MIT-style license
-
-authors:
-- Nicolas Sorosac
-- Antoine Abt
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.French]
-
-...
-*/
- 
-MooTools.lang.set('fr-FR', 'Date', {
-
-	months: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
-	days: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
-	dateOrder: ['date', 'month', 'year'],
-
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	getOrdinal: function(dayOfMonth){
-	  return (dayOfMonth > 1) ? '' : 'er';
-	},
-
-	lessThanMinuteAgo: 'il y a moins d\'une minute',
-	minuteAgo: 'il y a une minute',
-	minutesAgo: 'il y a {delta} minutes',
-	hourAgo: 'il y a une heure',
-	hoursAgo: 'il y a {delta} heures',
-	dayAgo: 'il y a un jour',
-	daysAgo: 'il y a {delta} jours',
-	weekAgo: 'il y a une semaine',
-	weeksAgo: 'il y a {delta} semaines',
-	monthAgo: 'il y a 1 mois',
-	monthsAgo: 'il y a {delta} mois',
-	yearthAgo: 'il y a 1 an',
-	yearsAgo: 'il y a {delta} ans',
-	lessThanMinuteUntil: 'dans moins d\'une minute',
-	minuteUntil: 'dans une minute',
-	minutesUntil: 'dans {delta} minutes',
-	hourUntil: 'dans une heure',
-	hoursUntil: 'dans {delta} heures',
-	dayUntil: 'dans un jour',
-	daysUntil: 'dans {delta} jours',
-	weekUntil: 'dans 1 semaine',
-	weeksUntil: 'dans {delta} semaines',
-	monthUntil: 'dans 1 mois',
-	monthsUntil: 'dans {delta} mois',
-	yearUntil: 'dans 1 an',
-	yearsUntil: 'dans {delta} ans'
-
-});
-/*
----
-
-script: Date.Italian.js
-
-description: Date messages for Italian.
-
-license: MIT-style license.
-
-authors:
-- Andrea Novero
-- Valerio Proietti
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Italian]
-
-...
-*/
- 
-MooTools.lang.set('it-IT', 'Date', {
- 
-	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
-	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],
-	//culture's date order: DD/MM/YYYY
-	dateOrder: ['date', 'month', 'year'],
-
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H.%M',
-
-	/* Date.Extras */
-	ordinal: '&ordm;',
-
-	lessThanMinuteAgo: 'meno di un minuto fa',
-	minuteAgo: 'circa un minuto fa',
-	minutesAgo: 'circa {delta} minuti fa',
-	hourAgo: 'circa un\'ora fa',
-	hoursAgo: 'circa {delta} ore fa',
-	dayAgo: 'circa 1 giorno fa',
-	daysAgo: 'circa {delta} giorni fa',
-	lessThanMinuteUntil: 'tra meno di un minuto',
-	minuteUntil: 'tra circa un minuto',
-	minutesUntil: 'tra circa {delta} minuti',
-	hourUntil: 'tra circa un\'ora',
-	hoursUntil: 'tra circa {delta} ore',
-	dayUntil: 'tra circa un giorno',
-	daysUntil: 'tra circa {delta} giorni'
-
-});/*
----
-
-script: Date.Norwegian.js
-
-description: Date messages in Norwegian.
-
-license: MIT-style license
-
-authors:
-- Espen 'Rexxars' Hovlandsdal
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Norwegian]
-
-...
-*/
-
-MooTools.lang.set('no-NO', 'Date', {
-
-	dateOrder: ['date', 'month', 'year'],
-
-	shortDate: '%d.%m.%Y',
-	shortTime: '%H:%M',
-
-	lessThanMinuteAgo: 'kortere enn et minutt siden',
-	minuteAgo: 'omtrent et minutt siden',
-	minutesAgo: '{delta} minutter siden',
-	hourAgo: 'omtrent en time siden',
-	hoursAgo: 'omtrent {delta} timer siden',
-	dayAgo: '{delta} dag siden',
-	daysAgo: '{delta} dager siden'
-
-});/*
----
-
-script: Date.Polish.js
-
-description: Date messages for Polish.
-
-license: MIT-style license
-
-authors:
-- Oskar Krawczyk
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Polish]
-
-...
-*/
-
-MooTools.lang.set('pl-PL', 'Date', {
-	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
-	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
-	dateOrder: ['year', 'month', 'date'],
-	AM: 'nad ranem',
-	PM: 'po południu',
-
-	shortDate: '%Y-%m-%d',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: function(dayOfMonth){
-		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
-	},
-
-	lessThanMinuteAgo: 'mniej niż minute temu',
-	minuteAgo: 'około minutę temu',
-	minutesAgo: '{delta} minut temu',
-	hourAgo: 'około godzinę temu',
-	hoursAgo: 'około {delta} godzin temu',
-	dayAgo: 'Wczoraj',
-	daysAgo: '{delta} dni temu',
-	lessThanMinuteUntil: 'za niecałą minutę',
-	minuteUntil: 'za około minutę',
-	minutesUntil: 'za {delta} minut',
-	hourUntil: 'za około godzinę',
-	hoursUntil: 'za około {delta} godzin',
-	dayUntil: 'za 1 dzień',
-	daysUntil: 'za {delta} dni'
-});/*
----
-
-script: Date.Portuguese.BR.js
-
-description: Date messages in Portuguese-BR (Brazil).
-
-license: MIT-style license
-
-authors:
-- Fabio Miranda Costa
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Portuguese.BR]
-
-...
-*/
-
-MooTools.lang.set('pt-BR', 'Date', {
-
-	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
-	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
-	//culture's date order: DD/MM/YYYY
-	dateOrder: ['date', 'month', 'year'],
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: function(dayOfMonth){
-		//1º, 2º, 3º, etc.
-    	return '&ordm;';
-	},
-
-	lessThanMinuteAgo: 'há menos de um minuto',
-	minuteAgo: 'há cerca de um minuto',
-	minutesAgo: 'há {delta} minutos',
-	hourAgo: 'há cerca de uma hora',
-	hoursAgo: 'há cerca de {delta} horas',
-	dayAgo: 'há um dia',
-	daysAgo: 'há {delta} dias',
-    weekAgo: 'há uma semana',
-	weeksAgo: 'há {delta} semanas',
-	monthAgo: 'há um mês',
-	monthsAgo: 'há {delta} meses',
-	yearAgo: 'há um ano',
-	yearsAgo: 'há {delta} anos',
-	lessThanMinuteUntil: 'em menos de um minuto',
-	minuteUntil: 'em um minuto',
-	minutesUntil: 'em {delta} minutos',
-	hourUntil: 'em uma hora',
-	hoursUntil: 'em {delta} horas',
-	dayUntil: 'em um dia',
-	daysUntil: 'em {delta} dias',
-	weekUntil: 'em uma semana',
-	weeksUntil: 'em {delta} semanas',
-	monthUntil: 'em um mês',
-	monthsUntil: 'em {delta} meses',
-	yearUntil: 'em um ano',
-	yearsUntil: 'em {delta} anos'
-
-});/*
----
-
-script: Date.Spanish.US.js
-
-description: Date messages for Spanish.
-
-license: MIT-style license
-
-authors:
-- Ãlfons Sanchez
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Spanish]
-
-...
-*/
-
-MooTools.lang.set('es-ES', 'Date', {
-
-	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
-	days: ['Domingo', 'Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado'],
-	//culture's date order: MM/DD/YYYY
-	dateOrder: ['date', 'month', 'year'],
-	AM: 'AM',
-	PM: 'PM',
-
-	shortDate: '%d/%m/%Y',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: '',
-
-	lessThanMinuteAgo: 'hace menos de un minuto',
-	minuteAgo: 'hace un minuto',
-	minutesAgo: 'hace {delta} minutos',
-	hourAgo: 'hace una hora',
-	hoursAgo: 'hace unas {delta} horas',
-	dayAgo: 'hace un dia',
-	daysAgo: 'hace {delta} dias',
-	weekAgo: 'hace una semana',
-	weeksAgo: 'hace unas {delta} semanas',
-	monthAgo: 'hace un mes',
-	monthsAgo: 'hace {delta} meses',
-	yearAgo: 'hace un año',
-	yearsAgo: 'hace {delta} años',
-	lessThanMinuteUntil: 'menos de un minuto desde ahora',
-	minuteUntil: 'un minuto desde ahora',
-	minutesUntil: '{delta} minutos desde ahora',
-	hourUntil: 'una hora desde ahora',
-	hoursUntil: 'unas {delta} horas desde ahora',
-	dayUntil: 'un dia desde ahora',
-	daysUntil: '{delta} dias desde ahora',
-	weekUntil: 'una semana desde ahora',
-	weeksUntil: 'unas {delta} semanas desde ahora',
-	monthUntil: 'un mes desde ahora',
-	monthsUntil: '{delta} meses desde ahora',
-	yearUntil: 'un año desde ahora',
-	yearsUntil: '{delta} años desde ahora'
-
-});/*
----
-
-script: Date.Swedish.js
-
-description: Date messages for Swedish (SE).
-
-license: MIT-style license
-
-authors:
-- Martin Lundgren
-
-requires:
-- /Lang
-- /Date
-
-provides: [Date.Swedish]
-
-...
-*/
-
-MooTools.lang.set('sv-SE', 'Date', {
-
-	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
-	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
-	// culture's date order: YYYY-MM-DD
-	dateOrder: ['year', 'month', 'date'],
-	AM: '',
-	PM: '',
-
-	shortDate: '%Y-%m-%d',
-	shortTime: '%H:%M',
-
-	/* Date.Extras */
-	ordinal: function(dayOfMonth){
-		// Not used in Swedish
-		return '';
-	},
-
-	lessThanMinuteAgo: 'mindre än en minut sedan',
-	minuteAgo: 'ungefär en minut sedan',
-	minutesAgo: '{delta} minuter sedan',
-	hourAgo: 'ungefär en timme sedan',
-	hoursAgo: 'ungefär {delta} timmar sedan',
-	dayAgo: '1 dag sedan',
-	daysAgo: '{delta} dagar sedan',
-	lessThanMinuteUntil: 'mindre än en minut sedan',
-	minuteUntil: 'ungefär en minut sedan',
-	minutesUntil: '{delta} minuter sedan',
-	hourUntil: 'ungefär en timme sedan',
-	hoursUntil: 'ungefär {delta} timmar sedan',
-	dayUntil: '1 dag sedan',
-	daysUntil: '{delta} dagar sedan'
-
-});/*
----
-
-script: Form.Validator.Arabic.js
-
-description: Form.Validator messages in Arabic.
-
-license: MIT-style license
-
-authors:
-- Chafik Barbar
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Arabic]
-
-...
-*/
-
-MooTools.lang.set('ar', 'Form.Validator', {
-	required:'هذا الحقل مطلوب.',
-	minLength:'رجاءً إدخال {minLength}  أحرف على الأقل (تم إدخال {length} أحرف).',
-	maxLength:'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
-	integer:'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
-	numeric:'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
-	digits:'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو شحطة)',
-	alpha:'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
-	alphanum:'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
-	dateSuchAs:'الرجاء إدخال تاريخ صحيح كالتالي {date}',
-	dateInFormatMDY:'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
-	email:'الرجاء إدخال بريد إلكتروني صحيح.',
-	url:'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
-	currencyDollar:'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
-	oneRequired:'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
-	errorPrefix: 'خطأ: ',
-	warningPrefix: 'تحذير: '
-}).set('ar', 'Date', {
-	dateOrder: ['date', 'month', 'year', '/']
-});/*
----
-
-script: Form.Validator.Catalan.js
-
-description: Date messages for Catalan.
-
-license: MIT-style license
-
-authors:
-- Miquel Hudin
-- Alfons Sanchez
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Catalan]
-
-...
-*/
-
-MooTools.lang.set('ca-CA', 'Form.Validator', {
-
-	required:'Aquest camp es obligatori.',
-	minLength:'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
-	maxLength:'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
-	integer:'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
-	numeric:'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
-	digits:'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
-	alpha:'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
-	alphanum:'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
-	dateSuchAs:'Per favor introdueix una data valida com {date}',
-	dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
-	email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',
-	url:'Per favor introdueix una URL valida com http://www.google.com.',
-	currencyDollar:'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
-	oneRequired:'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
-	errorPrefix: 'Error: ',
-	warningPrefix: 'Avis: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'No poden haver espais en aquesta entrada.',
-	reqChkByNode: 'No hi han elements seleccionats.',
-	requiredChk: 'Aquest camp es obligatori.',
-	reqChkByName: 'Per favor selecciona una {label}.',
-	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
-	startDate: 'la data de inici',
-	endDate: 'la data de fi',
-	currendDate: 'la data actual',
-	afterDate: 'La data deu ser igual o posterior a {label}.',
-	beforeDate: 'La data deu ser igual o anterior a {label}.',
-	startMonth: 'Per favor selecciona un mes d´orige',
-	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
-
-});/*
----
-
-script: Form.Validator.Chinese.js
-
-description: Form.Validator messages in chinese (both simplified and traditional).
-
-license: MIT-style license
-
-authors:
-- 陈桂军 - guidy <at> ixuer [dot] net
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Chinese]
-
-...
-*/
-
-/*
-In Chinese:
-------------
-需要指出的是:
-简体中文适用于中国大陆,
-繁体中文适用于香港、澳门和台湾省。
-简体中文和繁体中文在字体和语法上有很多的不同之处。
-
-我可以确保简体中文语言包的准确性,
-但对于繁体中文,我可以保证用户可以准确的理解,但无法保证语句符合他们的阅读习惯。
-如果您不能确认的话,可以只使用简体中文语言包,因为它是最通用的。
-
-In English:
-------------
-It should be noted that:
-Simplified  Chinese apply to mainland Chinese,
-Traditional Chinese apply to Hong Kong, Macao and Taiwan Province.
-There are a lot of different from Simplified  Chinese and Traditional Chinese , Contains font and syntax .
-
-I can assure Simplified Chinese language pack accuracy .
-For Traditional Chinese, I can only guarantee that users can understand, but not necessarily in line with their reading habits.
-If you are unsure, you can only use the simplified Chinese language pack, as it is the most common.
-
-*/
-
-// Simplified Chinese
-MooTools.lang.set('zhs-CN', 'Form.Validator', {
-	required:'这是必填项。',
-	minLength:'请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
-	maxLength:'最多只能输入 {maxLength} 个字符 (已输入 {length} 个)。',
-	integer:'请输入一个整数,不能包含小数点。例如:"1", "200"。',
-	numeric:'请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。',
-	digits:'这里只能接受数字和标点的输入,标点可以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
-	alpha:'请输入 A-Z 的 26 个字母,不能包含空格或任何其他字符。',
-	alphanum:'请输入 A-Z 的 26 个字母或 0-9 的 10 个数字,不能包含空格或任何其他字符。',
-	dateSuchAs:'请输入合法的日期格式,如:{date}。',
-	dateInFormatMDY:'请输入合法的日期格式,例如:MM/DD/YYYY ("12/31/1999")。',
-	email:'请输入合法的电子信箱地址,例如:"fred at domain.com"。',
-	url:'请输入合法的 Url 地址,例如:http://www.google.com。',
-	currencyDollar:'请输入合法的货币符号,例如:¥',
-	oneRequired:'请至少选择一项。',
-	errorPrefix: '错误:',
-	warningPrefix: '警告:'
-});
-
-// Traditional Chinese
-MooTools.lang.set('zht-CN', 'Form.Validator', {
-	required:'這是必填項。',
-	minLength:'請至少鍵入 {minLength} 個字符(已鍵入 {length} 個)。',
-	maxLength:'最多只能鍵入 {maxLength} 個字符(已鍵入 {length} 個)。',
-	integer:'請鍵入一個整數,不能包含小數點。例如:"1", "200"。',
-	numeric:'請鍵入一個數字,例如:"1", "1.1", "-1", "-1.1"。',
-	digits:'這裡只能接受數字和標點的鍵入,標點可以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
-	alpha:'請鍵入 A-Z 的 26 個字母,不能包含空格或任何其他字符。',
-	alphanum:'請鍵入 A-Z 的 26 個字母或 0-9 的 10 個數字,不能包含空格或任何其他字符。',
-	dateSuchAs:'請鍵入合法的日期格式,如:{date}。',
-	dateInFormatMDY:'請鍵入合法的日期格式,例如:MM/DD/YYYY ("12/31/1999")。',
-	email:'請鍵入合法的電子信箱地址,例如:"fred at domain.com"。',
-	url:'請鍵入合法的 Url 地址,例如:http://www.google.com。',
-	currencyYuan:'請鍵入合法的貨幣符號,例如:¥',
-	oneRequired:'請至少選擇一項。',
-	errorPrefix: '錯誤:',
-	warningPrefix: '警告:'
-});
-
-Form.Validator.add('validate-currency-yuan', {
-	errorMsg: function(){
-		return Form.Validator.getMsg('currencyYuan');
-	},
-	test: function(element) {
-		// [ï¿¥]1[##][,###]+[.##]
-		// [ï¿¥]1###+[.##]
-		// [ï¿¥]0.##
-		// [ï¿¥].##
-		return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
-	}
-});
-/*
----
-
-script: Form.Validator.Dutch.js
-
-description: Form.Validator messages in Dutch.
-
-license: MIT-style license
-
-authors:
-- Lennart Pilon
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Dutch]
-
-...
-*/
-
-MooTools.lang.set('nl-NL', 'Form.Validator', {
-	required:'Dit veld is verplicht.',
-	minLength:'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
-	maxLength:'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
-	integer:'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1,25) zijn niet toegestaan.',
-	numeric:'Vul alleen numerieke waarden in (bijvoorbeeld. "1" of "1.1" of "-1" of "-1.1").',
-	digits:'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met een streepje).',
-	alpha:'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
-	alphanum:'Vul alleen letters in (a-z) of nummers (0-9). Spaties en andere karakters zijn niet toegestaan.',
-	dateSuchAs:'Vul een geldige datum in, zoals {date}',
-	dateInFormatMDY:'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
-	email:'Vul een geldig e-mailadres in. Bijvoorbeeld "fred at domein.nl".',
-	url:'Vul een geldige URL in, zoals http://www.google.nl.',
-	currencyDollar:'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
-	oneRequired:'Vul iets in bij minimaal een van de invoervelden.',
-	warningPrefix: 'Waarschuwing: ',
-	errorPrefix: 'Fout: '
-});/*
----
-
-script: Form.Validator.Estonian.js
-
-description: Date messages for Estonian.
-
-license: MIT-style license
-
-authors:
-- Kevin Valdek
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Estonian]
-
-...
-*/
-
-MooTools.lang.set('et-EE', 'Form.Validator', {
-
-	required:'Väli peab olema täidetud.',
-	minLength:'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
-	maxLength:'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
-	integer:'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
-	numeric:'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
-	digits:'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
-	alpha:'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
-	alphanum:'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
-	dateSuchAs:'Palun sisestage kehtiv kuupäev kujul {date}',
-	dateInFormatMDY:'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
-	email:'Palun sisestage kehtiv e-maili aadress (näiteks: "fred at domain.com").',
-	url:'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
-	currencyDollar:'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
-	oneRequired:'Palun sisestage midagi vähemalt ühele antud väljadest.',
-	errorPrefix: 'Viga: ',
-	warningPrefix: 'Hoiatus: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'Väli ei tohi sisaldada tühikuid.',
-	reqChkByNode: 'Ükski väljadest pole valitud.',
-	requiredChk: 'Välja täitmine on vajalik.',
-	reqChkByName: 'Palun valige üks {label}.',
-	match: 'Väli peab sobima {matchName} väljaga',
-	startDate: 'algkuupäev',
-	endDate: 'lõppkuupäev',
-	currendDate: 'praegune kuupäev',
-	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
-	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
-	startMonth: 'Palun valige algkuupäev.',
-	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'
-
-});/*
----
-
-script: Form.Validator.French.js
-
-description: Form.Validator messages in French.
-
-license: MIT-style license
-
-authors: 
-- Miquel Hudin
-- Nicolas Sorosac <nicolas <dot> sorosac <at> gmail <dot> com>
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.French]
-
-...
-*/
- 
-MooTools.lang.set('fr-FR', 'Form.Validator', {
-  required:'Ce champ est obligatoire.',
-  minLength:'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
-  maxLength:'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
-  integer:'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
-  numeric:'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
-  digits:'Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d\'union est autoris&eacute;).',
-  alpha:'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
-  alphanum:'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
-  dateSuchAs:'Veuillez saisir une date correcte comme {date}',
-  dateInFormatMDY:'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
-  email:'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred at domaine.com".',
-  url:'Veuillez saisir une URL, comme http://www.google.com.',
-  currencyDollar:'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
-  oneRequired:'Veuillez s&eacute;lectionner au moins une de ces options.',
-  errorPrefix: 'Erreur : ',
-  warningPrefix: 'Attention : ',
-  
-  //Form.Validator.Extras
- 
-  noSpace: 'Ce champ n\'accepte pas les espaces.',
-  reqChkByNode: 'Aucun &eacute;l&eacute;ment n\'est s&eacute;lectionn&eacute;.',
-  requiredChk: 'Ce champ est obligatoire.',
-  reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
-  match: 'Ce champ doit correspondre avec le champ {matchName}.',
-  startDate: 'date de d&eacute;but',
-  endDate: 'date de fin',
-  currendDate: 'date actuelle',
-  afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
-  beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
-  startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
-  sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'
- 
-});/*
----
-
-script: Form.Validator.Italian.js
-
-description: Form.Validator messages in Italian.
-
-license: MIT-style license
-
-authors:
-- Leonardo Laureti
-- Andrea Novero
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Italian]
-
-...
-*/
- 
-MooTools.lang.set('it-IT', 'Form.Validator', {
-
-	required:'Il campo &egrave; obbligatorio.',
-	minLength:'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
-	maxLength:'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
-	integer:'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
-	numeric:'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
-	digits:'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
-	alpha:'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
-	alphanum:'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
-	dateSuchAs:'Inserire una data valida del tipo {date}',
-	dateInFormatMDY:'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
-	email:'Inserire un indirizzo email valido. Per esempio "nome at dominio.com".',
-	url:'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
-	currencyDollar:'Inserire un importo valido. Per esempio "$100.00".',
-	oneRequired:'Completare almeno uno dei campi richiesti.',
-	errorPrefix: 'Errore: ',
-	warningPrefix: 'Attenzione: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'Non sono consentiti spazi.',
-	reqChkByNode: 'Nessuna voce selezionata.',
-	requiredChk: 'Il campo &egrave; obbligatorio.',
-	reqChkByName: 'Selezionare un(a) {label}.',
-	match: 'Il valore deve corrispondere al campo {matchName}',
-	startDate: 'data d\'inizio',
-	endDate: 'data di fine',
-	currendDate: 'data attuale',
-	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
-	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
-	startMonth: 'Selezionare un mese d\'inizio',
-	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
-
-});/*
----
-
-script: Form.Validator.Norwegian.js
-
-description: Form.Validator messages in Norwegian.
-
-license: MIT-style license
-
-authors:
-- Espen 'Rexxars' Hovlandsdal
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Norwegian]
-
-...
-*/
-
-MooTools.lang.set('no-NO', 'Form.Validator', {
-   required:'Dette feltet er påkrevd.',
-   minLength:'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
-   maxLength:'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
-   integer:'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
-   numeric:'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
-   digits:'Vennligst bruk kun nummer og skilletegn i dette feltet.',
-   alpha:'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
-   alphanum:'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
-   dateSuchAs:'Vennligst skriv inn en gyldig dato, som {date}',
-   dateInFormatMDY:'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
-   email:'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen at domene.no".',
-   url:'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
-   currencyDollar:'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .',
-   oneRequired:'Vennligst fyll ut noe i minst ett av disse feltene.',
-   errorPrefix: 'Feil: ',
-   warningPrefix: 'Advarsel: '
-});/*
----
-
-script: Form.Validator.Polish.js
-
-description: Date messages for Polish.
-
-license: MIT-style license
-
-authors:
-- Oskar Krawczyk
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Polish]
-
-...
-*/
-
-MooTools.lang.set('pl-PL', 'Form.Validator', {
-
-	required:'To pole jest wymagane.',
-	minLength:'Wymagane jest przynajmniej {minLenght} znaków (wpisanych zostało tylko {length}).',
-	maxLength:'Dozwolone jest nie więcej niż {maxLenght} znaków (wpisanych zostało {length})',
-	integer:'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
-	numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
-	digits:'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
-	alpha:'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
-	alphanum:'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
-	dateSuchAs:'Prosimy podać prawidłową datę w formacie: {date}',
-	dateInFormatMDY:'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
-	email:'Prosimy podać prawidłowy adres e-mail, np. "jan at domena.pl".',
-	url:'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
-	currencyDollar:'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
-	oneRequired:'Prosimy wypełnić chociaż jedno z pól.',
-	errorPrefix: 'BÅ‚Ä…d: ',
-	warningPrefix: 'Uwaga: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'W tym polu nie mogą znajdować się spacje.',
-	reqChkByNode: 'Brak zaznaczonych elementów.',
-	requiredChk: 'To pole jest wymagane.',
-	reqChkByName: 'Prosimy wybrać z {label}.',
-	match: 'To pole musi być takie samo jak {matchName}',
-	startDate: 'data poczÄ…tkowa',
-	endDate: 'data końcowa',
-	currendDate: 'aktualna data',
-	afterDate: 'Podana data poinna być taka sama lub po {label}.',
-	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
-	startMonth: 'Prosimy wybrać początkowy miesiąc.',
-	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
-
-});/*
----
-
-script: Form.Validator.Portuguese.js
-
-description: Form.Validator messages in Portuguese.
-
-license: MIT-style license
-
-authors:
-- Miquel Hudin
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Portuguese]
-
-...
-*/
-
-MooTools.lang.set('pt-PT', 'Form.Validator', {
-	required:'Este campo é necessário.',
-	minLength:'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
-	maxLength:'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
-	integer:'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
-	numeric:'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
-	digits:'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
-	alpha:'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
-	alphanum:'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
-	dateSuchAs:'Digite uma data válida, como {date}',
-	dateInFormatMDY:'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
-	email:'Digite um endereço de email válido. Por exemplo "fred at domain.com".',
-	url:'Digite uma URL válida, como http://www.google.com.',
-	currencyDollar:'Digite um valor válido $. Por exemplo $ 100,00. ',
-	oneRequired:'Digite algo para pelo menos um desses insumos.',
-	errorPrefix: 'Erro: ',
-	warningPrefix: 'Aviso: '
-
-}).set('pt-PT', 'Date', {
-	dateOrder: ['date', 'month', 'year', '/']
-});/*
----
-
-script: Form.Validator.Portuguese.BR.js
-
-description: Form.Validator messages in Portuguese-BR.
-
-license: MIT-style license
-
-authors:
-- Fábio Miranda Costa
-
-requires:
-- /Lang
-- /Form.Validator.Portuguese
-
-provides: [Form.Validator.Portuguese.BR]
-
-...
-*/
-
-MooTools.lang.set('pt-BR', 'Form.Validator', {
-
-	required: 'Este campo é obrigatório.',
-	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
-	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
-	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
-	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
-	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
-	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
-	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
-	dateSuchAs: 'Digite uma data válida, como {date}',
-	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
-	email: 'Digite um endereço de email válido. Por exemplo "nome at dominio.com".',
-	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
-	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
-	oneRequired: 'Digite algo para pelo menos um desses campos.',
-	errorPrefix: 'Erro: ',
-	warningPrefix: 'Aviso: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'Não é possível digitar espaços neste campo.',
-	reqChkByNode: 'Não foi selecionado nenhum item.',
-	requiredChk: 'Este campo é obrigatório.',
-	reqChkByName: 'Por favor digite um {label}.',
-	match: 'Este campo deve ser igual ao campo {matchName}.',
-	startDate: 'a data inicial',
-	endDate: 'a data final',
-	currendDate: 'a data atual',
-	afterDate: 'A data deve ser igual ou posterior a {label}.',
-	beforeDate: 'A data deve ser igual ou anterior a {label}.',
-	startMonth: 'Por favor selecione uma data inicial.',
-	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
-	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
-
-});/*
----
-
-script: Form.Validator.Russian.js
-
-description: Form.Validator messages in Russian (utf-8 and cp1251).
-
-license: MIT-style license
-
-authors:
-- Chernodarov Egor
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Russian]
-
-...
-*/
-
-MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {
-	required:'Это поле обязательно к заполнению.',
-	minLength:'Пожалуйста, введите хотя бы {minLength} символов (Вы ввели {length}).',
-	maxLength:'Пожалуйста, введите не больше {maxLength} символов (Вы ввели {length}).',
-	integer:'Пожалуйста, введите в это поле число. Дробные числа (например 1.25) тут не разрешены.',
-	numeric:'Пожалуйста, введите в это поле число (например "1" или "1.1", или "-1", или "-1.1").',
-	digits:'В этом поле Вы можете использовать только цифры и знаки пунктуации (например, телефонный номер со знаками дефиса или с точками).',
-	alpha:'В этом поле можно использовать только латинские буквы (a-z). Пробелы и другие символы запрещены.',
-	alphanum:'В этом поле можно использовать только латинские буквы (a-z) и цифры (0-9). Пробелы и другие символы запрещены.',
-	dateSuchAs:'Пожалуйста, введите корректную дату {date}',
-	dateInFormatMDY:'Пожалуйста, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
-	email:'Пожалуйста, введите корректный емейл-адрес. Для примера "fred at domain.com".',
-	url:'Пожалуйста, введите правильную ссылку вида http://www.google.com.',
-	currencyDollar:'Пожалуйста, введите сумму в долларах. Например: $100.00 .',
-	oneRequired:'Пожалуйста, выберите хоть что-нибудь в одном из этих полей.',
-	errorPrefix: 'Ошибка: ',
-	warningPrefix: 'Внимание: '
-});
-
-//translation in windows-1251 codepage
-MooTools.lang.set('ru-RU', 'Form.Validator', {
-	required:'Ýòî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
-	minLength:'Ïîæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
-	maxLength:'Ïîæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
-	integer:'Ïîæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
-	numeric:'Ïîæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
-	digits:' ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
-	alpha:'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). Ïðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
-	alphanum:'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). Ïðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
-	dateSuchAs:'Ïîæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
-	dateInFormatMDY:'Ïîæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃÃ (íàïðèìåð "12/31/1999")',
-	email:'Ïîæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred at domain.com".',
-	url:'Ïîæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
-	currencyDollar:'Ïîæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. Íàïðèìåð: $100.00 .',
-	oneRequired:'Ïîæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
-	errorPrefix: 'Îøèáêà: ',
-	warningPrefix: 'Âíèìàíèå: '
-});/*
----
-
-script: Form.Validator.Spanish.js
-
-description: Date messages for Spanish.
-
-license: MIT-style license
-
-authors:
-- Ãlfons Sanchez
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Spanish]
-
-...
-*/
-
-MooTools.lang.set('es-ES', 'Form.Validator', {
-
-	required:'Este campo es obligatorio.',
-	minLength:'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
-	maxLength:'Por favor introduce no mas de {maxLength} caracteres (has introducido {length} caracteres).',
-	integer:'Por favor introduce un numero entero en este campo. Numeros con decimales (p.e. 1,25) no se permiten.',
-	numeric:'Por favor introduce solo valores numericos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
-	digits:'Por favor usa solo numeros y puntuacion en este campo (por ejemplo, un numero de telefono con guines y puntos no esta permitido).',
-	alpha:'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
-	alphanum:'Por favor, usa solo letras (a-z) o numeros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
-	dateSuchAs:'Por favor introduce una fecha valida como {date}',
-	dateInFormatMDY:'Por favor introduce una fecha valida como DD/MM/YYYY (p.e. "31/12/1999")',
-	email:'Por favor, introduce una direccione de email valida. Por ejemplo,  "fred at domain.com".',
-	url:'Por favor introduce una URL valida como http://www.google.com.',
-	currencyDollar:'Por favor introduce una cantidad valida de €. Por ejemplo €100,00 .',
-	oneRequired:'Por favor introduce algo para por lo menos una de estas entradas.',
-	errorPrefix: 'Error: ',
-	warningPrefix: 'Aviso: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'No pueden haber espacios en esta entrada.',
-	reqChkByNode: 'No hay elementos seleccionados.',
-	requiredChk: 'Este campo es obligatorio.',
-	reqChkByName: 'Por favor selecciona una {label}.',
-	match: 'Este campo necesita coincidir con el campo {matchName}',
-	startDate: 'la fecha de inicio',
-	endDate: 'la fecha de fin',
-	currendDate: 'la fecha actual',
-	afterDate: 'La fecha debe ser igual o posterior a {label}.',
-	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
-	startMonth: 'Por favor selecciona un mes de origen',
-	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
-
-});/*
----
-
-script: Form.Validator.Swedish.js
-
-description: Date messages for Swedish.
-
-license: MIT-style license
-
-authors:
-- Martin Lundgren
-
-requires:
-- /Lang
-- /Form.Validator
-
-provides: [Form.Validator.Swedish]
-
-...
-*/
-
-MooTools.lang.set('sv-SE', 'Form.Validator', {
-
-	required:'Fältet är obligatoriskt.',
-	minLength:'Ange minst {minLength} tecken (du angav {length} tecken).',
-	maxLength:'Ange högst {maxLength} tecken (du angav {length} tecken). ',
-	integer:'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
-	numeric:'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
-	digits:'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
-	alpha:'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
-	alphanum:'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
-	dateSuchAs:'Ange ett giltigt datum som t.ex. {date}',
-	dateInFormatMDY:'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
-	email:'Ange en giltig e-postadress. Till exempel "erik at domain.com".',
-	url:'Ange en giltig webbadress som http://www.google.com.',
-	currencyDollar:'Ange en giltig belopp. Exempelvis 100,00.',
-	oneRequired:'Vänligen ange minst ett av dessa alternativ.',
-	errorPrefix: 'Fel: ',
-	warningPrefix: 'Varning: ',
-
-	//Form.Validator.Extras
-
-	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
-	reqChkByNode: 'Inga objekt är valda.',
-	requiredChk: 'Detta är ett obligatoriskt fält.',
-	reqChkByName: 'Välj en {label}.',
-	match: 'Detta fält måste matcha {matchName}',
-	startDate: 'startdatumet',
-	endDate: 'slutdatum',
-	currendDate: 'dagens datum',
-	afterDate: 'Datumet bör vara samma eller senare än {label}.',
-	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
-	startMonth: 'Välj en start månad',
-	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'
-
-});// $Id: common.js 625 2009-11-20 13:19:50Z pagameba $
+/*
+---
+
+script: Core.js
+
+description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [MooTools, Native, Hash.base, Array.each, $util]
+
+...
+*/
+
+var MooTools = {
+	'version': '1.2.5dev',
+	'build': '%build%'
+};
+
+var Native = function(options){
+	options = options || {};
+	var name = options.name;
+	var legacy = options.legacy;
+	var protect = options.protect;
+	var methods = options.implement;
+	var generics = options.generics;
+	var initialize = options.initialize;
+	var afterImplement = options.afterImplement || function(){};
+	var object = initialize || legacy;
+	generics = generics !== false;
+
+	object.constructor = Native;
+	object.$family = {name: 'native'};
+	if (legacy && initialize) object.prototype = legacy.prototype;
+	object.prototype.constructor = object;
+
+	if (name){
+		var family = name.toLowerCase();
+		object.prototype.$family = {name: family};
+		Native.typize(object, family);
+	}
+
+	var add = function(obj, name, method, force){
+		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
+		if (generics) Native.genericize(obj, name, protect);
+		afterImplement.call(obj, name, method);
+		return obj;
+	};
+
+	object.alias = function(a1, a2, a3){
+		if (typeof a1 == 'string'){
+			var pa1 = this.prototype[a1];
+			if ((a1 = pa1)) return add(this, a2, a1, a3);
+		}
+		for (var a in a1) this.alias(a, a1[a], a2);
+		return this;
+	};
+
+	object.implement = function(a1, a2, a3){
+		if (typeof a1 == 'string') return add(this, a1, a2, a3);
+		for (var p in a1) add(this, p, a1[p], a2);
+		return this;
+	};
+
+	if (methods) object.implement(methods);
+
+	return object;
+};
+
+Native.genericize = function(object, property, check){
+	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
+		var args = Array.prototype.slice.call(arguments);
+		return object.prototype[property].apply(args.shift(), args);
+	};
+};
+
+Native.implement = function(objects, properties){
+	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
+};
+
+Native.typize = function(object, family){
+	if (!object.type) object.type = function(item){
+		return ($type(item) === family);
+	};
+};
+
+(function(){
+	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
+	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
+
+	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
+	for (var t in types) Native.typize(types[t], t);
+
+	var generics = {
+		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
+		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
+	};
+	for (var g in generics){
+		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
+	}
+})();
+
+var Hash = new Native({
+
+	name: 'Hash',
+
+	initialize: function(object){
+		if ($type(object) == 'hash') object = $unlink(object.getClean());
+		for (var key in object) this[key] = object[key];
+		return this;
+	}
+
+});
+
+Hash.implement({
+
+	forEach: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
+		}
+	},
+
+	getClean: function(){
+		var clean = {};
+		for (var key in this){
+			if (this.hasOwnProperty(key)) clean[key] = this[key];
+		}
+		return clean;
+	},
+
+	getLength: function(){
+		var length = 0;
+		for (var key in this){
+			if (this.hasOwnProperty(key)) length++;
+		}
+		return length;
+	}
+
+});
+
+Hash.alias('forEach', 'each');
+
+Array.implement({
+
+	forEach: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
+	}
+
+});
+
+Array.alias('forEach', 'each');
+
+function $A(iterable){
+	if (iterable.item){
+		var l = iterable.length, array = new Array(l);
+		while (l--) array[l] = iterable[l];
+		return array;
+	}
+	return Array.prototype.slice.call(iterable);
+};
+
+function $arguments(i){
+	return function(){
+		return arguments[i];
+	};
+};
+
+function $chk(obj){
+	return !!(obj || obj === 0);
+};
+
+function $clear(timer){
+	clearTimeout(timer);
+	clearInterval(timer);
+	return null;
+};
+
+function $defined(obj){
+	return (obj != undefined);
+};
+
+function $each(iterable, fn, bind){
+	var type = $type(iterable);
+	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
+};
+
+function $empty(){};
+
+function $extend(original, extended){
+	for (var key in (extended || {})) original[key] = extended[key];
+	return original;
+};
+
+function $H(object){
+	return new Hash(object);
+};
+
+function $lambda(value){
+	return ($type(value) == 'function') ? value : function(){
+		return value;
+	};
+};
+
+function $merge(){
+	var args = Array.slice(arguments);
+	args.unshift({});
+	return $mixin.apply(null, args);
+};
+
+function $mixin(mix){
+	for (var i = 1, l = arguments.length; i < l; i++){
+		var object = arguments[i];
+		if ($type(object) != 'object') continue;
+		for (var key in object){
+			var op = object[key], mp = mix[key];
+			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
+		}
+	}
+	return mix;
+};
+
+function $pick(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		if (arguments[i] != undefined) return arguments[i];
+	}
+	return null;
+};
+
+function $random(min, max){
+	return Math.floor(Math.random() * (max - min + 1) + min);
+};
+
+function $splat(obj){
+	var type = $type(obj);
+	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
+};
+
+var $time = Date.now || function(){
+	return +new Date;
+};
+
+function $try(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		try {
+			return arguments[i]();
+		} catch(e){}
+	}
+	return null;
+};
+
+function $type(obj){
+	if (obj == undefined) return false;
+	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
+	if (obj.nodeName){
+		switch (obj.nodeType){
+			case 1: return 'element';
+			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
+		}
+	} else if (typeof obj.length == 'number'){
+		if (obj.callee) return 'arguments';
+		else if (obj.item) return 'collection';
+	}
+	return typeof obj;
+};
+
+function $unlink(object){
+	var unlinked;
+	switch ($type(object)){
+		case 'object':
+			unlinked = {};
+			for (var p in object) unlinked[p] = $unlink(object[p]);
+		break;
+		case 'hash':
+			unlinked = new Hash(object);
+		break;
+		case 'array':
+			unlinked = [];
+			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+		break;
+		default: return object;
+	}
+	return unlinked;
+};
+/*
+---
+
+script: Browser.js
+
+description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: 
+- /Native
+- /$util
+
+provides: [Browser, Window, Document, $exec]
+
+...
+*/
+
+var Browser = $merge({
+
+	Engine: {name: 'unknown', version: 0},
+
+	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
+
+	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
+
+	Plugins: {},
+
+	Engines: {
+
+		presto: function(){
+			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
+		},
+
+		trident: function(){
+			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
+		},
+
+		webkit: function(){
+			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
+		},
+
+		gecko: function(){
+			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
+		}
+
+	}
+
+}, Browser || {});
+
+Browser.Platform[Browser.Platform.name] = true;
+
+Browser.detect = function(){
+
+	for (var engine in this.Engines){
+		var version = this.Engines[engine]();
+		if (version){
+			this.Engine = {name: engine, version: version};
+			this.Engine[engine] = this.Engine[engine + version] = true;
+			break;
+		}
+	}
+
+	return {name: engine, version: version};
+
+};
+
+Browser.detect();
+
+Browser.Request = function(){
+	return $try(function(){
+		return new XMLHttpRequest();
+	}, function(){
+		return new ActiveXObject('MSXML2.XMLHTTP');
+	}, function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
+	});
+};
+
+Browser.Features.xhr = !!(Browser.Request());
+
+Browser.Plugins.Flash = (function(){
+	var version = ($try(function(){
+		return navigator.plugins['Shockwave Flash'].description;
+	}, function(){
+		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+	}) || '0 r0').match(/\d+/g);
+	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+})();
+
+function $exec(text){
+	if (!text) return text;
+	if (window.execScript){
+		window.execScript(text);
+	} else {
+		var script = document.createElement('script');
+		script.setAttribute('type', 'text/javascript');
+		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
+		document.head.appendChild(script);
+		document.head.removeChild(script);
+	}
+	return text;
+};
+
+Native.UID = 1;
+
+var $uid = (Browser.Engine.trident) ? function(item){
+	return (item.uid || (item.uid = [Native.UID++]))[0];
+} : function(item){
+	return item.uid || (item.uid = Native.UID++);
+};
+
+var Window = new Native({
+
+	name: 'Window',
+
+	legacy: (Browser.Engine.trident) ? null: window.Window,
+
+	initialize: function(win){
+		$uid(win);
+		if (!win.Element){
+			win.Element = $empty;
+			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
+			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
+		}
+		win.document.window = win;
+		return $extend(win, Window.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		window[property] = Window.Prototype[property] = value;
+	}
+
+});
+
+Window.Prototype = {$family: {name: 'window'}};
+
+new Window(window);
+
+var Document = new Native({
+
+	name: 'Document',
+
+	legacy: (Browser.Engine.trident) ? null: window.Document,
+
+	initialize: function(doc){
+		$uid(doc);
+		doc.head = doc.getElementsByTagName('head')[0];
+		doc.html = doc.getElementsByTagName('html')[0];
+		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
+			doc.execCommand("BackgroundImageCache", false, true);
+		});
+		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
+			doc.window.detachEvent('onunload', arguments.callee);
+			doc.head = doc.html = doc.window = null;
+		});
+		return $extend(doc, Document.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		document[property] = Document.Prototype[property] = value;
+	}
+
+});
+
+Document.Prototype = {$family: {name: 'document'}};
+
+new Document(document);
+/*
+---
+
+script: Array.js
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Array.each
+
+provides: [Array]
+
+...
+*/
+
+Array.implement({
+
+	every: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (!fn.call(bind, this[i], i, this)) return false;
+		}
+		return true;
+	},
+
+	filter: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
+		}
+		return results;
+	},
+
+	clean: function(){
+		return this.filter($defined);
+	},
+
+	indexOf: function(item, from){
+		var len = this.length;
+		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+			if (this[i] === item) return i;
+		}
+		return -1;
+	},
+
+	map: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
+		return results;
+	},
+
+	some: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) return true;
+		}
+		return false;
+	},
+
+	associate: function(keys){
+		var obj = {}, length = Math.min(this.length, keys.length);
+		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+		return obj;
+	},
+
+	link: function(object){
+		var result = {};
+		for (var i = 0, l = this.length; i < l; i++){
+			for (var key in object){
+				if (object[key](this[i])){
+					result[key] = this[i];
+					delete object[key];
+					break;
+				}
+			}
+		}
+		return result;
+	},
+
+	contains: function(item, from){
+		return this.indexOf(item, from) != -1;
+	},
+
+	extend: function(array){
+		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
+		return this;
+	},
+	
+	getLast: function(){
+		return (this.length) ? this[this.length - 1] : null;
+	},
+
+	getRandom: function(){
+		return (this.length) ? this[$random(0, this.length - 1)] : null;
+	},
+
+	include: function(item){
+		if (!this.contains(item)) this.push(item);
+		return this;
+	},
+
+	combine: function(array){
+		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+		return this;
+	},
+
+	erase: function(item){
+		for (var i = this.length; i--; i){
+			if (this[i] === item) this.splice(i, 1);
+		}
+		return this;
+	},
+
+	empty: function(){
+		this.length = 0;
+		return this;
+	},
+
+	flatten: function(){
+		var array = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			var type = $type(this[i]);
+			if (!type) continue;
+			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
+		}
+		return array;
+	},
+
+	hexToRgb: function(array){
+		if (this.length != 3) return null;
+		var rgb = this.map(function(value){
+			if (value.length == 1) value += value;
+			return value.toInt(16);
+		});
+		return (array) ? rgb : 'rgb(' + rgb + ')';
+	},
+
+	rgbToHex: function(array){
+		if (this.length < 3) return null;
+		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+		var hex = [];
+		for (var i = 0; i < 3; i++){
+			var bit = (this[i] - 0).toString(16);
+			hex.push((bit.length == 1) ? '0' + bit : bit);
+		}
+		return (array) ? hex : '#' + hex.join('');
+	}
+
+});
+/*
+---
+
+script: Function.js
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Function]
+
+...
+*/
+
+Function.implement({
+
+	extend: function(properties){
+		for (var property in properties) this[property] = properties[property];
+		return this;
+	},
+
+	create: function(options){
+		var self = this;
+		options = options || {};
+		return function(event){
+			var args = options.arguments;
+			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+			if (options.event) args = [event || window.event].extend(args);
+			var returns = function(){
+				return self.apply(options.bind || null, args);
+			};
+			if (options.delay) return setTimeout(returns, options.delay);
+			if (options.periodical) return setInterval(returns, options.periodical);
+			if (options.attempt) return $try(returns);
+			return returns();
+		};
+	},
+
+	run: function(args, bind){
+		return this.apply(bind, $splat(args));
+	},
+
+	pass: function(args, bind){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bind: function(bind, args){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bindWithEvent: function(bind, args){
+		return this.create({bind: bind, arguments: args, event: true});
+	},
+
+	attempt: function(args, bind){
+		return this.create({bind: bind, arguments: args, attempt: true})();
+	},
+
+	delay: function(delay, bind, args){
+		return this.create({bind: bind, arguments: args, delay: delay})();
+	},
+
+	periodical: function(periodical, bind, args){
+		return this.create({bind: bind, arguments: args, periodical: periodical})();
+	}
+
+});
+/*
+---
+
+script: Number.js
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Number]
+
+...
+*/
+
+Number.implement({
+
+	limit: function(min, max){
+		return Math.min(max, Math.max(min, this));
+	},
+
+	round: function(precision){
+		precision = Math.pow(10, precision || 0);
+		return Math.round(this * precision) / precision;
+	},
+
+	times: function(fn, bind){
+		for (var i = 0; i < this; i++) fn.call(bind, i, this);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	}
+
+});
+
+Number.alias('times', 'each');
+
+(function(math){
+	var methods = {};
+	math.each(function(name){
+		if (!Number[name]) methods[name] = function(){
+			return Math[name].apply(null, [this].concat($A(arguments)));
+		};
+	});
+	Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+/*
+---
+
+script: String.js
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires:
+- /Native
+
+provides: [String]
+
+...
+*/
+
+String.implement({
+
+	test: function(regex, params){
+		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
+	},
+
+	contains: function(string, separator){
+		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+	},
+
+	trim: function(){
+		return this.replace(/^\s+|\s+$/g, '');
+	},
+
+	clean: function(){
+		return this.replace(/\s+/g, ' ').trim();
+	},
+
+	camelCase: function(){
+		return this.replace(/-\D/g, function(match){
+			return match.charAt(1).toUpperCase();
+		});
+	},
+
+	hyphenate: function(){
+		return this.replace(/[A-Z]/g, function(match){
+			return ('-' + match.charAt(0).toLowerCase());
+		});
+	},
+
+	capitalize: function(){
+		return this.replace(/\b[a-z]/g, function(match){
+			return match.toUpperCase();
+		});
+	},
+
+	escapeRegExp: function(){
+		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	hexToRgb: function(array){
+		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+		return (hex) ? hex.slice(1).hexToRgb(array) : null;
+	},
+
+	rgbToHex: function(array){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHex(array) : null;
+	},
+
+	stripScripts: function(option){
+		var scripts = '';
+		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
+			scripts += arguments[1] + '\n';
+			return '';
+		});
+		if (option === true) $exec(scripts);
+		else if ($type(option) == 'function') option(scripts, text);
+		return text;
+	},
+
+	substitute: function(object, regexp){
+		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+			if (match.charAt(0) == '\\') return match.slice(1);
+			return (object[name] != undefined) ? object[name] : '';
+		});
+	}
+
+});
+/*
+---
+
+script: Hash.js
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+- /Hash.base
+
+provides: [Hash]
+
+...
+*/
+
+Hash.implement({
+
+	has: Object.prototype.hasOwnProperty,
+
+	keyOf: function(value){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && this[key] === value) return key;
+		}
+		return null;
+	},
+
+	hasValue: function(value){
+		return (Hash.keyOf(this, value) !== null);
+	},
+
+	extend: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.set(this, key, value);
+		}, this);
+		return this;
+	},
+
+	combine: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.include(this, key, value);
+		}, this);
+		return this;
+	},
+
+	erase: function(key){
+		if (this.hasOwnProperty(key)) delete this[key];
+		return this;
+	},
+
+	get: function(key){
+		return (this.hasOwnProperty(key)) ? this[key] : null;
+	},
+
+	set: function(key, value){
+		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+		return this;
+	},
+
+	empty: function(){
+		Hash.each(this, function(value, key){
+			delete this[key];
+		}, this);
+		return this;
+	},
+
+	include: function(key, value){
+		if (this[key] == undefined) this[key] = value;
+		return this;
+	},
+
+	map: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			results.set(key, fn.call(bind, value, key, this));
+		}, this);
+		return results;
+	},
+
+	filter: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			if (fn.call(bind, value, key, this)) results.set(key, value);
+		}, this);
+		return results;
+	},
+
+	every: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
+		}
+		return true;
+	},
+
+	some: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
+		}
+		return false;
+	},
+
+	getKeys: function(){
+		var keys = [];
+		Hash.each(this, function(value, key){
+			keys.push(key);
+		});
+		return keys;
+	},
+
+	getValues: function(){
+		var values = [];
+		Hash.each(this, function(value){
+			values.push(value);
+		});
+		return values;
+	},
+
+	toQueryString: function(base){
+		var queryString = [];
+		Hash.each(this, function(value, key){
+			if (base) key = base + '[' + key + ']';
+			var result;
+			switch ($type(value)){
+				case 'object': result = Hash.toQueryString(value, key); break;
+				case 'array':
+					var qs = {};
+					value.each(function(val, i){
+						qs[i] = val;
+					});
+					result = Hash.toQueryString(qs, key);
+				break;
+				default: result = key + '=' + encodeURIComponent(value);
+			}
+			if (value != undefined) queryString.push(result);
+		});
+
+		return queryString.join('&');
+	}
+
+});
+
+Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
+/*
+---
+
+script: Event.js
+
+description: Contains the Event Class, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Hash
+- /Array
+- /Function
+- /String
+
+provides: [Event]
+
+...
+*/
+
+var Event = new Native({
+
+	name: 'Event',
+
+	initialize: function(event, win){
+		win = win || window;
+		var doc = win.document;
+		event = event || win.event;
+		if (event.$extended) return event;
+		this.$extended = true;
+		var type = event.type;
+		var target = event.target || event.srcElement;
+		while (target && target.nodeType == 3) target = target.parentNode;
+
+		if (type.test(/key/)){
+			var code = event.which || event.keyCode;
+			var key = Event.Keys.keyOf(code);
+			if (type == 'keydown'){
+				var fKey = code - 111;
+				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
+			}
+			key = key || String.fromCharCode(code).toLowerCase();
+		} else if (type.match(/(click|mouse|menu)/i)){
+			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+			var page = {
+				x: event.pageX || event.clientX + doc.scrollLeft,
+				y: event.pageY || event.clientY + doc.scrollTop
+			};
+			var client = {
+				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
+				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
+			};
+			if (type.match(/DOMMouseScroll|mousewheel/)){
+				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+			}
+			var rightClick = (event.which == 3) || (event.button == 2);
+			var related = null;
+			if (type.match(/over|out/)){
+				switch (type){
+					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
+					case 'mouseout': related = event.relatedTarget || event.toElement;
+				}
+				if (!(function(){
+					while (related && related.nodeType == 3) related = related.parentNode;
+					return true;
+				}).create({attempt: Browser.Engine.gecko})()) related = false;
+			}
+		}
+
+		return $extend(this, {
+			event: event,
+			type: type,
+
+			page: page,
+			client: client,
+			rightClick: rightClick,
+
+			wheel: wheel,
+
+			relatedTarget: related,
+			target: target,
+
+			code: code,
+			key: key,
+
+			shift: event.shiftKey,
+			control: event.ctrlKey,
+			alt: event.altKey,
+			meta: event.metaKey
+		});
+	}
+
+});
+
+Event.Keys = new Hash({
+	'enter': 13,
+	'up': 38,
+	'down': 40,
+	'left': 37,
+	'right': 39,
+	'esc': 27,
+	'space': 32,
+	'backspace': 8,
+	'tab': 9,
+	'delete': 46
+});
+
+Event.implement({
+
+	stop: function(){
+		return this.stopPropagation().preventDefault();
+	},
+
+	stopPropagation: function(){
+		if (this.event.stopPropagation) this.event.stopPropagation();
+		else this.event.cancelBubble = true;
+		return this;
+	},
+
+	preventDefault: function(){
+		if (this.event.preventDefault) this.event.preventDefault();
+		else this.event.returnValue = false;
+		return this;
+	}
+
+});
+/*
+---
+
+script: Class.js
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Native
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Class]
+
+...
+*/
+
+function Class(params){
+	
+	if (params instanceof Function) params = {initialize: params};
+	
+	var newClass = function(){
+		Object.reset(this);
+		if (newClass._prototyping) return this;
+		this._current = $empty;
+		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+		delete this._current; delete this.caller;
+		return value;
+	}.extend(this);
+	
+	newClass.implement(params);
+	
+	newClass.constructor = Class;
+	newClass.prototype.constructor = newClass;
+
+	return newClass;
+
+};
+
+Function.prototype.protect = function(){
+	this._protected = true;
+	return this;
+};
+
+Object.reset = function(object, key){
+		
+	if (key == null){
+		for (var p in object) Object.reset(object, p);
+		return object;
+	}
+	
+	delete object[key];
+	
+	switch ($type(object[key])){
+		case 'object':
+			var F = function(){};
+			F.prototype = object[key];
+			var i = new F;
+			object[key] = Object.reset(i);
+		break;
+		case 'array': object[key] = $unlink(object[key]); break;
+	}
+	
+	return object;
+	
+};
+
+new Native({name: 'Class', initialize: Class}).extend({
+
+	instantiate: function(F){
+		F._prototyping = true;
+		var proto = new F;
+		delete F._prototyping;
+		return proto;
+	},
+	
+	wrap: function(self, key, method){
+		if (method._origin) method = method._origin;
+		
+		return function(){
+			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
+			var caller = this.caller, current = this._current;
+			this.caller = current; this._current = arguments.callee;
+			var result = method.apply(this, arguments);
+			this._current = current; this.caller = caller;
+			return result;
+		}.extend({_owner: self, _origin: method, _name: key});
+
+	}
+	
+});
+
+Class.implement({
+	
+	implement: function(key, value){
+		
+		if ($type(key) == 'object'){
+			for (var p in key) this.implement(p, key[p]);
+			return this;
+		}
+		
+		var mutator = Class.Mutators[key];
+		
+		if (mutator){
+			value = mutator.call(this, value);
+			if (value == null) return this;
+		}
+		
+		var proto = this.prototype;
+
+		switch ($type(value)){
+			
+			case 'function':
+				if (value._hidden) return this;
+				proto[key] = Class.wrap(this, key, value);
+			break;
+			
+			case 'object':
+				var previous = proto[key];
+				if ($type(previous) == 'object') $mixin(previous, value);
+				else proto[key] = $unlink(value);
+			break;
+			
+			case 'array':
+				proto[key] = $unlink(value);
+			break;
+			
+			default: proto[key] = value;
+
+		}
+		
+		return this;
+
+	}
+	
+});
+
+Class.Mutators = {
+	
+	Extends: function(parent){
+
+		this.parent = parent;
+		this.prototype = Class.instantiate(parent);
+
+		this.implement('parent', function(){
+			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
+			if (!previous) throw new Error('The method "' + name + '" has no parent.');
+			return previous.apply(this, arguments);
+		}.protect());
+
+	},
+
+	Implements: function(items){
+		$splat(items).each(function(item){
+			if (item instanceof Function) item = Class.instantiate(item);
+			this.implement(item);
+		}, this);
+
+	}
+	
+};
+/*
+---
+
+script: Class.Extras.js
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires:
+- /Class
+
+provides: [Chain, Events, Options]
+
+...
+*/
+
+var Chain = new Class({
+
+	$chain: [],
+
+	chain: function(){
+		this.$chain.extend(Array.flatten(arguments));
+		return this;
+	},
+
+	callChain: function(){
+		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+	},
+
+	clearChain: function(){
+		this.$chain.empty();
+		return this;
+	}
+
+});
+
+var Events = new Class({
+
+	$events: {},
+
+	addEvent: function(type, fn, internal){
+		type = Events.removeOn(type);
+		if (fn != $empty){
+			this.$events[type] = this.$events[type] || [];
+			this.$events[type].include(fn);
+			if (internal) fn.internal = true;
+		}
+		return this;
+	},
+
+	addEvents: function(events){
+		for (var type in events) this.addEvent(type, events[type]);
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		type = Events.removeOn(type);
+		if (!this.$events || !this.$events[type]) return this;
+		this.$events[type].each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		type = Events.removeOn(type);
+		if (!this.$events[type]) return this;
+		if (!fn.internal) this.$events[type].erase(fn);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		if (events) events = Events.removeOn(events);
+		for (type in this.$events){
+			if (events && events != type) continue;
+			var fns = this.$events[type];
+			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
+		}
+		return this;
+	}
+
+});
+
+Events.removeOn = function(string){
+	return string.replace(/^on([A-Z])/, function(full, first){
+		return first.toLowerCase();
+	});
+};
+
+var Options = new Class({
+
+	setOptions: function(){
+		this.options = $merge.run([this.options].extend(arguments));
+		if (!this.addEvent) return this;
+		for (var option in this.options){
+			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+			this.addEvent(option, this.options[option]);
+			delete this.options[option];
+		}
+		return this;
+	}
+
+});
+/*
+---
+
+script: Element.js
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Element, Elements, $, $$, Iframe]
+
+...
+*/
+
+var Element = new Native({
+
+	name: 'Element',
+
+	legacy: window.Element,
+
+	initialize: function(tag, props){
+		var konstructor = Element.Constructors.get(tag);
+		if (konstructor) return konstructor(props);
+		if (typeof tag == 'string') return document.newElement(tag, props);
+		return document.id(tag).set(props);
+	},
+
+	afterImplement: function(key, value){
+		Element.Prototype[key] = value;
+		if (Array[key]) return;
+		Elements.implement(key, function(){
+			var items = [], elements = true;
+			for (var i = 0, j = this.length; i < j; i++){
+				var returns = this[i][key].apply(this[i], arguments);
+				items.push(returns);
+				if (elements) elements = ($type(returns) == 'element');
+			}
+			return (elements) ? new Elements(items) : items;
+		});
+	}
+
+});
+
+Element.Prototype = {$family: {name: 'element'}};
+
+Element.Constructors = new Hash;
+
+var IFrame = new Native({
+
+	name: 'IFrame',
+
+	generics: false,
+
+	initialize: function(){
+		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
+		var props = params.properties || {};
+		var iframe = document.id(params.iframe);
+		var onload = props.onload || $empty;
+		delete props.onload;
+		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
+		iframe = new Element(iframe || 'iframe', props);
+		var onFrameLoad = function(){
+			var host = $try(function(){
+				return iframe.contentWindow.location.host;
+			});
+			if (!host || host == window.location.host){
+				var win = new Window(iframe.contentWindow);
+				new Document(iframe.contentWindow.document);
+				$extend(win.Element.prototype, Element.Prototype);
+			}
+			onload.call(iframe.contentWindow, iframe.contentWindow.document);
+		};
+		var contentWindow = $try(function(){
+			return iframe.contentWindow;
+		});
+		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
+		return iframe;
+	}
+
+});
+
+var Elements = new Native({
+
+	initialize: function(elements, options){
+		options = $extend({ddup: true, cash: true}, options);
+		elements = elements || [];
+		if (options.ddup || options.cash){
+			var uniques = {}, returned = [];
+			for (var i = 0, l = elements.length; i < l; i++){
+				var el = document.id(elements[i], !options.cash);
+				if (options.ddup){
+					if (uniques[el.uid]) continue;
+					uniques[el.uid] = true;
+				}
+				if (el) returned.push(el);
+			}
+			elements = returned;
+		}
+		return (options.cash) ? $extend(elements, this) : elements;
+	}
+
+});
+
+Elements.implement({
+
+	filter: function(filter, bind){
+		if (!filter) return this;
+		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
+			return item.match(filter);
+		} : filter, bind));
+	}
+
+});
+
+Document.implement({
+
+	newElement: function(tag, props){
+		if (Browser.Engine.trident && props){
+			['name', 'type', 'checked'].each(function(attribute){
+				if (!props[attribute]) return;
+				tag += ' ' + attribute + '="' + props[attribute] + '"';
+				if (attribute != 'checked') delete props[attribute];
+			});
+			tag = '<' + tag + '>';
+		}
+		return document.id(this.createElement(tag)).set(props);
+	},
+
+	newTextNode: function(text){
+		return this.createTextNode(text);
+	},
+
+	getDocument: function(){
+		return this;
+	},
+
+	getWindow: function(){
+		return this.window;
+	},
+	
+	id: (function(){
+		
+		var types = {
+
+			string: function(id, nocash, doc){
+				id = doc.getElementById(id);
+				return (id) ? types.element(id, nocash) : null;
+			},
+			
+			element: function(el, nocash){
+				$uid(el);
+				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
+					var proto = Element.Prototype;
+					for (var p in proto) el[p] = proto[p];
+				};
+				return el;
+			},
+			
+			object: function(obj, nocash, doc){
+				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+				return null;
+			}
+			
+		};
+
+		types.textnode = types.whitespace = types.window = types.document = $arguments(0);
+		
+		return function(el, nocash, doc){
+			if (el && el.$family && el.uid) return el;
+			var type = $type(el);
+			return (types[type]) ? types[type](el, nocash, doc || document) : null;
+		};
+
+	})()
+
+});
+
+if (window.$ == null) Window.implement({
+	$: function(el, nc){
+		return document.id(el, nc, this.document);
+	}
+});
+
+Window.implement({
+
+	$$: function(selector){
+		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
+		var elements = [];
+		var args = Array.flatten(arguments);
+		for (var i = 0, l = args.length; i < l; i++){
+			var item = args[i];
+			switch ($type(item)){
+				case 'element': elements.push(item); break;
+				case 'string': elements.extend(this.document.getElements(item, true));
+			}
+		}
+		return new Elements(elements);
+	},
+
+	getDocument: function(){
+		return this.document;
+	},
+
+	getWindow: function(){
+		return this;
+	}
+
+});
+
+Native.implement([Element, Document], {
+
+	getElement: function(selector, nocash){
+		return document.id(this.getElements(selector, true)[0] || null, nocash);
+	},
+
+	getElements: function(tags, nocash){
+		tags = tags.split(',');
+		var elements = [];
+		var ddup = (tags.length > 1);
+		tags.each(function(tag){
+			var partial = this.getElementsByTagName(tag.trim());
+			(ddup) ? elements.extend(partial) : elements = partial;
+		}, this);
+		return new Elements(elements, {ddup: ddup, cash: !nocash});
+	}
+
+});
+
+(function(){
+
+var collected = {}, storage = {};
+var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
+
+var get = function(uid){
+	return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item, retain){
+	if (!item) return;
+	var uid = item.uid;
+	if (retain !== true) retain = false;
+	if (Browser.Engine.trident){
+		if (item.clearAttributes){
+			var clone = retain && item.cloneNode(false);
+			item.clearAttributes();
+			if (clone) item.mergeAttributes(clone);
+		} else if (item.removeEvents){
+			item.removeEvents();
+		}
+		if ((/object/i).test(item.tagName)){
+			for (var p in item){
+				if (typeof item[p] == 'function') item[p] = $empty;
+			}
+			Element.dispose(item);
+		}
+	}	
+	if (!uid) return;
+	collected[uid] = storage[uid] = null;
+};
+
+var purge = function(){
+	Hash.each(collected, clean);
+	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
+	if (window.CollectGarbage) CollectGarbage();
+	collected = storage = null;
+};
+
+var walk = function(element, walk, start, match, all, nocash){
+	var el = element[start || walk];
+	var elements = [];
+	while (el){
+		if (el.nodeType == 1 && (!match || Element.match(el, match))){
+			if (!all) return document.id(el, nocash);
+			elements.push(el);
+		}
+		el = el[walk];
+	}
+	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
+};
+
+var attributes = {
+	'html': 'innerHTML',
+	'class': 'className',
+	'for': 'htmlFor',
+	'defaultValue': 'defaultValue',
+	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
+};
+var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
+var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
+
+bools = bools.associate(bools);
+
+Hash.extend(attributes, bools);
+Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
+
+var inserters = {
+
+	before: function(context, element){
+		if (element.parentNode) element.parentNode.insertBefore(context, element);
+	},
+
+	after: function(context, element){
+		if (!element.parentNode) return;
+		var next = element.nextSibling;
+		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
+	},
+
+	bottom: function(context, element){
+		element.appendChild(context);
+	},
+
+	top: function(context, element){
+		var first = element.firstChild;
+		(first) ? element.insertBefore(context, first) : element.appendChild(context);
+	}
+
+};
+
+inserters.inside = inserters.bottom;
+
+Hash.each(inserters, function(inserter, where){
+
+	where = where.capitalize();
+
+	Element.implement('inject' + where, function(el){
+		inserter(this, document.id(el, true));
+		return this;
+	});
+
+	Element.implement('grab' + where, function(el){
+		inserter(document.id(el, true), this);
+		return this;
+	});
+
+});
+
+Element.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				var property = Element.Properties.get(prop);
+				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		var property = Element.Properties.get(prop);
+		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
+	},
+
+	erase: function(prop){
+		var property = Element.Properties.get(prop);
+		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+		return this;
+	},
+
+	setProperty: function(attribute, value){
+		var key = attributes[attribute];
+		if (value == undefined) return this.removeProperty(attribute);
+		if (key && bools[attribute]) value = !!value;
+		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
+		return this;
+	},
+
+	setProperties: function(attributes){
+		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+		return this;
+	},
+
+	getProperty: function(attribute){
+		var key = attributes[attribute];
+		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
+		return (bools[attribute]) ? !!value : (key) ? value : value || null;
+	},
+
+	getProperties: function(){
+		var args = $A(arguments);
+		return args.map(this.getProperty, this).associate(args);
+	},
+
+	removeProperty: function(attribute){
+		var key = attributes[attribute];
+		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
+		return this;
+	},
+
+	removeProperties: function(){
+		Array.each(arguments, this.removeProperty, this);
+		return this;
+	},
+
+	hasClass: function(className){
+		return this.className.contains(className, ' ');
+	},
+
+	addClass: function(className){
+		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+		return this;
+	},
+
+	removeClass: function(className){
+		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+		return this;
+	},
+
+	toggleClass: function(className){
+		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+	},
+
+	adopt: function(){
+		Array.flatten(arguments).each(function(element){
+			element = document.id(element, true);
+			if (element) this.appendChild(element);
+		}, this);
+		return this;
+	},
+
+	appendText: function(text, where){
+		return this.grab(this.getDocument().newTextNode(text), where);
+	},
+
+	grab: function(el, where){
+		inserters[where || 'bottom'](document.id(el, true), this);
+		return this;
+	},
+
+	inject: function(el, where){
+		inserters[where || 'bottom'](this, document.id(el, true));
+		return this;
+	},
+
+	replaces: function(el){
+		el = document.id(el, true);
+		el.parentNode.replaceChild(this, el);
+		return this;
+	},
+
+	wraps: function(el, where){
+		el = document.id(el, true);
+		return this.replaces(el).grab(el, where);
+	},
+
+	getPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, false, nocash);
+	},
+
+	getAllPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, true, nocash);
+	},
+
+	getNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, false, nocash);
+	},
+
+	getAllNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, true, nocash);
+	},
+
+	getFirst: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
+	},
+
+	getLast: function(match, nocash){
+		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
+	},
+
+	getParent: function(match, nocash){
+		return walk(this, 'parentNode', null, match, false, nocash);
+	},
+
+	getParents: function(match, nocash){
+		return walk(this, 'parentNode', null, match, true, nocash);
+	},
+	
+	getSiblings: function(match, nocash){
+		return this.getParent().getChildren(match, nocash).erase(this);
+	},
+
+	getChildren: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
+	},
+
+	getWindow: function(){
+		return this.ownerDocument.window;
+	},
+
+	getDocument: function(){
+		return this.ownerDocument;
+	},
+
+	getElementById: function(id, nocash){
+		var el = this.ownerDocument.getElementById(id);
+		if (!el) return null;
+		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
+			if (!parent) return null;
+		}
+		return document.id(el, nocash);
+	},
+
+	getSelected: function(){
+		return new Elements($A(this.options).filter(function(option){
+			return option.selected;
+		}));
+	},
+
+	getComputedStyle: function(property){
+		if (this.currentStyle) return this.currentStyle[property.camelCase()];
+		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
+		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
+	},
+
+	toQueryString: function(){
+		var queryString = [];
+		this.getElements('input, select, textarea', true).each(function(el){
+			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
+			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
+				return opt.value;
+			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
+			$splat(value).each(function(val){
+				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
+			});
+		});
+		return queryString.join('&');
+	},
+
+	clone: function(contents, keepid){
+		contents = contents !== false;
+		var clone = this.cloneNode(contents);
+		var clean = function(node, element){
+			if (!keepid) node.removeAttribute('id');
+			if (Browser.Engine.trident){
+				node.clearAttributes();
+				node.mergeAttributes(element);
+				node.removeAttribute('uid');
+				if (node.options){
+					var no = node.options, eo = element.options;
+					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+				}
+			}
+			var prop = props[element.tagName.toLowerCase()];
+			if (prop && element[prop]) node[prop] = element[prop];
+		};
+
+		if (contents){
+			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
+			for (var i = ce.length; i--;) clean(ce[i], te[i]);
+		}
+
+		clean(clone, this);
+		return document.id(clone);
+	},
+
+	destroy: function(){
+		Element.empty(this);
+		Element.dispose(this);
+		clean(this, true);
+		return null;
+	},
+
+	empty: function(){
+		$A(this.childNodes).each(function(node){
+			Element.destroy(node);
+		});
+		return this;
+	},
+
+	dispose: function(){
+		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+	},
+
+	hasChild: function(el){
+		el = document.id(el, true);
+		if (!el) return false;
+		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
+		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
+	},
+
+	match: function(tag){
+		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
+	}
+
+});
+
+Native.implement([Element, Window, Document], {
+
+	addListener: function(type, fn){
+		if (type == 'unload'){
+			var old = fn, self = this;
+			fn = function(){
+				self.removeListener('unload', fn);
+				old();
+			};
+		} else {
+			collected[this.uid] = this;
+		}
+		if (this.addEventListener) this.addEventListener(type, fn, false);
+		else this.attachEvent('on' + type, fn);
+		return this;
+	},
+
+	removeListener: function(type, fn){
+		if (this.removeEventListener) this.removeEventListener(type, fn, false);
+		else this.detachEvent('on' + type, fn);
+		return this;
+	},
+
+	retrieve: function(property, dflt){
+		var storage = get(this.uid), prop = storage[property];
+		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
+		return $pick(prop);
+	},
+
+	store: function(property, value){
+		var storage = get(this.uid);
+		storage[property] = value;
+		return this;
+	},
+
+	eliminate: function(property){
+		var storage = get(this.uid);
+		delete storage[property];
+		return this;
+	}
+
+});
+
+window.addListener('unload', purge);
+
+})();
+
+Element.Properties = new Hash;
+
+Element.Properties.style = {
+
+	set: function(style){
+		this.style.cssText = style;
+	},
+
+	get: function(){
+		return this.style.cssText;
+	},
+
+	erase: function(){
+		this.style.cssText = '';
+	}
+
+};
+
+Element.Properties.tag = {
+
+	get: function(){
+		return this.tagName.toLowerCase();
+	}
+
+};
+
+Element.Properties.html = (function(){
+	var wrapper = document.createElement('div');
+
+	var translations = {
+		table: [1, '<table>', '</table>'],
+		select: [1, '<select>', '</select>'],
+		tbody: [2, '<table><tbody>', '</tbody></table>'],
+		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+	};
+	translations.thead = translations.tfoot = translations.tbody;
+
+	var html = {
+		set: function(){
+			var html = Array.flatten(arguments).join('');
+			var wrap = Browser.Engine.trident && translations[this.get('tag')];
+			if (wrap){
+				var first = wrapper;
+				first.innerHTML = wrap[1] + html + wrap[2];
+				for (var i = wrap[0]; i--;) first = first.firstChild;
+				this.empty().adopt(first.childNodes);
+			} else {
+				this.innerHTML = html;
+			}
+		}
+	};
+
+	html.erase = html.set;
+
+	return html;
+})();
+
+if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
+	get: function(){
+		if (this.innerText) return this.innerText;
+		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
+		var text = temp.innerText;
+		temp.destroy();
+		return text;
+	}
+};
+/*
+---
+
+script: Element.Event.js
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: 
+- /Element
+- /Event
+
+provides: [Element.Event]
+
+...
+*/
+
+Element.Properties.events = {set: function(events){
+	this.addEvents(events);
+}};
+
+Native.implement([Element, Window, Document], {
+
+	addEvent: function(type, fn){
+		var events = this.retrieve('events', {});
+		events[type] = events[type] || {'keys': [], 'values': []};
+		if (events[type].keys.contains(fn)) return this;
+		events[type].keys.push(fn);
+		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
+		if (custom){
+			if (custom.onAdd) custom.onAdd.call(this, fn);
+			if (custom.condition){
+				condition = function(event){
+					if (custom.condition.call(this, event)) return fn.call(this, event);
+					return true;
+				};
+			}
+			realType = custom.base || realType;
+		}
+		var defn = function(){
+			return fn.call(self);
+		};
+		var nativeEvent = Element.NativeEvents[realType];
+		if (nativeEvent){
+			if (nativeEvent == 2){
+				defn = function(event){
+					event = new Event(event, self.getWindow());
+					if (condition.call(self, event) === false) event.stop();
+				};
+			}
+			this.addListener(realType, defn);
+		}
+		events[type].values.push(defn);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		var pos = events[type].keys.indexOf(fn);
+		if (pos == -1) return this;
+		events[type].keys.splice(pos, 1);
+		var value = events[type].values.splice(pos, 1)[0];
+		var custom = Element.Events.get(type);
+		if (custom){
+			if (custom.onRemove) custom.onRemove.call(this, fn);
+			type = custom.base || type;
+		}
+		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
+	},
+
+	addEvents: function(events){
+		for (var event in events) this.addEvent(event, events[event]);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		var attached = this.retrieve('events');
+		if (!attached) return this;
+		if (!events){
+			for (type in attached) this.removeEvents(type);
+			this.eliminate('events');
+		} else if (attached[events]){
+			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
+			attached[events] = null;
+		}
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		events[type].keys.each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	cloneEvents: function(from, type){
+		from = document.id(from);
+		var fevents = from.retrieve('events');
+		if (!fevents) return this;
+		if (!type){
+			for (var evType in fevents) this.cloneEvents(from, evType);
+		} else if (fevents[type]){
+			fevents[type].keys.each(function(fn){
+				this.addEvent(type, fn);
+			}, this);
+		}
+		return this;
+	}
+
+});
+
+Element.NativeEvents = {
+	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+	keydown: 2, keypress: 2, keyup: 2, //keyboard
+	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
+	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+	error: 1, abort: 1, scroll: 1 //misc
+};
+
+(function(){
+
+var $check = function(event){
+	var related = event.relatedTarget;
+	if (related == undefined) return true;
+	if (related === false) return false;
+	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
+};
+
+Element.Events = new Hash({
+
+	mouseenter: {
+		base: 'mouseover',
+		condition: $check
+	},
+
+	mouseleave: {
+		base: 'mouseout',
+		condition: $check
+	},
+
+	mousewheel: {
+		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
+	}
+
+});
+
+})();
+/*
+---
+
+script: Element.Style.js
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Element.Style]
+
+...
+*/
+
+Element.Properties.styles = {set: function(styles){
+	this.setStyles(styles);
+}};
+
+Element.Properties.opacity = {
+
+	set: function(opacity, novisibility){
+		if (!novisibility){
+			if (opacity == 0){
+				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
+			} else {
+				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
+			}
+		}
+		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
+		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
+		this.style.opacity = opacity;
+		this.store('opacity', opacity);
+	},
+
+	get: function(){
+		return this.retrieve('opacity', 1);
+	}
+
+};
+
+Element.implement({
+
+	setOpacity: function(value){
+		return this.set('opacity', value, true);
+	},
+
+	getOpacity: function(){
+		return this.get('opacity');
+	},
+
+	setStyle: function(property, value){
+		switch (property){
+			case 'opacity': return this.set('opacity', parseFloat(value));
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		if ($type(value) != 'string'){
+			var map = (Element.Styles.get(property) || '@').split(' ');
+			value = $splat(value).map(function(val, i){
+				if (!map[i]) return '';
+				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+			}).join(' ');
+		} else if (value == String(Number(value))){
+			value = Math.round(value);
+		}
+		this.style[property] = value;
+		return this;
+	},
+
+	getStyle: function(property){
+		switch (property){
+			case 'opacity': return this.get('opacity');
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		var result = this.style[property];
+		if (!$chk(result)){
+			result = [];
+			for (var style in Element.ShortStyles){
+				if (property != style) continue;
+				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+				return result.join(' ');
+			}
+			result = this.getComputedStyle(property);
+		}
+		if (result){
+			result = String(result);
+			var color = result.match(/rgba?\([\d\s,]+\)/);
+			if (color) result = result.replace(color[0], color[0].rgbToHex());
+		}
+		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
+			if (property.test(/^(height|width)$/)){
+				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+				values.each(function(value){
+					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+				}, this);
+				return this['offset' + property.capitalize()] - size + 'px';
+			}
+			if ((Browser.Engine.presto) && String(result).test('px')) return result;
+			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
+		}
+		return result;
+	},
+
+	setStyles: function(styles){
+		for (var style in styles) this.setStyle(style, styles[style]);
+		return this;
+	},
+
+	getStyles: function(){
+		var result = {};
+		Array.flatten(arguments).each(function(key){
+			result[key] = this.getStyle(key);
+		}, this);
+		return result;
+	}
+
+});
+
+Element.Styles = new Hash({
+	left: '@px', top: '@px', bottom: '@px', right: '@px',
+	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+});
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+	var Short = Element.ShortStyles;
+	var All = Element.Styles;
+	['margin', 'padding'].each(function(style){
+		var sd = style + direction;
+		Short[style][sd] = All[sd] = '@px';
+	});
+	var bd = 'border' + direction;
+	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+	Short[bd] = {};
+	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+/*
+---
+
+script: Element.Dimensions.js
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires:
+- /Element
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+Element.implement({
+
+	scrollTo: function(x, y){
+		if (isBody(this)){
+			this.getWindow().scrollTo(x, y);
+		} else {
+			this.scrollLeft = x;
+			this.scrollTop = y;
+		}
+		return this;
+	},
+
+	getSize: function(){
+		if (isBody(this)) return this.getWindow().getSize();
+		return {x: this.offsetWidth, y: this.offsetHeight};
+	},
+
+	getScrollSize: function(){
+		if (isBody(this)) return this.getWindow().getScrollSize();
+		return {x: this.scrollWidth, y: this.scrollHeight};
+	},
+
+	getScroll: function(){
+		if (isBody(this)) return this.getWindow().getScroll();
+		return {x: this.scrollLeft, y: this.scrollTop};
+	},
+
+	getScrolls: function(){
+		var element = this, position = {x: 0, y: 0};
+		while (element && !isBody(element)){
+			position.x += element.scrollLeft;
+			position.y += element.scrollTop;
+			element = element.parentNode;
+		}
+		return position;
+	},
+
+	getOffsetParent: function(){
+		var element = this;
+		if (isBody(element)) return null;
+		if (!Browser.Engine.trident) return element.offsetParent;
+		while ((element = element.parentNode) && !isBody(element)){
+			if (styleString(element, 'position') != 'static') return element;
+		}
+		return null;
+	},
+
+	getOffsets: function(){
+		if (this.getBoundingClientRect){
+			var bound = this.getBoundingClientRect(),
+				html = document.id(this.getDocument().documentElement),
+				htmlScroll = html.getScroll(),
+				elemScrolls = this.getScrolls(),
+				elemScroll = this.getScroll(),
+				isFixed = (styleString(this, 'position') == 'fixed');
+
+			return {
+				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+			};
+		}
+
+		var element = this, position = {x: 0, y: 0};
+		if (isBody(this)) return position;
+
+		while (element && !isBody(element)){
+			position.x += element.offsetLeft;
+			position.y += element.offsetTop;
+
+			if (Browser.Engine.gecko){
+				if (!borderBox(element)){
+					position.x += leftBorder(element);
+					position.y += topBorder(element);
+				}
+				var parent = element.parentNode;
+				if (parent && styleString(parent, 'overflow') != 'visible'){
+					position.x += leftBorder(parent);
+					position.y += topBorder(parent);
+				}
+			} else if (element != this && Browser.Engine.webkit){
+				position.x += leftBorder(element);
+				position.y += topBorder(element);
+			}
+
+			element = element.offsetParent;
+		}
+		if (Browser.Engine.gecko && !borderBox(this)){
+			position.x -= leftBorder(this);
+			position.y -= topBorder(this);
+		}
+		return position;
+	},
+
+	getPosition: function(relative){
+		if (isBody(this)) return {x: 0, y: 0};
+		var offset = this.getOffsets(),
+				scroll = this.getScrolls();
+		var position = {
+			x: offset.x - scroll.x,
+			y: offset.y - scroll.y
+		};
+		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
+		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
+	},
+
+	getCoordinates: function(element){
+		if (isBody(this)) return this.getWindow().getCoordinates();
+		var position = this.getPosition(element),
+				size = this.getSize();
+		var obj = {
+			left: position.x,
+			top: position.y,
+			width: size.x,
+			height: size.y
+		};
+		obj.right = obj.left + obj.width;
+		obj.bottom = obj.top + obj.height;
+		return obj;
+	},
+
+	computePosition: function(obj){
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
+	},
+
+	setPosition: function(obj){
+		return this.setStyles(this.computePosition(obj));
+	}
+
+});
+
+
+Native.implement([Document, Window], {
+
+	getSize: function(){
+		if (Browser.Engine.presto || Browser.Engine.webkit){
+			var win = this.getWindow();
+			return {x: win.innerWidth, y: win.innerHeight};
+		}
+		var doc = getCompatElement(this);
+		return {x: doc.clientWidth, y: doc.clientHeight};
+	},
+
+	getScroll: function(){
+		var win = this.getWindow(), doc = getCompatElement(this);
+		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+	},
+
+	getScrollSize: function(){
+		var doc = getCompatElement(this), min = this.getSize();
+		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
+	},
+
+	getPosition: function(){
+		return {x: 0, y: 0};
+	},
+
+	getCoordinates: function(){
+		var size = this.getSize();
+		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+	}
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+	return styleString(element, style).toInt() || 0;
+};
+
+function borderBox(element){
+	return styleString(element, '-moz-box-sizing') == 'border-box';
+};
+
+function topBorder(element){
+	return styleNumber(element, 'border-top-width');
+};
+
+function leftBorder(element){
+	return styleNumber(element, 'border-left-width');
+};
+
+function isBody(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+};
+
+function getCompatElement(element){
+	var doc = element.getDocument();
+	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+};
+
+})();
+
+//aliases
+Element.alias('setPosition', 'position'); //compatability
+
+Native.implement([Window, Document, Element], {
+
+	getHeight: function(){
+		return this.getSize().y;
+	},
+
+	getWidth: function(){
+		return this.getSize().x;
+	},
+
+	getScrollTop: function(){
+		return this.getScroll().y;
+	},
+
+	getScrollLeft: function(){
+		return this.getScroll().x;
+	},
+
+	getScrollHeight: function(){
+		return this.getScrollSize().y;
+	},
+
+	getScrollWidth: function(){
+		return this.getScrollSize().x;
+	},
+
+	getTop: function(){
+		return this.getPosition().y;
+	},
+
+	getLeft: function(){
+		return this.getPosition().x;
+	}
+
+});
+/*
+---
+
+script: Selectors.js
+
+description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Selectors]
+
+...
+*/
+
+Native.implement([Document, Element], {
+
+	getElements: function(expression, nocash){
+		expression = expression.split(',');
+		var items, local = {};
+		for (var i = 0, l = expression.length; i < l; i++){
+			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
+			if (i != 0 && elements.item) elements = $A(elements);
+			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
+		}
+		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
+	}
+
+});
+
+Element.implement({
+
+	match: function(selector){
+		if (!selector || (selector == this)) return true;
+		var tagid = Selectors.Utils.parseTagAndID(selector);
+		var tag = tagid[0], id = tagid[1];
+		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
+		var parsed = Selectors.Utils.parseSelector(selector);
+		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
+	}
+
+});
+
+var Selectors = {Cache: {nth: {}, parsed: {}}};
+
+Selectors.RegExps = {
+	id: (/#([\w-]+)/),
+	tag: (/^(\w+|\*)/),
+	quick: (/^(\w+|\*)$/),
+	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
+	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
+};
+
+Selectors.Utils = {
+
+	chk: function(item, uniques){
+		if (!uniques) return true;
+		var uid = $uid(item);
+		if (!uniques[uid]) return uniques[uid] = true;
+		return false;
+	},
+
+	parseNthArgument: function(argument){
+		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
+		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
+		if (!parsed) return false;
+		var inta = parseInt(parsed[1], 10);
+		var a = (inta || inta === 0) ? inta : 1;
+		var special = parsed[2] || false;
+		var b = parseInt(parsed[3], 10) || 0;
+		if (a != 0){
+			b--;
+			while (b < 1) b += a;
+			while (b >= a) b -= a;
+		} else {
+			a = b;
+			special = 'index';
+		}
+		switch (special){
+			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
+			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
+			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
+			case 'first': parsed = {a: 0, special: 'index'}; break;
+			case 'last': parsed = {special: 'last-child'}; break;
+			case 'only': parsed = {special: 'only-child'}; break;
+			default: parsed = {a: (a - 1), special: 'index'};
+		}
+
+		return Selectors.Cache.nth[argument] = parsed;
+	},
+
+	parseSelector: function(selector){
+		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
+		var m, parsed = {classes: [], pseudos: [], attributes: []};
+		while ((m = Selectors.RegExps.combined.exec(selector))){
+			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
+			if (cn){
+				parsed.classes.push(cn);
+			} else if (pn){
+				var parser = Selectors.Pseudo.get(pn);
+				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
+				else parsed.attributes.push({name: pn, operator: '=', value: pa});
+			} else if (an){
+				parsed.attributes.push({name: an, operator: ao, value: av});
+			}
+		}
+		if (!parsed.classes.length) delete parsed.classes;
+		if (!parsed.attributes.length) delete parsed.attributes;
+		if (!parsed.pseudos.length) delete parsed.pseudos;
+		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
+		return Selectors.Cache.parsed[selector] = parsed;
+	},
+
+	parseTagAndID: function(selector){
+		var tag = selector.match(Selectors.RegExps.tag);
+		var id = selector.match(Selectors.RegExps.id);
+		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
+	},
+
+	filter: function(item, parsed, local){
+		var i;
+		if (parsed.classes){
+			for (i = parsed.classes.length; i--; i){
+				var cn = parsed.classes[i];
+				if (!Selectors.Filters.byClass(item, cn)) return false;
+			}
+		}
+		if (parsed.attributes){
+			for (i = parsed.attributes.length; i--; i){
+				var att = parsed.attributes[i];
+				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
+			}
+		}
+		if (parsed.pseudos){
+			for (i = parsed.pseudos.length; i--; i){
+				var psd = parsed.pseudos[i];
+				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
+			}
+		}
+		return true;
+	},
+
+	getByTagAndID: function(ctx, tag, id){
+		if (id){
+			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
+			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
+		} else {
+			return ctx.getElementsByTagName(tag);
+		}
+	},
+
+	search: function(self, expression, local){
+		var splitters = [];
+
+		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
+			splitters.push(m1);
+			return ':)' + m2;
+		}).split(':)');
+
+		var items, filtered, item;
+
+		for (var i = 0, l = selectors.length; i < l; i++){
+
+			var selector = selectors[i];
+
+			if (i == 0 && Selectors.RegExps.quick.test(selector)){
+				items = self.getElementsByTagName(selector);
+				continue;
+			}
+
+			var splitter = splitters[i - 1];
+
+			var tagid = Selectors.Utils.parseTagAndID(selector);
+			var tag = tagid[0], id = tagid[1];
+
+			if (i == 0){
+				items = Selectors.Utils.getByTagAndID(self, tag, id);
+			} else {
+				var uniques = {}, found = [];
+				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
+				items = found;
+			}
+
+			var parsed = Selectors.Utils.parseSelector(selector);
+
+			if (parsed){
+				filtered = [];
+				for (var m = 0, n = items.length; m < n; m++){
+					item = items[m];
+					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
+				}
+				items = filtered;
+			}
+
+		}
+
+		return items;
+
+	}
+
+};
+
+Selectors.Getters = {
+
+	' ': function(found, self, tag, id, uniques){
+		var items = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = items.length; i < l; i++){
+			var item = items[i];
+			if (Selectors.Utils.chk(item, uniques)) found.push(item);
+		}
+		return found;
+	},
+
+	'>': function(found, self, tag, id, uniques){
+		var children = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = children.length; i < l; i++){
+			var child = children[i];
+			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
+		}
+		return found;
+	},
+
+	'+': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+				break;
+			}
+		}
+		return found;
+	},
+
+	'~': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (!Selectors.Utils.chk(self, uniques)) break;
+				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+			}
+		}
+		return found;
+	}
+
+};
+
+Selectors.Filters = {
+
+	byTag: function(self, tag){
+		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
+	},
+
+	byID: function(self, id){
+		return (!id || (self.id && self.id == id));
+	},
+
+	byClass: function(self, klass){
+		return (self.className && self.className.contains && self.className.contains(klass, ' '));
+	},
+
+	byPseudo: function(self, parser, argument, local){
+		return parser.call(self, argument, local);
+	},
+
+	byAttribute: function(self, name, operator, value){
+		var result = Element.prototype.getProperty.call(self, name);
+		if (!result) return (operator == '!=');
+		if (!operator || value == undefined) return true;
+		switch (operator){
+			case '=': return (result == value);
+			case '*=': return (result.contains(value));
+			case '^=': return (result.substr(0, value.length) == value);
+			case '$=': return (result.substr(result.length - value.length) == value);
+			case '!=': return (result != value);
+			case '~=': return result.contains(value, ' ');
+			case '|=': return result.contains(value, '-');
+		}
+		return false;
+	}
+
+};
+
+Selectors.Pseudo = new Hash({
+
+	// w3c pseudo selectors
+
+	checked: function(){
+		return this.checked;
+	},
+	
+	empty: function(){
+		return !(this.innerText || this.textContent || '').length;
+	},
+
+	not: function(selector){
+		return !Element.match(this, selector);
+	},
+
+	contains: function(text){
+		return (this.innerText || this.textContent || '').contains(text);
+	},
+
+	'first-child': function(){
+		return Selectors.Pseudo.index.call(this, 0);
+	},
+
+	'last-child': function(){
+		var element = this;
+		while ((element = element.nextSibling)){
+			if (element.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'only-child': function(){
+		var prev = this;
+		while ((prev = prev.previousSibling)){
+			if (prev.nodeType == 1) return false;
+		}
+		var next = this;
+		while ((next = next.nextSibling)){
+			if (next.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'nth-child': function(argument, local){
+		argument = (argument == undefined) ? 'n' : argument;
+		var parsed = Selectors.Utils.parseNthArgument(argument);
+		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
+		var count = 0;
+		local.positions = local.positions || {};
+		var uid = $uid(this);
+		if (!local.positions[uid]){
+			var self = this;
+			while ((self = self.previousSibling)){
+				if (self.nodeType != 1) continue;
+				count ++;
+				var position = local.positions[$uid(self)];
+				if (position != undefined){
+					count = position + count;
+					break;
+				}
+			}
+			local.positions[uid] = count;
+		}
+		return (local.positions[uid] % parsed.a == parsed.b);
+	},
+
+	// custom pseudo selectors
+
+	index: function(index){
+		var element = this, count = 0;
+		while ((element = element.previousSibling)){
+			if (element.nodeType == 1 && ++count > index) return false;
+		}
+		return (count == index);
+	},
+
+	even: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
+	},
+
+	odd: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
+	},
+	
+	selected: function(){
+		return this.selected;
+	},
+	
+	enabled: function(){
+		return (this.disabled === false);
+	}
+
+});
+/*
+---
+
+script: DomReady.js
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires:
+- /Element.Event
+
+provides: [DomReady]
+
+...
+*/
+
+Element.Events.domready = {
+
+	onAdd: function(fn){
+		if (Browser.loaded) fn.call(this);
+	}
+
+};
+
+(function(){
+
+	var domready = function(){
+		if (Browser.loaded) return;
+		Browser.loaded = true;
+		window.fireEvent('domready');
+		document.fireEvent('domready');
+	};
+	
+	window.addEvent('load', domready);
+
+	if (Browser.Engine.trident){
+		var temp = document.createElement('div');
+		(function(){
+			($try(function(){
+				temp.doScroll(); // Technique by Diego Perini
+				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
+			})) ? domready() : arguments.callee.delay(50);
+		})();
+	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
+		(function(){
+			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
+		})();
+	} else {
+		document.addEvent('DOMContentLoaded', domready);
+	}
+
+})();
+/*
+---
+
+script: JSON.js
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+See Also: <http://www.json.org/>
+
+requires:
+- /Array
+- /String
+- /Number
+- /Function
+- /Hash
+
+provides: [JSON]
+
+...
+*/
+
+var JSON = new Hash(this.JSON && {
+	stringify: JSON.stringify,
+	parse: JSON.parse
+}).extend({
+	
+	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},
+
+	$replaceChars: function(chr){
+		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
+	},
+
+	encode: function(obj){
+		switch ($type(obj)){
+			case 'string':
+				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
+			case 'array':
+				return '[' + String(obj.map(JSON.encode).clean()) + ']';
+			case 'object': case 'hash':
+				var string = [];
+				Hash.each(obj, function(value, key){
+					var json = JSON.encode(value);
+					if (json) string.push(JSON.encode(key) + ':' + json);
+				});
+				return '{' + string + '}';
+			case 'number': case 'boolean': return String(obj);
+			case false: return 'null';
+		}
+		return null;
+	},
+
+	decode: function(string, secure){
+		if ($type(string) != 'string' || !string.length) return null;
+		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
+		return eval('(' + string + ')');
+	}
+
+});
+
+Native.implement([Hash, Array, String, Number], {
+
+	toJSON: function(){
+		return JSON.encode(this);
+	}
+
+});
+/*
+---
+
+script: Cookie.js
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+- Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires:
+- /Options
+
+provides: [Cookie]
+
+...
+*/
+
+var Cookie = new Class({
+
+	Implements: Options,
+
+	options: {
+		path: false,
+		domain: false,
+		duration: false,
+		secure: false,
+		document: document
+	},
+
+	initialize: function(key, options){
+		this.key = key;
+		this.setOptions(options);
+	},
+
+	write: function(value){
+		value = encodeURIComponent(value);
+		if (this.options.domain) value += '; domain=' + this.options.domain;
+		if (this.options.path) value += '; path=' + this.options.path;
+		if (this.options.duration){
+			var date = new Date();
+			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+			value += '; expires=' + date.toGMTString();
+		}
+		if (this.options.secure) value += '; secure';
+		this.options.document.cookie = this.key + '=' + value;
+		return this;
+	},
+
+	read: function(){
+		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+		return (value) ? decodeURIComponent(value[1]) : null;
+	},
+
+	dispose: function(){
+		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
+		return this;
+	}
+
+});
+
+Cookie.write = function(key, value, options){
+	return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+	return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+	return new Cookie(key, options).dispose();
+};
+/*
+---
+
+script: Swiff.js
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits: 
+- Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires:
+- /Options
+- /$util
+
+provides: [Swiff]
+
+...
+*/
+
+var Swiff = new Class({
+
+	Implements: [Options],
+
+	options: {
+		id: null,
+		height: 1,
+		width: 1,
+		container: null,
+		properties: {},
+		params: {
+			quality: 'high',
+			allowScriptAccess: 'always',
+			wMode: 'transparent',
+			swLiveConnect: true
+		},
+		callBacks: {},
+		vars: {}
+	},
+
+	toElement: function(){
+		return this.object;
+	},
+
+	initialize: function(path, options){
+		this.instance = 'Swiff_' + $time();
+
+		this.setOptions(options);
+		options = this.options;
+		var id = this.id = options.id || this.instance;
+		var container = document.id(options.container);
+
+		Swiff.CallBacks[this.instance] = {};
+
+		var params = options.params, vars = options.vars, callBacks = options.callBacks;
+		var properties = $extend({height: options.height, width: options.width}, options.properties);
+
+		var self = this;
+
+		for (var callBack in callBacks){
+			Swiff.CallBacks[this.instance][callBack] = (function(option){
+				return function(){
+					return option.apply(self.object, arguments);
+				};
+			})(callBacks[callBack]);
+			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+		}
+
+		params.flashVars = Hash.toQueryString(vars);
+		if (Browser.Engine.trident){
+			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			params.movie = path;
+		} else {
+			properties.type = 'application/x-shockwave-flash';
+			properties.data = path;
+		}
+		var build = '<object id="' + id + '"';
+		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+		build += '>';
+		for (var param in params){
+			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+		}
+		build += '</object>';
+		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+	},
+
+	replaces: function(element){
+		element = document.id(element, true);
+		element.parentNode.replaceChild(this.toElement(), element);
+		return this;
+	},
+
+	inject: function(element){
+		document.id(element, true).appendChild(this.toElement());
+		return this;
+	},
+
+	remote: function(){
+		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
+	}
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+	return eval(rs);
+};
+/*
+---
+
+script: Fx.js
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires:
+- /Chain
+- /Events
+- /Options
+
+provides: [Fx]
+
+...
+*/
+
+var Fx = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {
+		/*
+		onStart: $empty,
+		onCancel: $empty,
+		onComplete: $empty,
+		*/
+		fps: 50,
+		unit: false,
+		duration: 500,
+		link: 'ignore'
+	},
+
+	initialize: function(options){
+		this.subject = this.subject || this;
+		this.setOptions(options);
+		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
+		var wait = this.options.wait;
+		if (wait === false) this.options.link = 'cancel';
+	},
+
+	getTransition: function(){
+		return function(p){
+			return -(Math.cos(Math.PI * p) - 1) / 2;
+		};
+	},
+
+	step: function(){
+		var time = $time();
+		if (time < this.time + this.options.duration){
+			var delta = this.transition((time - this.time) / this.options.duration);
+			this.set(this.compute(this.from, this.to, delta));
+		} else {
+			this.set(this.compute(this.from, this.to, 1));
+			this.complete();
+		}
+	},
+
+	set: function(now){
+		return now;
+	},
+
+	compute: function(from, to, delta){
+		return Fx.compute(from, to, delta);
+	},
+
+	check: function(){
+		if (!this.timer) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	start: function(from, to){
+		if (!this.check(from, to)) return this;
+		this.from = from;
+		this.to = to;
+		this.time = 0;
+		this.transition = this.getTransition();
+		this.startTimer();
+		this.onStart();
+		return this;
+	},
+
+	complete: function(){
+		if (this.stopTimer()) this.onComplete();
+		return this;
+	},
+
+	cancel: function(){
+		if (this.stopTimer()) this.onCancel();
+		return this;
+	},
+
+	onStart: function(){
+		this.fireEvent('start', this.subject);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', this.subject);
+		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+	},
+
+	onCancel: function(){
+		this.fireEvent('cancel', this.subject).clearChain();
+	},
+
+	pause: function(){
+		this.stopTimer();
+		return this;
+	},
+
+	resume: function(){
+		this.startTimer();
+		return this;
+	},
+
+	stopTimer: function(){
+		if (!this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = $clear(this.timer);
+		return true;
+	},
+
+	startTimer: function(){
+		if (this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
+		return true;
+	}
+
+});
+
+Fx.compute = function(from, to, delta){
+	return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+/*
+---
+
+script: Fx.CSS.js
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires:
+- /Fx
+- /Element.Style
+
+provides: [Fx.CSS]
+
+...
+*/
+
+Fx.CSS = new Class({
+
+	Extends: Fx,
+
+	//prepares the base from/to object
+
+	prepare: function(element, property, values){
+		values = $splat(values);
+		var values1 = values[1];
+		if (!$chk(values1)){
+			values[1] = values[0];
+			values[0] = element.getStyle(property);
+		}
+		var parsed = values.map(this.parse);
+		return {from: parsed[0], to: parsed[1]};
+	},
+
+	//parses a value into an array
+
+	parse: function(value){
+		value = $lambda(value)();
+		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
+		return value.map(function(val){
+			val = String(val);
+			var found = false;
+			Fx.CSS.Parsers.each(function(parser, key){
+				if (found) return;
+				var parsed = parser.parse(val);
+				if ($chk(parsed)) found = {value: parsed, parser: parser};
+			});
+			found = found || {value: val, parser: Fx.CSS.Parsers.String};
+			return found;
+		});
+	},
+
+	//computes by a from and to prepared objects, using their parsers.
+
+	compute: function(from, to, delta){
+		var computed = [];
+		(Math.min(from.length, to.length)).times(function(i){
+			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+		});
+		computed.$family = {name: 'fx:css:value'};
+		return computed;
+	},
+
+	//serves the value as settable
+
+	serve: function(value, unit){
+		if ($type(value) != 'fx:css:value') value = this.parse(value);
+		var returned = [];
+		value.each(function(bit){
+			returned = returned.concat(bit.parser.serve(bit.value, unit));
+		});
+		return returned;
+	},
+
+	//renders the change to an element
+
+	render: function(element, property, value, unit){
+		element.setStyle(property, this.serve(value, unit));
+	},
+
+	//searches inside the page css to find the values for a selector
+
+	search: function(selector){
+		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+		var to = {};
+		Array.each(document.styleSheets, function(sheet, j){
+			var href = sheet.href;
+			if (href && href.contains('://') && !href.contains(document.domain)) return;
+			var rules = sheet.rules || sheet.cssRules;
+			Array.each(rules, function(rule, i){
+				if (!rule.style) return;
+				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+					return m.toLowerCase();
+				}) : null;
+				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
+				Element.Styles.each(function(value, style){
+					if (!rule.style[style] || Element.ShortStyles[style]) return;
+					value = String(rule.style[style]);
+					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
+				});
+			});
+		});
+		return Fx.CSS.Cache[selector] = to;
+	}
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = new Hash({
+
+	Color: {
+		parse: function(value){
+			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+		},
+		compute: function(from, to, delta){
+			return from.map(function(value, i){
+				return Math.round(Fx.compute(from[i], to[i], delta));
+			});
+		},
+		serve: function(value){
+			return value.map(Number);
+		}
+	},
+
+	Number: {
+		parse: parseFloat,
+		compute: Fx.compute,
+		serve: function(value, unit){
+			return (unit) ? value + unit : value;
+		}
+	},
+
+	String: {
+		parse: $lambda(false),
+		compute: $arguments(1),
+		serve: $arguments(0)
+	}
+
+});
+/*
+---
+
+script: Fx.Tween.js
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: 
+- /Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(property, now){
+		if (arguments.length == 1){
+			now = property;
+			property = this.property || this.options.property;
+		}
+		this.render(this.element, property, now, this.options.unit);
+		return this;
+	},
+
+	start: function(property, from, to){
+		if (!this.check(property, from, to)) return this;
+		var args = Array.flatten(arguments);
+		this.property = this.options.property || args.shift();
+		var parsed = this.prepare(this.element, this.property, args);
+		return this.parent(parsed.from, parsed.to);
+	}
+
+});
+
+Element.Properties.tween = {
+
+	set: function(options){
+		var tween = this.retrieve('tween');
+		if (tween) tween.cancel();
+		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('tween')){
+			if (options || !this.retrieve('tween:options')) this.set('tween', options);
+			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
+		}
+		return this.retrieve('tween');
+	}
+
+};
+
+Element.implement({
+
+	tween: function(property, from, to){
+		this.get('tween').start(arguments);
+		return this;
+	},
+
+	fade: function(how){
+		var fade = this.get('tween'), o = 'opacity', toggle;
+		how = $pick(how, 'toggle');
+		switch (how){
+			case 'in': fade.start(o, 1); break;
+			case 'out': fade.start(o, 0); break;
+			case 'show': fade.set(o, 1); break;
+			case 'hide': fade.set(o, 0); break;
+			case 'toggle':
+				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
+				fade.start(o, (flag) ? 0 : 1);
+				this.store('fade:flag', !flag);
+				toggle = true;
+			break;
+			default: fade.start(o, arguments);
+		}
+		if (!toggle) this.eliminate('fade:flag');
+		return this;
+	},
+
+	highlight: function(start, end){
+		if (!end){
+			end = this.retrieve('highlight:original', this.getStyle('background-color'));
+			end = (end == 'transparent') ? '#fff' : end;
+		}
+		var tween = this.get('tween');
+		tween.start('background-color', start || '#ffff88', end).chain(function(){
+			this.setStyle('background-color', this.retrieve('highlight:original'));
+			tween.callChain();
+		}.bind(this));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Morph.js
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires:
+- /Fx.CSS
+
+provides: [Fx.Morph]
+
+...
+*/
+
+Fx.Morph = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(now){
+		if (typeof now == 'string') now = this.search(now);
+		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+		return now;
+	},
+
+	start: function(properties){
+		if (!this.check(properties)) return this;
+		if (typeof properties == 'string') properties = this.search(properties);
+		var from = {}, to = {};
+		for (var p in properties){
+			var parsed = this.prepare(this.element, p, properties[p]);
+			from[p] = parsed.from;
+			to[p] = parsed.to;
+		}
+		return this.parent(from, to);
+	}
+
+});
+
+Element.Properties.morph = {
+
+	set: function(options){
+		var morph = this.retrieve('morph');
+		if (morph) morph.cancel();
+		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('morph')){
+			if (options || !this.retrieve('morph:options')) this.set('morph', options);
+			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
+		}
+		return this.retrieve('morph');
+	}
+
+};
+
+Element.implement({
+
+	morph: function(props){
+		this.get('morph').start(props);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Transitions.js
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires:
+- /Fx
+
+provides: [Fx.Transitions]
+
+...
+*/
+
+Fx.implement({
+
+	getTransition: function(){
+		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+		if (typeof trans == 'string'){
+			var data = trans.split(':');
+			trans = Fx.Transitions;
+			trans = trans[data[0]] || trans[data[0].capitalize()];
+			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+		}
+		return trans;
+	}
+
+});
+
+Fx.Transition = function(transition, params){
+	params = $splat(params);
+	return $extend(transition, {
+		easeIn: function(pos){
+			return transition(pos, params);
+		},
+		easeOut: function(pos){
+			return 1 - transition(1 - pos, params);
+		},
+		easeInOut: function(pos){
+			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
+		}
+	});
+};
+
+Fx.Transitions = new Hash({
+
+	linear: $arguments(0)
+
+});
+
+Fx.Transitions.extend = function(transitions){
+	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+	Pow: function(p, x){
+		return Math.pow(p, x[0] || 6);
+	},
+
+	Expo: function(p){
+		return Math.pow(2, 8 * (p - 1));
+	},
+
+	Circ: function(p){
+		return 1 - Math.sin(Math.acos(p));
+	},
+
+	Sine: function(p){
+		return 1 - Math.sin((1 - p) * Math.PI / 2);
+	},
+
+	Back: function(p, x){
+		x = x[0] || 1.618;
+		return Math.pow(p, 2) * ((x + 1) * p - x);
+	},
+
+	Bounce: function(p){
+		var value;
+		for (var a = 0, b = 1; 1; a += b, b /= 2){
+			if (p >= (7 - 4 * a) / 11){
+				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+				break;
+			}
+		}
+		return value;
+	},
+
+	Elastic: function(p, x){
+		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+	}
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+	Fx.Transitions[transition] = new Fx.Transition(function(p){
+		return Math.pow(p, [i + 2]);
+	});
+});
+/*
+---
+
+script: Request.js
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires:
+- /Element
+- /Chain
+- /Events
+- /Options
+- /Browser
+
+provides: [Request]
+
+...
+*/
+
+var Request = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {/*
+		onRequest: $empty,
+		onComplete: $empty,
+		onCancel: $empty,
+		onSuccess: $empty,
+		onFailure: $empty,
+		onException: $empty,*/
+		url: '',
+		data: '',
+		headers: {
+			'X-Requested-With': 'XMLHttpRequest',
+			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+		},
+		async: true,
+		format: false,
+		method: 'post',
+		link: 'ignore',
+		isSuccess: null,
+		emulation: true,
+		urlEncoded: true,
+		encoding: 'utf-8',
+		evalScripts: false,
+		evalResponse: false,
+		noCache: false
+	},
+
+	initialize: function(options){
+		this.xhr = new Browser.Request();
+		this.setOptions(options);
+		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+		this.headers = new Hash(this.options.headers);
+	},
+
+	onStateChange: function(){
+		if (this.xhr.readyState != 4 || !this.running) return;
+		this.running = false;
+		this.status = 0;
+		$try(function(){
+			this.status = this.xhr.status;
+		}.bind(this));
+		this.xhr.onreadystatechange = $empty;
+		if (this.options.isSuccess.call(this, this.status)){
+			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
+			this.success(this.response.text, this.response.xml);
+		} else {
+			this.response = {text: null, xml: null};
+			this.failure();
+		}
+	},
+
+	isSuccess: function(){
+		return ((this.status >= 200) && (this.status < 300));
+	},
+
+	processScripts: function(text){
+		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
+		return text.stripScripts(this.options.evalScripts);
+	},
+
+	success: function(text, xml){
+		this.onSuccess(this.processScripts(text), xml);
+	},
+
+	onSuccess: function(){
+		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+	},
+
+	failure: function(){
+		this.onFailure();
+	},
+
+	onFailure: function(){
+		this.fireEvent('complete').fireEvent('failure', this.xhr);
+	},
+
+	setHeader: function(name, value){
+		this.headers.set(name, value);
+		return this;
+	},
+
+	getHeader: function(name){
+		return $try(function(){
+			return this.xhr.getResponseHeader(name);
+		}.bind(this));
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!this.check(options)) return this;
+		this.running = true;
+
+		var type = $type(options);
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		var old = this.options;
+		options = $extend({data: old.data, url: old.url, method: old.method}, options);
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+		switch ($type(data)){
+			case 'element': data = document.id(data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(data);
+		}
+
+		if (this.options.format){
+			var format = 'format=' + this.options.format;
+			data = (data) ? format + '&' + data : format;
+		}
+
+		if (this.options.emulation && !['get', 'post'].contains(method)){
+			var _method = '_method=' + method;
+			data = (data) ? _method + '&' + data : _method;
+			method = 'post';
+		}
+
+		if (this.options.urlEncoded && method == 'post'){
+			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
+		}
+
+		if (this.options.noCache){
+			var noCache = 'noCache=' + new Date().getTime();
+			data = (data) ? noCache + '&' + data : noCache;
+		}
+
+		var trimPosition = url.lastIndexOf('/');
+		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+		if (data && method == 'get'){
+			url = url + (url.contains('?') ? '&' : '?') + data;
+			data = null;
+		}
+
+		this.xhr.open(method.toUpperCase(), url, this.options.async);
+
+		this.xhr.onreadystatechange = this.onStateChange.bind(this);
+
+		this.headers.each(function(value, key){
+			try {
+				this.xhr.setRequestHeader(key, value);
+			} catch (e){
+				this.fireEvent('exception', [key, value]);
+			}
+		}, this);
+
+		this.fireEvent('request');
+		this.xhr.send(data);
+		if (!this.options.async) this.onStateChange();
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.xhr.abort();
+		this.xhr.onreadystatechange = $empty;
+		this.xhr = new Browser.Request();
+		this.fireEvent('cancel');
+		return this;
+	}
+
+});
+
+(function(){
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+	methods[method] = function(){
+		var params = Array.link(arguments, {url: String.type, data: $defined});
+		return this.send($extend(params, {method: method}));
+	};
+});
+
+Request.implement(methods);
+
+})();
+
+Element.Properties.send = {
+
+	set: function(options){
+		var send = this.retrieve('send');
+		if (send) send.cancel();
+		return this.eliminate('send').store('send:options', $extend({
+			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+		}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('send')){
+			if (options || !this.retrieve('send:options')) this.set('send', options);
+			this.store('send', new Request(this.retrieve('send:options')));
+		}
+		return this.retrieve('send');
+	}
+
+};
+
+Element.implement({
+
+	send: function(url){
+		var sender = this.get('send');
+		sender.send({data: this, url: url || sender.options.url});
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.HTML.js
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires:
+- /Request
+- /Element
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.HTML = new Class({
+
+	Extends: Request,
+
+	options: {
+		update: false,
+		append: false,
+		evalScripts: true,
+		filter: false
+	},
+
+	processHTML: function(text){
+		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+		text = (match) ? match[1] : text;
+
+		var container = new Element('div');
+
+		return $try(function(){
+			var root = '<root>' + text + '</root>', doc;
+			if (Browser.Engine.trident){
+				doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = false;
+				doc.loadXML(root);
+			} else {
+				doc = new DOMParser().parseFromString(root, 'text/xml');
+			}
+			root = doc.getElementsByTagName('root')[0];
+			if (!root) return null;
+			for (var i = 0, k = root.childNodes.length; i < k; i++){
+				var child = Element.clone(root.childNodes[i], true, true);
+				if (child) container.grab(child);
+			}
+			return container;
+		}) || container.set('html', text);
+	},
+
+	success: function(text){
+		var options = this.options, response = this.response;
+
+		response.html = text.stripScripts(function(script){
+			response.javascript = script;
+		});
+
+		var temp = this.processHTML(response.html);
+
+		response.tree = temp.childNodes;
+		response.elements = temp.getElements('*');
+
+		if (options.filter) response.tree = response.elements.filter(options.filter);
+		if (options.update) document.id(options.update).empty().set('html', response.html);
+		else if (options.append) document.id(options.append).adopt(temp.getChildren());
+		if (options.evalScripts) $exec(response.javascript);
+
+		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+	}
+
+});
+
+Element.Properties.load = {
+
+	set: function(options){
+		var load = this.retrieve('load');
+		if (load) load.cancel();
+		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
+	},
+
+	get: function(options){
+		if (options || ! this.retrieve('load')){
+			if (options || !this.retrieve('load:options')) this.set('load', options);
+			this.store('load', new Request.HTML(this.retrieve('load:options')));
+		}
+		return this.retrieve('load');
+	}
+
+};
+
+Element.implement({
+
+	load: function(){
+		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.JSON.js
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires:
+- /Request JSON
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.JSON = new Class({
+
+	Extends: Request,
+
+	options: {
+		secure: true
+	},
+
+	initialize: function(options){
+		this.parent(options);
+		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
+	},
+
+	success: function(text){
+		this.response.json = JSON.decode(text, this.options.secure);
+		this.onSuccess(this.response.json, text);
+	}
+
+});
+/*
+---
+
+script: More.js
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+- Guillermo Rauch
+- Thomas Aylott
+- Scott Kyle
+
+requires:
+- core:1.2.4/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+	'version': '1.2.4.4',
+	'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
+};/*
+---
+
+script: MooTools.Lang.js
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Events
+- /MooTools.More
+
+provides: [MooTools.Lang]
+
+...
+*/
+
+(function(){
+
+	var data = {
+		language: 'en-US',
+		languages: {
+			'en-US': {}
+		},
+		cascades: ['en-US']
+	};
+	
+	var cascaded;
+
+	MooTools.lang = new Events();
+
+	$extend(MooTools.lang, {
+
+		setLanguage: function(lang){
+			if (!data.languages[lang]) return this;
+			data.language = lang;
+			this.load();
+			this.fireEvent('langChange', lang);
+			return this;
+		},
+
+		load: function() {
+			var langs = this.cascade(this.getCurrentLanguage());
+			cascaded = {};
+			$each(langs, function(set, setName){
+				cascaded[setName] = this.lambda(set);
+			}, this);
+		},
+
+		getCurrentLanguage: function(){
+			return data.language;
+		},
+
+		addLanguage: function(lang){
+			data.languages[lang] = data.languages[lang] || {};
+			return this;
+		},
+
+		cascade: function(lang){
+			var cascades = (data.languages[lang] || {}).cascades || [];
+			cascades.combine(data.cascades);
+			cascades.erase(lang).push(lang);
+			var langs = cascades.map(function(lng){
+				return data.languages[lng];
+			}, this);
+			return $merge.apply(this, langs);
+		},
+
+		lambda: function(set) {
+			(set || {}).get = function(key, args){
+				return $lambda(set[key]).apply(this, $splat(args));
+			};
+			return set;
+		},
+
+		get: function(set, key, args){
+			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
+		},
+
+		set: function(lang, set, members){
+			this.addLanguage(lang);
+			langData = data.languages[lang];
+			if (!langData[set]) langData[set] = {};
+			$extend(langData[set], members);
+			if (lang == this.getCurrentLanguage()){
+				this.load();
+				this.fireEvent('langChange', lang);
+			}
+			return this;
+		},
+
+		list: function(){
+			return Hash.getKeys(data.languages);
+		}
+
+	});
+
+})();/*
+---
+
+script: Log.js
+
+description: Provides basic logging functionality for plugins to implement.
+
+license: MIT-style license
+
+authors:
+- Guillermo Rauch
+- Thomas Aylott
+- Scott Kyle
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Log]
+
+...
+*/
+
+(function(){
+
+var global = this;
+
+var log = function(){
+	if (global.console && console.log){
+		try {
+			console.log.apply(console, arguments);
+		} catch(e) {
+			console.log(Array.slice(arguments));
+		}
+	} else {
+		Log.logged.push(arguments);
+	}
+	return this;
+};
+
+var disabled = function(){
+	this.logged.push(arguments);
+	return this;
+};
+
+this.Log = new Class({
+	
+	logged: [],
+	
+	log: disabled,
+	
+	resetLog: function(){
+		this.logged.empty();
+		return this;
+	},
+
+	enableLog: function(){
+		this.log = log;
+		this.logged.each(function(args){
+			this.log.apply(this, args);
+		}, this);
+		return this.resetLog();
+	},
+
+	disableLog: function(){
+		this.log = disabled;
+		return this;
+	}
+	
+});
+
+Log.extend(new Log).enableLog();
+
+// legacy
+Log.logger = function(){
+	return this.log.apply(this, arguments);
+};
+
+})();/*
+---
+
+script: Depender.js
+
+description: A stand alone dependency loader for the MooTools library.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Events
+- core:1.2.4/Request.JSON
+- /MooTools.More
+- /Log
+
+provides: Depender
+
+...
+*/
+
+var Depender = {
+
+	options: {
+		/* 
+		onRequire: $empty(options),
+		onRequirementLoaded: $empty([scripts, options]),
+		onScriptLoaded: $empty({
+			script: script, 
+			totalLoaded: percentOfTotalLoaded, 
+			loaded: scriptsState
+		}),
+		serial: false,
+		target: null,
+		noCache: false,
+		log: false,*/
+		loadedSources: [],
+		loadedScripts: ['Core', 'Browser', 'Array', 'String', 'Function', 'Number', 'Hash', 'Element', 'Event', 'Element.Event', 'Class', 'DomReady', 'Class.Extras', 'Request', 'JSON', 'Request.JSON', 'More', 'Depender', 'Log'],
+		useScriptInjection: true
+	},
+
+	loaded: [],
+
+	sources: {},
+
+	libs: {},
+
+	include: function(libs){
+		this.log('include: ', libs);
+		this.mapLoaded = false;
+		var loader = function(data){
+			this.libs = $merge(this.libs, data);
+			$each(this.libs, function(data, lib){
+				if (data.scripts) this.loadSource(lib, data.scripts);
+			}, this);
+		}.bind(this);
+		if ($type(libs) == 'string'){
+			this.log('fetching libs ', libs);
+			this.request(libs, loader);
+		} else {
+			loader(libs);
+		}
+		return this;
+	},
+
+	required: [],
+
+	require: function(options){
+		var loaded = function(){
+			var scripts = this.calculateDependencies(options.scripts);
+			if (options.sources){
+				options.sources.each(function(source){
+					scripts.combine(this.libs[source].files);
+				}, this);
+			}
+			if (options.serial) scripts.combine(this.getLoadedScripts());
+			options.scripts = scripts;
+			this.required.push(options);
+			this.fireEvent('require', options);
+			this.loadScripts(options.scripts);
+		};
+		if (this.mapLoaded) loaded.call(this);
+		else this.addEvent('mapLoaded', loaded.bind(this));
+		return this;
+	},
+
+	cleanDoubleSlash: function(str){
+		if (!str) return str;
+		var prefix = '';
+		if (str.test(/^http:\/\//)){
+			prefix = 'http://';
+			str = str.substring(7, str.length);
+		}
+		str = str.replace(/\/\//g, '/');
+		return prefix + str;
+	},
+
+	request: function(url, callback){
+		new Request.JSON({
+			url: url,
+			secure: false,
+			onSuccess: callback
+		}).send();
+	},
+
+	loadSource: function(lib, source){
+		if (this.libs[lib].files){
+			this.dataLoaded();
+			return;
+		}
+		this.log('loading source: ', source);
+		this.request(this.cleanDoubleSlash(source + '/scripts.json'), function(result){
+			this.log('loaded source: ', source);
+			this.libs[lib].files = result;
+			this.dataLoaded();
+		}.bind(this));
+	},
+
+	dataLoaded: function(){
+		var loaded = true;
+		$each(this.libs, function(v, k){
+			if (!this.libs[k].files) loaded = false;
+		}, this);
+		if (loaded){
+			this.mapTree();
+			this.mapLoaded = true;
+			this.calculateLoaded();
+			this.lastLoaded = this.getLoadedScripts().getLength();
+			this.fireEvent('mapLoaded');
+			this.removeEvents('mapLoaded');
+		}
+	},
+
+	calculateLoaded: function(){
+		var set = function(script){
+			this.scriptsState[script] = true;
+		}.bind(this);
+		if (this.options.loadedScripts) this.options.loadedScripts.each(set);
+		if (this.options.loadedSources){
+			this.options.loadedSources.each(function(lib){
+				$each(this.libs[lib].files, function(dir){
+					$each(dir, function(data, file){
+						set(file);
+					}, this);
+				}, this);
+			}, this);
+		}
+	},
+
+	deps: {},
+
+	pathMap: {},
+
+	mapTree: function(){
+		$each(this.libs, function(data, source){
+			$each(data.files, function(scripts, folder){
+				$each(scripts, function(details, script){
+					var path = source + ':' + folder + ':' + script;
+					if (this.deps[path]) return;
+					this.deps[path] = details.deps;
+					this.pathMap[script] = path;
+				}, this);
+			}, this);
+		}, this);
+	},
+
+	getDepsForScript: function(script){
+		return this.deps[this.pathMap[script]] || [];
+	},
+
+	calculateDependencies: function(scripts){
+		var reqs = [];
+		$splat(scripts).each(function(script){
+			if (script == 'None' || !script) return;
+			var deps = this.getDepsForScript(script);
+			if (!deps){
+				if (window.console && console.warn) console.warn('dependencies not mapped: script: %o, map: %o, :deps: %o', script, this.pathMap, this.deps);
+			} else {
+				deps.each(function(scr){
+					if (scr == script || scr == 'None' || !scr) return;
+					if (!reqs.contains(scr)) reqs.combine(this.calculateDependencies(scr));
+					reqs.include(scr);
+				}, this);
+			}
+			reqs.include(script);
+		}, this);
+		return reqs;
+	},
+
+	getPath: function(script){
+		try {
+			var chunks = this.pathMap[script].split(':');
+			var lib = this.libs[chunks[0]];
+			var dir = (lib.path || lib.scripts) + '/';
+			chunks.shift();
+			return this.cleanDoubleSlash(dir + chunks.join('/') + '.js');
+		} catch(e){
+			return script;
+		}
+	},
+
+	loadScripts: function(scripts){
+		scripts = scripts.filter(function(s){
+			if (!this.scriptsState[s] && s != 'None'){
+				this.scriptsState[s] = false;
+				return true;
+			}
+		}, this);
+		if (scripts.length){
+			scripts.each(function(scr){
+				this.loadScript(scr);
+			}, this);
+		} else {
+			this.check();
+		}
+	},
+
+	toLoad: [],
+
+	loadScript: function(script){
+		if (this.scriptsState[script] && this.toLoad.length){
+			this.loadScript(this.toLoad.shift());
+			return;
+		} else if (this.loading){
+			this.toLoad.push(script);
+			return;
+		}
+		var finish = function(){
+			this.loading = false;
+			this.scriptLoaded(script);
+			if (this.toLoad.length) this.loadScript(this.toLoad.shift());
+		}.bind(this);
+		var error = function(){
+			this.log('could not load: ', scriptPath);
+		}.bind(this);
+		this.loading = true;
+		var scriptPath = this.getPath(script);
+		if (this.options.useScriptInjection){
+			this.log('injecting script: ', scriptPath);
+			var loaded = function(){
+				this.log('loaded script: ', scriptPath);
+				finish();
+			}.bind(this);
+			new Element('script', {
+				src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
+				events: {
+					load: loaded,
+					readystatechange: function(){
+						if (['loaded', 'complete'].contains(this.readyState)) loaded();
+					},
+					error: error
+				}
+			}).inject(this.options.target || document.head);
+		} else {
+			this.log('requesting script: ', scriptPath);
+			new Request({
+				url: scriptPath,
+				noCache: this.options.noCache,
+				onComplete: function(js){
+					this.log('loaded script: ', scriptPath);
+					$exec(js);
+					finish();
+				}.bind(this),
+				onFailure: error,
+				onException: error
+			}).send();
+		}
+	},
+
+	scriptsState: $H(),
+	
+	getLoadedScripts: function(){
+		return this.scriptsState.filter(function(state){
+			return state;
+		});
+	},
+
+	scriptLoaded: function(script){
+		this.log('loaded script: ', script);
+		this.scriptsState[script] = true;
+		this.check();
+		var loaded = this.getLoadedScripts();
+		var loadedLength = loaded.getLength();
+		var toLoad = this.scriptsState.getLength();
+		this.fireEvent('scriptLoaded', {
+			script: script,
+			totalLoaded: (loadedLength / toLoad * 100).round(),
+			currentLoaded: ((loadedLength - this.lastLoaded) / (toLoad - this.lastLoaded) * 100).round(),
+			loaded: loaded
+		});
+		if (loadedLength == toLoad) this.lastLoaded = loadedLength;
+	},
+
+	lastLoaded: 0,
+
+	check: function(){
+		var incomplete = [];
+		this.required.each(function(required){
+			var loaded = [];
+			required.scripts.each(function(script){
+				if (this.scriptsState[script]) loaded.push(script);
+			}, this);
+			if (required.onStep){
+				required.onStep({
+					percent: loaded.length / required.scripts.length * 100,
+					scripts: loaded
+				});
+			};
+			if (required.scripts.length != loaded.length) return;
+			required.callback();
+			this.required.erase(required);
+			this.fireEvent('requirementLoaded', [loaded, required]);
+		}, this);
+	}
+
+};
+
+$extend(Depender, new Events);
+$extend(Depender, new Options);
+$extend(Depender, new Log);
+
+Depender._setOptions = Depender.setOptions;
+Depender.setOptions = function(){
+	Depender._setOptions.apply(Depender, arguments);
+	if (this.options.log) Depender.enableLog();
+	return this;
+};
+/*
+---
+
+script: Class.Refactor.js
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Class.refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+	$each(refactors, function(item, name){
+		var origin = original.prototype[name];
+		if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
+			var old = this.previous;
+			this.previous = origin;
+			var value = item.apply(this, arguments);
+			this.previous = old;
+			return value;
+		}); else original.implement(name, item);
+	});
+
+	return original;
+
+};/*
+---
+
+script: Class.Binds.js
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+    return binds;
+};
+
+Class.Mutators.initialize = function(initialize){
+	return function(){
+		$splat(this.Binds).each(function(name){
+			var original = this[name];
+			if (original) this[name] = original.bind(this);
+		}, this);
+		return initialize.apply(this, arguments);
+	};
+};
+/*
+---
+
+script: Class.Occlude.js
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+- Aaron Newton
+
+requires: 
+- core/1.2.4/Class
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+	occlude: function(property, element){
+		element = document.id(element || this.element);
+		var instance = element.retrieve(property || this.property);
+		if (instance && !$defined(this.occluded))
+			return this.occluded = instance;
+
+		this.occluded = false;
+		element.store(property || this.property, this);
+		return this.occluded;
+	}
+
+});/*
+---
+
+script: Chain.Wait.js
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+- Aaron Newton
+
+requires: 
+- core:1.2.4/Chain 
+- core:1.2.4/Element
+- core:1.2.4/Fx
+- /MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+	var wait = {
+		wait: function(duration){
+			return this.chain(function(){
+				this.callChain.delay($pick(duration, 500), this);
+			}.bind(this));
+		}
+	};
+
+	Chain.implement(wait);
+
+	if (window.Fx){
+		Fx.implement(wait);
+		['Css', 'Tween', 'Elements'].each(function(cls){
+			if (Fx[cls]) Fx[cls].implement(wait);
+		});
+	}
+
+	Element.implement({
+		chains: function(effects){
+			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
+				effect = this.get(effect);
+				if (!effect) return;
+				effect.setOptions({
+					link:'chain'
+				});
+			}, this);
+			return this;
+		},
+		pauseFx: function(duration, effect){
+			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
+			return this;
+		}
+	});
+
+})();/*
+---
+
+script: Array.Extras.js
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Array
+
+provides: [Array.Extras]
+
+...
+*/
+Array.implement({
+
+	min: function(){
+		return Math.min.apply(null, this);
+	},
+
+	max: function(){
+		return Math.max.apply(null, this);
+	},
+
+	average: function(){
+		return this.length ? this.sum() / this.length : 0;
+	},
+
+	sum: function(){
+		var result = 0, l = this.length;
+		if (l){
+			do {
+				result += this[--l];
+			} while (l);
+		}
+		return result;
+	},
+
+	unique: function(){
+		return [].combine(this);
+	},
+
+	shuffle: function(){
+		for (var i = this.length; i && --i;){
+			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+			this[i] = this[r];
+			this[r] = temp;
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Date.English.US.js
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.English.US]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Date', {
+
+	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['month', 'date', 'year'],
+	shortDate: '%m/%d/%Y',
+	shortTime: '%I:%M%p',
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1st, 2nd, 3rd, etc.
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'less than a minute ago',
+	minuteAgo: 'about a minute ago',
+	minutesAgo: '{delta} minutes ago',
+	hourAgo: 'about an hour ago',
+	hoursAgo: 'about {delta} hours ago',
+	dayAgo: '1 day ago',
+	daysAgo: '{delta} days ago',
+	weekAgo: '1 week ago',
+	weeksAgo: '{delta} weeks ago',
+	monthAgo: '1 month ago',
+	monthsAgo: '{delta} months ago',
+	yearAgo: '1 year ago',
+	yearsAgo: '{delta} years ago',
+	lessThanMinuteUntil: 'less than a minute from now',
+	minuteUntil: 'about a minute from now',
+	minutesUntil: '{delta} minutes from now',
+	hourUntil: 'about an hour from now',
+	hoursUntil: 'about {delta} hours from now',
+	dayUntil: '1 day from now',
+	daysUntil: '{delta} days from now',
+	weekUntil: '1 week from now',
+	weeksUntil: '{delta} weeks from now',
+	monthUntil: '1 month from now',
+	monthsUntil: '{delta} months from now',
+	yearUntil: '1 year from now',
+	yearsUntil: '{delta} years from now'
+
+});
+/*
+---
+
+script: Date.js
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+- Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+- Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- core:1.2.4/Number
+- core:1.2.4/Lang
+- core:1.2.4/Date.English.US
+- /MooTools.More
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+if (!Date.now) Date.now = $time;
+
+Date.Methods = {
+	ms: 'Milliseconds',
+	year: 'FullYear',
+	min: 'Minutes',
+	mo: 'Month',
+	sec: 'Seconds',
+	hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
+	Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(what, length){
+	return new Array(length - String(what).length + 1).join('0') + what;
+};
+
+Date.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				prop = prop.toLowerCase();
+				var m = Date.Methods;
+				if (m[prop]) this['set' + m[prop]](value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		prop = prop.toLowerCase();
+		var m = Date.Methods;
+		if (m[prop]) return this['get' + m[prop]]();
+		return null;
+	},
+
+	clone: function(){
+		return new Date(this.get('time'));
+	},
+
+	increment: function(interval, times){
+		interval = interval || 'day';
+		times = $pick(times, 1);
+
+		switch (interval){
+			case 'year':
+				return this.increment('month', times * 12);
+			case 'month':
+				var d = this.get('date');
+				this.set('date', 1).set('mo', this.get('mo') + times);
+				return this.set('date', d.min(this.get('lastdayofmonth')));
+			case 'week':
+				return this.increment('day', times * 7);
+			case 'day':
+				return this.set('date', this.get('date') + times);
+		}
+
+		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+		return this.set('time', this.get('time') + times * Date.units[interval]());
+	},
+
+	decrement: function(interval, times){
+		return this.increment(interval, -1 * $pick(times, 1));
+	},
+
+	isLeapYear: function(){
+		return Date.isLeapYear(this.get('year'));
+	},
+
+	clearTime: function(){
+		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+	},
+
+	diff: function(date, resolution){
+		if ($type(date) == 'string') date = Date.parse(date);
+		
+		return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
+	},
+
+	getLastDayOfMonth: function(){
+		return Date.daysInMonth(this.get('mo'), this.get('year'));
+	},
+
+	getDayOfYear: function(){
+		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) 
+			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+	},
+
+	getWeek: function(){
+		return (this.get('dayofyear') / 7).ceil();
+	},
+	
+	getOrdinal: function(day){
+		return Date.getMsg('ordinal', day || this.get('date'));
+	},
+
+	getTimezone: function(){
+		return this.toString()
+			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+	},
+
+	getGMTOffset: function(){
+		var off = this.get('timezoneOffset');
+		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+	},
+
+	setAMPM: function(ampm){
+		ampm = ampm.toUpperCase();
+		var hr = this.get('hr');
+		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+		return this;
+	},
+
+	getAMPM: function(){
+		return (this.get('hr') < 12) ? 'AM' : 'PM';
+	},
+
+	parse: function(str){
+		this.set('time', Date.parse(str));
+		return this;
+	},
+
+	isValid: function(date) {
+		return !!(date || this).valueOf();
+	},
+
+	format: function(f){
+		if (!this.isValid()) return 'invalid date';
+		f = f || '%x %X';
+		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
+		var d = this;
+		return f.replace(/%([a-z%])/gi,
+			function($0, $1){
+				switch ($1){
+					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
+					case 'A': return Date.getMsg('days')[d.get('day')];
+					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
+					case 'B': return Date.getMsg('months')[d.get('month')];
+					case 'c': return d.toString();
+					case 'd': return pad(d.get('date'), 2);
+					case 'H': return pad(d.get('hr'), 2);
+					case 'I': return ((d.get('hr') % 12) || 12);
+					case 'j': return pad(d.get('dayofyear'), 3);
+					case 'm': return pad((d.get('mo') + 1), 2);
+					case 'M': return pad(d.get('min'), 2);
+					case 'o': return d.get('ordinal');
+					case 'p': return Date.getMsg(d.get('ampm'));
+					case 'S': return pad(d.get('seconds'), 2);
+					case 'U': return pad(d.get('week'), 2);
+					case 'w': return d.get('day');
+					case 'x': return d.format(Date.getMsg('shortDate'));
+					case 'X': return d.format(Date.getMsg('shortTime'));
+					case 'y': return d.get('year').toString().substr(2);
+					case 'Y': return d.get('year');
+					case 'T': return d.get('GMTOffset');
+					case 'Z': return d.get('Timezone');
+				}
+				return $1;
+			}
+		);
+	},
+
+	toISOString: function(){
+		return this.format('iso8601');
+	}
+
+});
+
+Date.alias('toISOString', 'toJSON');
+Date.alias('diff', 'compare');
+Date.alias('format', 'strftime');
+
+var formats = {
+	db: '%Y-%m-%d %H:%M:%S',
+	compact: '%Y%m%dT%H%M%S',
+	iso8601: '%Y-%m-%dT%H:%M:%S%T',
+	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
+	'short': '%d %b %H:%M',
+	'long': '%B %d, %Y %H:%M'
+};
+
+var parsePatterns = [];
+var nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+	var ret = -1;
+	var translated = Date.getMsg(type + 's');
+
+	switch ($type(word)){
+		case 'object':
+			ret = translated[word.get(type)];
+			break;
+		case 'number':
+			ret = translated[month - 1];
+			if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
+			break;
+		case 'string':
+			var match = translated.filter(function(name){
+				return this.test(name);
+			}, new RegExp('^' + word, 'i'));
+			if (!match.length)    throw new Error('Invalid ' + type + ' string');
+			if (match.length > 1) throw new Error('Ambiguous ' + type);
+			ret = match[0];
+	}
+
+	return (num) ? translated.indexOf(ret) : ret;
+};
+
+Date.extend({
+
+	getMsg: function(key, args) {
+		return MooTools.lang.get('Date', key, args);
+	},
+
+	units: {
+		ms: $lambda(1),
+		second: $lambda(1000),
+		minute: $lambda(60000),
+		hour: $lambda(3600000),
+		day: $lambda(86400000),
+		week: $lambda(608400000),
+		month: function(month, year){
+			var d = new Date;
+			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
+		},
+		year: function(year){
+			year = year || new Date().get('year');
+			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+		}
+	},
+
+	daysInMonth: function(month, year){
+		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+	},
+
+	isLeapYear: function(year){
+		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+	},
+
+	parse: function(from){
+		var t = $type(from);
+		if (t == 'number') return new Date(from);
+		if (t != 'string') return from;
+		from = from.clean();
+		if (!from.length) return null;
+
+		var parsed;
+		parsePatterns.some(function(pattern){
+			var bits = pattern.re.exec(from);
+			return (bits) ? (parsed = pattern.handler(bits)) : false;
+		});
+
+		return parsed || new Date(nativeParse(from));
+	},
+
+	parseDay: function(day, num){
+		return parseWord('day', day, num);
+	},
+
+	parseMonth: function(month, num){
+		return parseWord('month', month, num);
+	},
+
+	parseUTC: function(value){
+		var localDate = new Date(value);
+		var utcSeconds = Date.UTC(
+			localDate.get('year'),
+			localDate.get('mo'),
+			localDate.get('date'),
+			localDate.get('hr'),
+			localDate.get('min'),
+			localDate.get('sec')
+		);
+		return new Date(utcSeconds);
+	},
+
+	orderIndex: function(unit){
+		return Date.getMsg('dateOrder').indexOf(unit) + 1;
+	},
+
+	defineFormat: function(name, format){
+		formats[name] = format;
+	},
+
+	defineFormats: function(formats){
+		for (var name in formats) Date.defineFormat(name, formats[name]);
+	},
+
+	parsePatterns: parsePatterns, // this is deprecated
+	
+	defineParser: function(pattern){
+		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+	},
+	
+	defineParsers: function(){
+		Array.flatten(arguments).each(Date.defineParser);
+	},
+	
+	define2DigitYearStart: function(year){
+		startYear = year % 100;
+		startCentury = year - startYear;
+	}
+
+});
+
+var startCentury = 1900;
+var startYear = 70;
+
+var regexOf = function(type){
+	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+		return name.substr(0, 3);
+	}).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+	switch(key){
+		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+			return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
+		case 'X':
+			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
+	}
+	return null;
+};
+
+var keys = {
+	d: /[0-2]?[0-9]|3[01]/,
+	H: /[01]?[0-9]|2[0-3]/,
+	I: /0?[1-9]|1[0-2]/,
+	M: /[0-5]?\d/,
+	s: /\d+/,
+	o: /[a-z]*/,
+	p: /[ap]\.?m\.?/,
+	y: /\d{2}|\d{4}/,
+	Y: /\d{4}/,
+	T: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+	currentLanguage = language;
+	
+	keys.a = keys.A = regexOf('days');
+	keys.b = keys.B = regexOf('months');
+	
+	parsePatterns.each(function(pattern, i){
+		if (pattern.format) parsePatterns[i] = build(pattern.format);
+	});
+};
+
+var build = function(format){
+	if (!currentLanguage) return {format: format};
+	
+	var parsed = [];
+	var re = (format.source || format) // allow format to be regex
+	 .replace(/%([a-z])/gi,
+		function($0, $1){
+			return replacers($1) || $0;
+		}
+	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+	 .replace(/%([a-z%])/gi,
+		function($0, $1){
+			var p = keys[$1];
+			if (!p) return $1;
+			parsed.push($1);
+			return '(' + p.source + ')';
+		}
+	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words
+
+	return {
+		format: format,
+		re: new RegExp('^' + re + '$', 'i'),
+		handler: function(bits){
+			bits = bits.slice(1).associate(parsed);
+			var date = new Date().clearTime();
+			if ('d' in bits) handle.call(date, 'd', 1);
+			if ('m' in bits || 'b' in bits || 'B' in bits) handle.call(date, 'm', 1);
+			for (var key in bits) handle.call(date, key, bits[key]);
+			return date;
+		}
+	};
+};
+
+var handle = function(key, value){
+	if (!value) return this;
+
+	switch(key){
+		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+		case 'd': return this.set('date', value);
+		case 'H': case 'I': return this.set('hr', value);
+		case 'm': return this.set('mo', value - 1);
+		case 'M': return this.set('min', value);
+		case 'p': return this.set('ampm', value.replace(/\./g, ''));
+		case 'S': return this.set('sec', value);
+		case 's': return this.set('ms', ('0.' + value) * 1000);
+		case 'w': return this.set('day', value);
+		case 'Y': return this.set('year', value);
+		case 'y':
+			value = +value;
+			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+			return this.set('year', value);
+		case 'T':
+			if (value == 'Z') value = '+00';
+			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+			return this.set('time', this - offset * 60000);
+	}
+
+	return this;
+};
+
+Date.defineParsers(
+	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+	'%o %b %d %X %T %Y' // "Thu Oct 22 08:11:23 +0000 2009"
+);
+
+MooTools.lang.addEvent('langChange', function(language){
+	if (MooTools.lang.get('Date')) recompile(language);
+}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
+
+})();/*
+---
+
+script: Date.Extras.js
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- /Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+	timeDiffInWords: function(relative_to){
+		return Date.distanceOfTimeInWords(this, relative_to || new Date);
+	},
+
+	timeDiff: function(to, joiner){
+		if (to == null) to = new Date;
+		var delta = ((to - this) / 1000).toInt();
+		if (!delta) return '0s';
+		
+		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
+		var duration, vals = [];
+		
+		for (var step in durations){
+			if (!delta) break;
+			if ((duration = durations[step])){
+				vals.unshift((delta % duration) + step);
+				delta = (delta / duration).toInt();
+			} else {
+				vals.unshift(delta + step);
+			}
+		}
+		
+		return vals.join(joiner || ':');
+	}
+
+});
+
+Date.alias('timeDiffInWords', 'timeAgoInWords');
+
+Date.extend({
+
+	distanceOfTimeInWords: function(from, to){
+		return Date.getTimePhrase(((to - from) / 1000).toInt());
+	},
+
+	getTimePhrase: function(delta){
+		var suffix = (delta < 0) ? 'Until' : 'Ago';
+		if (delta < 0) delta *= -1;
+		
+		var units = {
+			minute: 60,
+			hour: 60,
+			day: 24,
+			week: 7,
+			month: 52 / 12,
+			year: 12,
+			eon: Infinity
+		};
+		
+		var msg = 'lessThanMinute';
+		
+		for (var unit in units){
+			var interval = units[unit];
+			if (delta < 1.5 * interval){
+				if (delta > 0.75 * interval) msg = unit;
+				break;
+			}
+			delta /= interval;
+			msg = unit + 's';
+		}
+		
+		return Date.getMsg(msg + suffix).substitute({delta: delta.round()});
+	}
+
+});
+
+
+Date.defineParsers(
+
+	{
+		// "today", "tomorrow", "yesterday"
+		re: /^(?:tod|tom|yes)/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			switch(bits[0]){
+				case 'tom': return d.increment();
+				case 'yes': return d.decrement();
+				default: 	return d;
+			}
+		}
+	},
+
+	{
+		// "next Wednesday", "last Thursday"
+		re: /^(next|last) ([a-z]+)$/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			var day = d.getDay();
+			var newDay = Date.parseDay(bits[2], true);
+			var addDays = newDay - day;
+			if (newDay <= day) addDays += 7;
+			if (bits[1] == 'last') addDays -= 7;
+			return d.set('date', d.getDate() + addDays);
+		}
+	}
+
+);
+/*
+---
+
+script: Hash.Extras.js
+
+description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Hash.base
+- /MooTools.More
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+	getFromPath: function(notation){
+		var source = this.getClean();
+		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
+			if (!source) return null;
+			var prop = arguments[2] || arguments[1] || arguments[0];
+			source = (prop in source) ? source[prop] : null;
+			return match;
+		});
+		return source;
+	},
+
+	cleanValues: function(method){
+		method = method || $defined;
+		this.each(function(v, k){
+			if (!method(v)) this.erase(k);
+		}, this);
+		return this;
+	},
+
+	run: function(){
+		var args = arguments;
+		this.each(function(v, k){
+			if ($type(v) == 'function') v.run(args);
+		});
+	}
+
+});/*
+---
+
+script: String.Extras.js
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Guillermo Rauch
+
+requires:
+- core:1.2.4/String
+- core:1.2.4/$util
+- core:1.2.4/Array
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+  
+var special = ['À','à','Ã?','á','Â','â','Ã','ã','Ä','ä','Ã…','Ã¥','Ä‚','ă','Ä„','Ä…','Ć','ć','ÄŒ','Ä?','Ç','ç', 'ÄŽ','Ä?','Ä?','Ä‘', 'È','è','É','é','Ê','ê','Ë','ë','Äš','Ä›','Ę','Ä™', 'Äž','ÄŸ','ÃŒ','ì','Ã?','í','ÃŽ','î','Ã?','ï', 'Ĺ','ĺ','Ľ','ľ','Å?','Å‚', 'Ñ','ñ','Ň','ň','Ń','Å„','Ã’','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','Å‘','Ř','Å™','Å”','Å•','Å ','Å¡','Åž','ÅŸ','Åš','Å›', 'Ť','Å¥','Ť','Å¥','Å¢','Å£','Ù','ù','Ú','ú','Û','û','Ãœ','ü','Å®','ů', 'Ÿ','ÿ','ý','Ã?','Ž','ž','Ź','ź','Å»','ż', 'Þ','þ','Ã?','ð','ß','Å’','Å“','Æ','æ','µ'];
+
+var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];
+
+var tidymap = {
+	"[\xa0\u2002\u2003\u2009]": " ",
+	"\xb7": "*",
+	"[\u2018\u2019]": "'",
+	"[\u201c\u201d]": '"',
+	"\u2026": "...",
+	"\u2013": "-",
+	"\u2014": "--",
+	"\uFFFD": "&raquo;"
+};
+
+var getRegForTag = function(tag, contents) {
+	tag = tag || '';
+	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
+	reg = new RegExp(regstr, "gi");
+	return reg;
+};
+
+String.implement({
+
+	standardize: function(){
+		var text = this;
+		special.each(function(ch, i){
+			text = text.replace(new RegExp(ch, 'g'), standard[i]);
+		});
+		return text;
+	},
+
+	repeat: function(times){
+		return new Array(times + 1).join(this);
+	},
+
+	pad: function(length, str, dir){
+		if (this.length >= length) return this;
+		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
+		if (!dir || dir == 'right') return this + pad;
+		if (dir == 'left') return pad + this;
+		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+	},
+
+	getTags: function(tag, contents){
+		return this.match(getRegForTag(tag, contents)) || [];
+	},
+
+	stripTags: function(tag, contents){
+		return this.replace(getRegForTag(tag, contents), '');
+	},
+
+	tidy: function(){
+		var txt = this.toString();
+		$each(tidymap, function(value, key){
+			txt = txt.replace(new RegExp(key, 'g'), value);
+		});
+		return txt;
+	}
+
+});
+
+})();/*
+---
+
+script: String.QueryString.js
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- /MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+	parseQueryString: function(){
+		var vars = this.split(/[&;]/), res = {};
+		if (vars.length) vars.each(function(val){
+			var index = val.indexOf('='),
+				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
+				value = decodeURIComponent(val.substr(index + 1)),
+				obj = res;
+			keys.each(function(key, i){
+				var current = obj[key];
+				if(i < keys.length - 1)
+					obj = obj[key] = current || {};
+				else if($type(current) == 'array')
+					current.push(value);
+				else
+					obj[key] = $defined(current) ? [current, value] : value;
+			});
+		});
+		return res;
+	},
+
+	cleanQueryString: function(method){
+		return this.split('&').filter(function(val){
+			var index = val.indexOf('='),
+			key = index < 0 ? '' : val.substr(0, index),
+			value = val.substr(index + 1);
+			return method ? method.run([key, value]) : $chk(value);
+		}).join('&');
+	}
+
+});/*
+---
+
+script: URI.js
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge
+- Aaron Newton
+
+requires:
+- core:1.2.4/Selectors
+- /String.QueryString
+
+provides: URI
+
+...
+*/
+
+var URI = new Class({
+
+	Implements: Options,
+
+	options: {
+		/*base: false*/
+	},
+
+	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+	initialize: function(uri, options){
+		this.setOptions(options);
+		var base = this.options.base || URI.base;
+		if(!uri) uri = base;
+		
+		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
+		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+	},
+
+	parse: function(value, base){
+		var bits = value.match(this.regex);
+		if (!bits) return false;
+		bits.shift();
+		return this.merge(bits.associate(this.parts), base);
+	},
+
+	merge: function(bits, base){
+		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+		if (base){
+			this.parts.every(function(part){
+				if (bits[part]) return false;
+				bits[part] = base[part] || '';
+				return true;
+			});
+		}
+		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+		return bits;
+	},
+
+	parseDirectory: function(directory, baseDirectory) {
+		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+		if (!directory.test(URI.regs.directoryDot)) return directory;
+		var result = [];
+		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+			if (dir == '..' && result.length > 0) result.pop();
+			else if (dir != '.') result.push(dir);
+		});
+		return result.join('/') + '/';
+	},
+
+	combine: function(bits){
+		return bits.value || bits.scheme + '://' +
+			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+			(bits.directory || '/') + (bits.file || '') +
+			(bits.query ? '?' + bits.query : '') +
+			(bits.fragment ? '#' + bits.fragment : '');
+	},
+
+	set: function(part, value, base){
+		if (part == 'value'){
+			var scheme = value.match(URI.regs.scheme);
+			if (scheme) scheme = scheme[1];
+			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
+			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+		} else if (part == 'data') {
+			this.setData(value);
+		} else {
+			this.parsed[part] = value;
+		}
+		return this;
+	},
+
+	get: function(part, base){
+		switch(part){
+			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+			case 'data' : return this.getData();
+		}
+		return this.parsed[part] || '';
+	},
+
+	go: function(){
+		document.location.href = this.toString();
+	},
+
+	toURI: function(){
+		return this;
+	},
+
+	getData: function(key, part){
+		var qs = this.get(part || 'query');
+		if (!$chk(qs)) return key ? null : {};
+		var obj = qs.parseQueryString();
+		return key ? obj[key] : obj;
+	},
+
+	setData: function(values, merge, part){
+		if (typeof values == 'string'){
+			data = this.getData();
+			data[arguments[0]] = arguments[1];
+			values = data;
+		} else if (merge) {
+			values = $merge(this.getData(), values);
+		}
+		return this.set(part || 'query', Hash.toQueryString(values));
+	},
+
+	clearData: function(part){
+		return this.set(part || 'query', '');
+	}
+
+});
+
+URI.prototype.toString = URI.prototype.valueOf = function(){
+	return this.get('value');
+};
+
+URI.regs = {
+	endSlash: /\/$/,
+	scheme: /^(\w+):/,
+	directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
+
+String.implement({
+
+	toURI: function(options){
+		return new URI(this, options);
+	}
+
+});/*
+---
+
+script: URI.Relative.js
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge
+
+
+requires:
+- /Class.refactor
+- /URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+	combine: function(bits, base){
+		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+			return this.previous.apply(this, arguments);
+		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+		var baseDir = base.directory.split('/'),
+			relDir = bits.directory.split('/'),
+			path = '',
+			offset;
+
+		var i = 0;
+		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+		return (path || (bits.file ? '' : './')) + end;
+	},
+
+	toAbsolute: function(base){
+		base = new URI(base);
+		if (base) base.set('directory', '').set('file', '');
+		return this.toRelative(base);
+	},
+
+	toRelative: function(base){
+		return this.get('value', new URI(base));
+	}
+
+});/*
+---
+
+script: Element.Forms.js
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+	tidy: function(){
+		this.set('value', this.get('value').tidy());
+	},
+
+	getTextInRange: function(start, end){
+		return this.get('value').substring(start, end);
+	},
+
+	getSelectedText: function(){
+		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		return document.selection.createRange().text;
+	},
+
+	getSelectedRange: function() {
+		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
+		var pos = {start: 0, end: 0};
+		var range = this.getDocument().selection.createRange();
+		if (!range || range.parentElement() != this) return pos;
+		var dup = range.duplicate();
+		if (this.type == 'text') {
+			pos.start = 0 - dup.moveStart('character', -100000);
+			pos.end = pos.start + range.text.length;
+		} else {
+			var value = this.get('value');
+			var offset = value.length;
+			dup.moveToElementText(this);
+			dup.setEndPoint('StartToEnd', range);
+			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+			pos.end = offset - dup.text.length;
+			dup.setEndPoint('StartToStart', range);
+			pos.start = offset - dup.text.length;
+		}
+		return pos;
+	},
+
+	getSelectionStart: function(){
+		return this.getSelectedRange().start;
+	},
+
+	getSelectionEnd: function(){
+		return this.getSelectedRange().end;
+	},
+
+	setCaretPosition: function(pos){
+		if (pos == 'end') pos = this.get('value').length;
+		this.selectRange(pos, pos);
+		return this;
+	},
+
+	getCaretPosition: function(){
+		return this.getSelectedRange().start;
+	},
+
+	selectRange: function(start, end){
+		if (this.setSelectionRange) {
+			this.focus();
+			this.setSelectionRange(start, end);
+		} else {
+			var value = this.get('value');
+			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+			start = value.substr(0, start).replace(/\r/g, '').length;
+			var range = this.createTextRange();
+			range.collapse(true);
+			range.moveEnd('character', start + diff);
+			range.moveStart('character', start);
+			range.select();
+		}
+		return this;
+	},
+
+	insertAtCursor: function(value, select){
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
+		else this.setCaretPosition(pos.start + value.length);
+		return this;
+	},
+
+	insertAroundCursor: function(options, select){
+		options = $extend({
+			before: '',
+			defaultMiddle: '',
+			after: ''
+		}, options);
+		var value = this.getSelectedText() || options.defaultMiddle;
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		if (pos.start == pos.end){
+			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+		} else {
+			var current = text.substring(pos.start, pos.end);
+			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+			var selStart = pos.start + options.before.length;
+			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
+			else this.setCaretPosition(selStart + text.length);
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Elements.From.js
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Elements.from]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+	if ($pick(excludeScripts, true)) text = text.stripScripts();
+
+	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
+
+	if (match){
+		container = new Element('table');
+		var tag = match[1].toLowerCase();
+		if (['td', 'th', 'tr'].contains(tag)){
+			container = new Element('tbody').inject(container);
+			if (tag != 'tr') container = new Element('tr').inject(container);
+		}
+	}
+
+	return (container || new Element('div')).set('html', text).getChildren();
+};/*
+---
+
+script: Element.Delegation.js
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+credits:
+- "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Daniel Steigerwald
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Selectors
+- /MooTools.More
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(addEvent, removeEvent){
+	
+	var match = /(.*?):relay\(([^)]+)\)$/,
+		combinators = /[+>~\s]/,
+		splitType = function(type){
+			var bits = type.match(match);
+			return !bits ? {event: type} : {
+				event: bits[1],
+				selector: bits[2]
+			};
+		},
+		check = function(e, selector){
+			var t = e.target;
+			if (combinators.test(selector = selector.trim())){
+				var els = this.getElements(selector);
+				for (var i = els.length; i--; ){
+					var el = els[i];
+					if (t == el || el.hasChild(t)) return el;
+				}
+			} else {
+				for ( ; t && t != this; t = t.parentNode){
+					if (Element.match(t, selector)) return document.id(t);
+				}
+			}
+			return null;
+		};
+
+	Element.implement({
+
+		addEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var monitors = this.retrieve('$moo:delegateMonitors', {});
+				if (!monitors[type]){
+					var monitor = function(e){
+						var el = check.call(this, e, splitted.selector);
+						if (el) this.fireEvent(type, [e, el], 0, el);
+					}.bind(this);
+					monitors[type] = monitor;
+					addEvent.call(this, splitted.event, monitor);
+				}
+			}
+			return addEvent.apply(this, arguments);
+		},
+
+		removeEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var events = this.retrieve('events');
+				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
+
+				if (fn) removeEvent.apply(this, [type, fn]);
+				else removeEvent.apply(this, type);
+
+				events = this.retrieve('events');
+				if (events && events[type] && events[type].keys.length == 0){
+					var monitors = this.retrieve('$moo:delegateMonitors', {});
+					removeEvent.apply(this, [splitted.event, monitors[type]]);
+					delete monitors[type];
+				}
+				return this;
+			}
+			return removeEvent.apply(this, arguments);
+		},
+
+		fireEvent: function(type, args, delay, bind){
+			var events = this.retrieve('events');
+			if (!events || !events[type]) return this;
+			events[type].keys.each(function(fn){
+				fn.create({bind: bind || this, delay: delay, arguments: args})();
+			}, this);
+			return this;
+		}
+
+	});
+
+})(Element.prototype.addEvent, Element.prototype.removeEvent);/*
+---
+
+script: Element.Measure.js
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Style
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+Element.implement({
+
+	measure: function(fn){
+		var vis = function(el) {
+			return !!(!el || el.offsetHeight || el.offsetWidth);
+		};
+		if (vis(this)) return fn.apply(this);
+		var parent = this.getParent(),
+			restorers = [],
+			toMeasure = []; 
+		while (!vis(parent) && parent != document.body) {
+			toMeasure.push(parent.expose());
+			parent = parent.getParent();
+		}
+		var restore = this.expose();
+		var result = fn.apply(this);
+		restore();
+		toMeasure.each(function(restore){
+			restore();
+		});
+		return result;
+	},
+
+	expose: function(){
+		if (this.getStyle('display') != 'none') return $empty;
+		var before = this.style.cssText;
+		this.setStyles({
+			display: 'block',
+			position: 'absolute',
+			visibility: 'hidden'
+		});
+		return function(){
+			this.style.cssText = before;
+		}.bind(this);
+	},
+
+	getDimensions: function(options){
+		options = $merge({computeSize: false},options);
+		var dim = {};
+		var getSize = function(el, options){
+			return (options.computeSize)?el.getComputedSize(options):el.getSize();
+		};
+		var parent = this.getParent('body');
+		if (parent && this.getStyle('display') == 'none'){
+			dim = this.measure(function(){
+				return getSize(this, options);
+			});
+		} else if (parent){
+			try { //safari sometimes crashes here, so catch it
+				dim = getSize(this, options);
+			}catch(e){}
+		} else {
+			dim = {x: 0, y: 0};
+		}
+		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
+	},
+
+	getComputedSize: function(options){
+		options = $merge({
+			styles: ['padding','border'],
+			plains: {
+				height: ['top','bottom'],
+				width: ['left','right']
+			},
+			mode: 'both'
+		}, options);
+		var size = {width: 0,height: 0};
+		switch (options.mode){
+			case 'vertical':
+				delete size.width;
+				delete options.plains.width;
+				break;
+			case 'horizontal':
+				delete size.height;
+				delete options.plains.height;
+				break;
+		}
+		var getStyles = [];
+		//this function might be useful in other places; perhaps it should be outside this function?
+		$each(options.plains, function(plain, key){
+			plain.each(function(edge){
+				options.styles.each(function(style){
+					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
+				});
+			});
+		});
+		var styles = {};
+		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
+		var subtracted = [];
+		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
+			var capitalized = key.capitalize();
+			size['total' + capitalized] = size['computed' + capitalized] = 0;
+			plain.each(function(edge){ //top, left, right, bottom
+				size['computed' + edge.capitalize()] = 0;
+				getStyles.each(function(style, i){ //padding, border, etc.
+					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
+					if (style.test(edge)){
+						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
+						size['total' + capitalized] = size['total' + capitalized] + styles[style];
+						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
+					}
+					//if width != width (so, padding-left, for instance), then subtract that from the total
+					if (style.test(edge) && key != style &&
+						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
+						subtracted.push(style);
+						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
+					}
+				});
+			});
+		});
+
+		['Width', 'Height'].each(function(value){
+			var lower = value.toLowerCase();
+			if(!$chk(size[lower])) return;
+
+			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
+			size['total' + value] = size[lower] + size['total' + value];
+			delete size['computed' + value];
+		}, this);
+
+		return $extend(styles, size);
+	}
+
+});/*
+---
+
+script: Element.Pin.js
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+	var supportsPositionFixed = false;
+	window.addEvent('domready', function(){
+		var test = new Element('div').setStyles({
+			position: 'fixed',
+			top: 0,
+			right: 0
+		}).inject(document.body);
+		supportsPositionFixed = (test.offsetTop === 0);
+		test.dispose();
+	});
+
+	Element.implement({
+
+		pin: function(enable){
+			if (this.getStyle('display') == 'none') return null;
+			
+			var p,
+					scroll = window.getScroll();
+			if (enable !== false){
+				p = this.getPosition();
+				if (!this.retrieve('pinned')){
+					var pos = {
+						top: p.y - scroll.y,
+						left: p.x - scroll.x
+					};
+					if (supportsPositionFixed){
+						this.setStyle('position', 'fixed').setStyles(pos);
+					} else {
+						this.store('pinnedByJS', true);
+						this.setStyles({
+							position: 'absolute',
+							top: p.y,
+							left: p.x
+						}).addClass('isPinned');
+						this.store('scrollFixer', (function(){
+							if (this.retrieve('pinned'))
+								var scroll = window.getScroll();
+								this.setStyles({
+									top: pos.top.toInt() + scroll.y,
+									left: pos.left.toInt() + scroll.x
+								});
+						}).bind(this));
+						window.addEvent('scroll', this.retrieve('scrollFixer'));
+					}
+					this.store('pinned', true);
+				}
+			} else {
+				var op;
+				if (!Browser.Engine.trident){
+					var parent = this.getParent();
+					op = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+				}
+				p = this.getPosition(op);
+				this.store('pinned', false);
+				var reposition;
+				if (supportsPositionFixed && !this.retrieve('pinnedByJS')){
+					reposition = {
+						top: p.y + scroll.y,
+						left: p.x + scroll.x
+					};
+				} else {
+					this.store('pinnedByJS', false);
+					window.removeEvent('scroll', this.retrieve('scrollFixer'));
+					reposition = {
+						top: p.y,
+						left: p.x
+					};
+				}
+				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
+			}
+			return this;
+		},
+
+		unpin: function(){
+			return this.pin(false);
+		},
+
+		togglepin: function(){
+			this.pin(!this.retrieve('pinned'));
+		}
+
+	});
+
+})();/*
+---
+
+script: Element.Position.js
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Element.Measure
+
+provides: [Elements.Position]
+
+...
+*/
+
+(function(){
+
+var original = Element.prototype.position;
+
+Element.implement({
+
+	position: function(options){
+		//call original position if the options are x/y values
+		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
+		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
+		options = $merge({
+			// minimum: { x: 0, y: 0 },
+			// maximum: { x: 0, y: 0},
+			relativeTo: document.body,
+			position: {
+				x: 'center', //left, center, right
+				y: 'center' //top, center, bottom
+			},
+			edge: false,
+			offset: {x: 0, y: 0},
+			returnPos: false,
+			relFixedPosition: false,
+			ignoreMargins: false,
+			ignoreScroll: false,
+			allowNegative: false
+		}, options);
+		//compute the offset of the parent positioned element if this element is in one
+		var parentOffset = {x: 0, y: 0}, 
+				parentPositioned = false;
+		/* dollar around getOffsetParent should not be necessary, but as it does not return
+		 * a mootools extended element in IE, an error occurs on the call to expose. See:
+		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
+		var offsetParent = this.measure(function(){
+			return document.id(this.getOffsetParent());
+		});
+		if (offsetParent && offsetParent != this.getDocument().body){
+			parentOffset = offsetParent.measure(function(){
+				return this.getPosition();
+			});
+			parentPositioned = offsetParent != document.id(options.relativeTo);
+			options.offset.x = options.offset.x - parentOffset.x;
+			options.offset.y = options.offset.y - parentOffset.y;
+		}
+		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
+		//topRight, topLeft, centerTop, centerBottom, center
+		var fixValue = function(option){
+			if ($type(option) != 'string') return option;
+			option = option.toLowerCase();
+			var val = {};
+			if (option.test('left')) val.x = 'left';
+			else if (option.test('right')) val.x = 'right';
+			else val.x = 'center';
+			if (option.test('upper') || option.test('top')) val.y = 'top';
+			else if (option.test('bottom')) val.y = 'bottom';
+			else val.y = 'center';
+			return val;
+		};
+		options.edge = fixValue(options.edge);
+		options.position = fixValue(options.position);
+		if (!options.edge){
+			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
+			else options.edge = {x:'left', y:'top'};
+		}
+
+		this.setStyle('position', 'absolute');
+		var rel = document.id(options.relativeTo) || document.body,
+				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
+				top = calc.y, left = calc.x;
+
+		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
+		var pos = {},
+				prefY = options.offset.y,
+				prefX = options.offset.x,
+				winSize = window.getSize();
+		switch(options.position.x){
+			case 'left':
+				pos.x = left + prefX;
+				break;
+			case 'right':
+				pos.x = left + prefX + rel.offsetWidth;
+				break;
+			default: //center
+				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
+				break;
+		}
+		switch(options.position.y){
+			case 'top':
+				pos.y = top + prefY;
+				break;
+			case 'bottom':
+				pos.y = top + prefY + rel.offsetHeight;
+				break;
+			default: //center
+				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
+				break;
+		}
+		if (options.edge){
+			var edgeOffset = {};
+
+			switch(options.edge.x){
+				case 'left':
+					edgeOffset.x = 0;
+					break;
+				case 'right':
+					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
+					break;
+				default: //center
+					edgeOffset.x = -(dim.totalWidth/2);
+					break;
+			}
+			switch(options.edge.y){
+				case 'top':
+					edgeOffset.y = 0;
+					break;
+				case 'bottom':
+					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
+					break;
+				default: //center
+					edgeOffset.y = -(dim.totalHeight/2);
+					break;
+			}
+			pos.x += edgeOffset.x;
+			pos.y += edgeOffset.y;
+		}
+		pos = {
+			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
+			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
+		};
+		var xy = {left: 'x', top: 'y'};
+		['minimum', 'maximum'].each(function(minmax) {
+			['left', 'top'].each(function(lr) {
+				var val = options[minmax] ? options[minmax][xy[lr]] : null;
+				if (val != null && pos[lr] < val) pos[lr] = val;
+			});
+		});
+		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
+			var winScroll = window.getScroll();
+			pos.top+= winScroll.y;
+			pos.left+= winScroll.x;
+		}
+		if (options.ignoreScroll) {
+			var relScroll = rel.getScroll();
+			pos.top-= relScroll.y;
+			pos.left-= relScroll.x;
+		}
+		if (options.ignoreMargins) {
+			pos.left += (
+				options.edge.x == 'right' ? dim['margin-right'] : 
+				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) : 
+					- dim['margin-left']
+			);
+			pos.top += (
+				options.edge.y == 'bottom' ? dim['margin-bottom'] : 
+				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) : 
+					- dim['margin-top']
+			);
+		}
+		pos.left = Math.ceil(pos.left);
+		pos.top = Math.ceil(pos.top);
+		if (options.returnPos) return pos;
+		else this.setStyles(pos);
+		return this;
+	}
+
+});
+
+})();/*
+---
+
+script: Element.Shortcuts.js
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+	isDisplayed: function(){
+		return this.getStyle('display') != 'none';
+	},
+
+	isVisible: function(){
+		var w = this.offsetWidth,
+			h = this.offsetHeight;
+		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
+	},
+
+	toggle: function(){
+		return this[this.isDisplayed() ? 'hide' : 'show']();
+	},
+
+	hide: function(){
+		var d;
+		try {
+			//IE fails here if the element is not in the dom
+			d = this.getStyle('display');
+		} catch(e){}
+		return this.store('originalDisplay', d || '').setStyle('display', 'none');
+	},
+
+	show: function(display){
+		display = display || this.retrieve('originalDisplay') || 'block';
+		return this.setStyle('display', (display == 'none') ? 'block' : display);
+	},
+
+	swapClass: function(remove, add){
+		return this.removeClass(remove).addClass(add);
+	}
+
+});
+/*
+---
+
+script: IframeShim.js
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/Options Events
+- /Element.Position
+- /Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+var IframeShim = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		className: 'iframeShim',
+		src: 'javascript:false;document.write("");',
+		display: false,
+		zIndex: null,
+		margin: 0,
+		offset: {x: 0, y: 0},
+		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
+	},
+
+	property: 'IframeShim',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.makeShim();
+		return this;
+	},
+
+	makeShim: function(){
+		if(this.options.browsers){
+			var zIndex = this.element.getStyle('zIndex').toInt();
+
+			if (!zIndex){
+				zIndex = 1;
+				var pos = this.element.getStyle('position');
+				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+				this.element.setStyle('zIndex', zIndex);
+			}
+			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+			if (zIndex < 0) zIndex = 1;
+			this.shim = new Element('iframe', {
+				src: this.options.src,
+				scrolling: 'no',
+				frameborder: 0,
+				styles: {
+					zIndex: zIndex,
+					position: 'absolute',
+					border: 'none',
+					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+				},
+				'class': this.options.className
+			}).store('IframeShim', this);
+			var inject = (function(){
+				this.shim.inject(this.element, 'after');
+				this[this.options.display ? 'show' : 'hide']();
+				this.fireEvent('inject');
+			}).bind(this);
+			if (!IframeShim.ready) window.addEvent('load', inject);
+			else inject();
+		} else {
+			this.position = this.hide = this.show = this.dispose = $lambda(this);
+		}
+	},
+
+	position: function(){
+		if (!IframeShim.ready || !this.shim) return this;
+		var size = this.element.measure(function(){ 
+			return this.getSize(); 
+		});
+		if (this.options.margin != undefined){
+			size.x = size.x - (this.options.margin * 2);
+			size.y = size.y - (this.options.margin * 2);
+			this.options.offset.x += this.options.margin;
+			this.options.offset.y += this.options.margin;
+		}
+		this.shim.set({width: size.x, height: size.y}).position({
+			relativeTo: this.element,
+			offset: this.options.offset
+		});
+		return this;
+	},
+
+	hide: function(){
+		if (this.shim) this.shim.setStyle('display', 'none');
+		return this;
+	},
+
+	show: function(){
+		if (this.shim) this.shim.setStyle('display', 'block');
+		return this.position();
+	},
+
+	dispose: function(){
+		if (this.shim) this.shim.dispose();
+		return this;
+	},
+
+	destroy: function(){
+		if (this.shim) this.shim.destroy();
+		return this;
+	}
+
+});
+
+window.addEvent('load', function(){
+	IframeShim.ready = true;
+});/*
+---
+
+script: Mask.js
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- /Class.Binds
+- /Element.Position
+- /IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['position'],
+
+	options: {
+		// onShow: $empty,
+		// onHide: $empty,
+		// onDestroy: $empty,
+		// onClick: $empty,
+		//inject: {
+		//  where: 'after',
+		//  target: null,
+		//},
+		// hideOnClick: false,
+		// id: null,
+		// destroyOnHide: false,
+		style: {},
+		'class': 'mask',
+		maskMargins: false,
+		useIframeShim: true,
+		iframeShimOptions: {}
+	},
+
+	initialize: function(target, options){
+		this.target = document.id(target) || document.id(document.body);
+		this.target.store('Mask', this);
+		this.setOptions(options);
+		this.render();
+		this.inject();
+	},
+	
+	render: function() {
+		this.element = new Element('div', {
+			'class': this.options['class'],
+			id: this.options.id || 'mask-' + $time(),
+			styles: $merge(this.options.style, {
+				display: 'none'
+			}),
+			events: {
+				click: function(){
+					this.fireEvent('click');
+					if (this.options.hideOnClick) this.hide();
+				}.bind(this)
+			}
+		});
+		this.hidden = true;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	inject: function(target, where){
+		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
+		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
+		this.element.inject(target, where);
+		if (this.options.useIframeShim) {
+			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+			this.addEvents({
+				show: this.shim.show.bind(this.shim),
+				hide: this.shim.hide.bind(this.shim),
+				destroy: this.shim.destroy.bind(this.shim)
+			});
+		}
+	},
+
+	position: function(){
+		this.resize(this.options.width, this.options.height);
+		this.element.position({
+			relativeTo: this.target,
+			position: 'topLeft',
+			ignoreMargins: !this.options.maskMargins,
+			ignoreScroll: this.target == document.body
+		});
+		return this;
+	},
+
+	resize: function(x, y){
+		var opt = {
+			styles: ['padding', 'border']
+		};
+		if (this.options.maskMargins) opt.styles.push('margin');
+		var dim = this.target.getComputedSize(opt);
+		if (this.target == document.body) {
+			var win = window.getSize();
+			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+		}
+		this.element.setStyles({
+			width: $pick(x, dim.totalWidth, dim.x),
+			height: $pick(y, dim.totalHeight, dim.y)
+		});
+		return this;
+	},
+
+	show: function(){
+		if (!this.hidden) return this;
+		window.addEvent('resize', this.position);
+		this.position();
+		this.showMask.apply(this, arguments);
+		return this;
+	},
+
+	showMask: function(){
+		this.element.setStyle('display', 'block');
+		this.hidden = false;
+		this.fireEvent('show');
+	},
+
+	hide: function(){
+		if (this.hidden) return this;
+		window.removeEvent('resize', this.position);
+		this.hideMask.apply(this, arguments);
+		if (this.options.destroyOnHide) return this.destroy();
+		return this;
+	},
+
+	hideMask: function(){
+		this.element.setStyle('display', 'none');
+		this.hidden = true;
+		this.fireEvent('hide');
+	},
+
+	toggle: function(){
+		this[this.hidden ? 'show' : 'hide']();
+	},
+
+	destroy: function(){
+		this.hide();
+		this.element.destroy();
+		this.fireEvent('destroy');
+		this.target.eliminate('mask');
+	}
+
+});
+
+Element.Properties.mask = {
+
+	set: function(options){
+		var mask = this.retrieve('mask');
+		return this.eliminate('mask').store('mask:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('mask')){
+			if (this.retrieve('mask')) this.retrieve('mask').destroy();
+			if (options || !this.retrieve('mask:options')) this.set('mask', options);
+			this.store('mask', new Mask(this, this.retrieve('mask:options')));
+		}
+		return this.retrieve('mask');
+	}
+
+};
+
+Element.implement({
+
+	mask: function(options){
+		this.get('mask', options).show();
+		return this;
+	},
+
+	unmask: function(){
+		this.get('mask').hide();
+		return this;
+	}
+
+});/*
+---
+
+script: Spinner.js
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Tween
+- /Class.refactor
+- /Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+	Extends: Mask,
+
+	options: {
+		/*message: false,*/
+		'class':'spinner',
+		containerPosition: {},
+		content: {
+			'class':'spinner-content'
+		},
+		messageContainer: {
+			'class':'spinner-msg'
+		},
+		img: {
+			'class':'spinner-img'
+		},
+		fxOptions: {
+			link: 'chain'
+		}
+	},
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		this.target.store('spinner', this);
+
+		//add this to events for when noFx is true; parent methods handle hide/show
+		var deactivate = function(){ this.active = false; }.bind(this);
+		this.addEvents({
+			hide: deactivate,
+			show: deactivate
+		});
+	},
+
+	render: function(){
+		this.parent();
+		this.element.set('id', this.options.id || 'spinner-'+$time());
+		this.content = document.id(this.options.content) || new Element('div', this.options.content);
+		this.content.inject(this.element);
+		if (this.options.message) {
+			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+			this.msg.inject(this.content);
+		}
+		if (this.options.img) {
+			this.img = document.id(this.options.img) || new Element('div', this.options.img);
+			this.img.inject(this.content);
+		}
+		this.element.set('tween', this.options.fxOptions);
+	},
+
+	show: function(noFx){
+		if (this.active) return this.chain(this.show.bind(this));
+		if (!this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	showMask: function(noFx){
+		var pos = function(){
+			this.content.position($merge({
+				relativeTo: this.element
+			}, this.options.containerPosition));
+		}.bind(this);
+		if (noFx) {
+			this.parent();
+			pos();
+		} else {
+			this.element.setStyles({
+				display: 'block',
+				opacity: 0
+			}).tween('opacity', this.options.style.opacity || 0.9);
+			pos();
+			this.hidden = false;
+			this.fireEvent('show');
+			this.callChain();
+		}
+	},
+
+	hide: function(noFx){
+		if (this.active) return this.chain(this.hide.bind(this));
+		if (this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	hideMask: function(noFx){
+		if (noFx) return this.parent();
+		this.element.tween('opacity', 0).get('tween').chain(function(){
+			this.element.setStyle('display', 'none');
+			this.hidden = true;
+			this.fireEvent('hide');
+			this.callChain();
+		}.bind(this));
+	},
+
+	destroy: function(){
+		this.content.destroy();
+		this.parent();
+		this.target.eliminate('spinner');
+	}
+
+});
+
+Spinner.implement(new Chain);
+
+if (window.Request) {
+	Request = Class.refactor(Request, {
+		
+		options: {
+			useSpinner: false,
+			spinnerOptions: {},
+			spinnerTarget: false
+		},
+		
+		initialize: function(options){
+			this._send = this.send;
+			this.send = function(options){
+				if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
+				else this._send(options);
+				return this;
+			};
+			this.previous(options);
+			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+			if (this.options.useSpinner && update) {
+				this.spinner = update.get('spinner', this.options.spinnerOptions);
+				['onComplete', 'onException', 'onCancel'].each(function(event){
+					this.addEvent(event, this.spinner.hide.bind(this.spinner));
+				}, this);
+			}
+		},
+		
+		getSpinner: function(){
+			return this.spinner;
+		}
+		
+	});
+}
+
+Element.Properties.spinner = {
+
+	set: function(options){
+		var spinner = this.retrieve('spinner');
+		return this.eliminate('spinner').store('spinner:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('spinner')){
+			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
+			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
+			new Spinner(this, this.retrieve('spinner:options'));
+		}
+		return this.retrieve('spinner');
+	}
+
+};
+
+Element.implement({
+
+	spin: function(options){
+		this.get('spinner', options).show();
+		return this;
+	},
+
+	unspin: function(){
+		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
+		this.get('spinner', opt.options).hide(opt.callback);
+		return this;
+	}
+
+});/*
+---
+
+script: Form.Request.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Request.HTML
+- /Class.Binds
+- /Class.Occlude
+- /Spinner
+- /String.QueryString
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+	Form.Request = new Class({
+
+		Binds: ['onSubmit', 'onFormValidate'],
+
+		Implements: [Options, Events, Class.Occlude],
+
+		options: {
+			//onFailure: $empty,
+			//onSuccess: #empty, //aliased to onComplete,
+			//onSend: $empty
+			requestOptions: {
+				evalScripts: true,
+				useSpinner: true,
+				emulation: false,
+				link: 'ignore'
+			},
+			extraData: {},
+			resetForm: true
+		},
+
+		property: 'form.request',
+
+		initialize: function(form, update, options) {
+			this.element = document.id(form);
+			if (this.occlude()) return this.occluded;
+			this.update = document.id(update);
+			this.setOptions(options);
+			this.makeRequest();
+			if (this.options.resetForm) {
+				this.request.addEvent('success', function(){
+					$try(function(){ this.element.reset(); }.bind(this));
+					if (window.OverText) OverText.update();
+				}.bind(this));
+			}
+			this.attach();
+		},
+
+		toElement: function() {
+			return this.element;
+		},
+
+		makeRequest: function(){
+			this.request = new Request.HTML($merge({
+					update: this.update,
+					emulation: false,
+					spinnerTarget: this.element,
+					method: this.element.get('method') || 'post'
+			}, this.options.requestOptions)).addEvents({
+				success: function(text, xml){
+					['complete', 'success'].each(function(evt){
+						this.fireEvent(evt, [this.update, text, xml]);
+					}, this);
+				}.bind(this),
+				failure: function(xhr){
+					this.fireEvent('complete').fireEvent('failure', xhr);
+				}.bind(this),
+				exception: function(){
+					this.fireEvent('failure', xhr);
+				}.bind(this)
+			});
+		},
+
+		attach: function(attach){
+			attach = $pick(attach, true);
+			method = attach ? 'addEvent' : 'removeEvent';
+			
+			var fv = this.element.retrieve('validator');
+			if (fv) fv[method]('onFormValidate', this.onFormValidate);
+			if (!fv || !attach) this.element[method]('submit', this.onSubmit);
+		},
+
+		detach: function(){
+			this.attach(false);
+		},
+
+		//public method
+		enable: function(){
+			this.attach();
+		},
+
+		//public method
+		disable: function(){
+			this.detach();
+		},
+
+		onFormValidate: function(valid, form, e) {
+			var fv = this.element.retrieve('validator');
+			if (valid || (fv && !fv.options.stopOnFailure)) {
+				if (e && e.stop) e.stop();
+				this.send();
+			}
+		},
+
+		onSubmit: function(e){
+			if (this.element.retrieve('validator')) {
+				//form validator was created after Form.Request
+				this.detach();
+				return;
+			}
+			e.stop();
+			this.send();
+		},
+
+		send: function(){
+			var str = this.element.toQueryString().trim();
+			var data = $H(this.options.extraData).toQueryString();
+			if (str) str += "&" + data;
+			else str = data;
+			this.fireEvent('send', [this.element, str.parseQueryString()]);
+			this.request.send({data: str, url: this.element.get("action")});
+			return this;
+		}
+
+	});
+
+	Element.Properties.formRequest = {
+
+		set: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			var updater = this.retrieve('form.request');
+			if (update) {
+				if (updater) updater.update = document.id(update);
+				this.store('form.request:update', update);
+			}
+			if (opt.options) {
+				if (updater) updater.setOptions(opt.options);
+				this.store('form.request:options', opt.options);
+			}
+			return this;
+		},
+
+		get: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			if (opt.options || update || !this.retrieve('form.request')){
+				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
+				if (update) this.set('form.request', update);
+				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
+			}
+			return this.retrieve('form.request');
+		}
+
+	};
+
+	Element.implement({
+
+		formUpdate: function(update, options){
+			this.get('form.request', update, options).send();
+			return this;
+		}
+
+	});
+
+})();/*
+---
+
+script: Fx.Reveal.js
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Morph
+- /Element.Shortcuts
+- /Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+Fx.Reveal = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {/*	  
+		onShow: $empty(thisElement),
+		onHide: $empty(thisElement),
+		onComplete: $empty(thisElement),
+		heightOverride: null,
+		widthOverride: null, */
+		link: 'cancel',
+		styles: ['padding', 'border', 'margin'],
+		transitionOpacity: !Browser.Engine.trident4,
+		mode: 'vertical',
+		display: 'block',
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
+	},
+
+	dissolve: function(){
+		try {
+			if (!this.hiding && !this.showing){
+				if (this.element.getStyle('display') != 'none'){
+					this.hiding = true;
+					this.showing = false;
+					this.hidden = true;
+					this.cssText = this.element.style.cssText;
+					var startStyles = this.element.getComputedSize({
+						styles: this.options.styles,
+						mode: this.options.mode
+					});
+					this.element.setStyle('display', this.options.display);
+					if (this.options.transitionOpacity) startStyles.opacity = 1;
+					var zero = {};
+					$each(startStyles, function(style, name){
+						zero[name] = [style, 0];
+					}, this);
+					this.element.setStyle('overflow', 'hidden');
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					this.$chain.unshift(function(){
+						if (this.hidden){
+							this.hiding = false;
+							$each(startStyles, function(style, name){
+								startStyles[name] = style;
+							}, this);
+							this.element.style.cssText = this.cssText;
+							this.element.setStyle('display', 'none');
+							if (hideThese) hideThese.setStyle('visibility', 'visible');
+						}
+						this.fireEvent('hide', this.element);
+						this.callChain();
+					}.bind(this));
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					this.start(zero);
+				} else {
+					this.callChain.delay(10, this);
+					this.fireEvent('complete', this.element);
+					this.fireEvent('hide', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.dissolve.bind(this));
+			} else if (this.options.link == 'cancel' && !this.hiding){
+				this.cancel();
+				this.dissolve();
+			}
+		} catch(e){
+			this.hiding = false;
+			this.element.setStyle('display', 'none');
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('hide', this.element);
+		}
+		return this;
+	},
+
+	reveal: function(){
+		try {
+			if (!this.showing && !this.hiding){
+				if (this.element.getStyle('display') == 'none' ||
+					 this.element.getStyle('visiblity') == 'hidden' ||
+					 this.element.getStyle('opacity') == 0){
+					this.showing = true;
+					this.hiding = this.hidden =  false;
+					var startStyles;
+					this.cssText = this.element.style.cssText;
+					//toggle display, but hide it
+					this.element.measure(function(){
+						//create the styles for the opened/visible state
+						startStyles = this.element.getComputedSize({
+							styles: this.options.styles,
+							mode: this.options.mode
+						});
+					}.bind(this));
+					$each(startStyles, function(style, name){
+						startStyles[name] = style;
+					});
+					//if we're overridding height/width
+					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
+					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
+					if (this.options.transitionOpacity) {
+						this.element.setStyle('opacity', 0);
+						startStyles.opacity = 1;
+					}
+					//create the zero state for the beginning of the transition
+					var zero = {
+						height: 0,
+						display: this.options.display
+					};
+					$each(startStyles, function(style, name){ zero[name] = 0; });
+					//set to zero
+					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
+					//hide inputs
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					//start the effect
+					this.start(startStyles);
+					this.$chain.unshift(function(){
+						this.element.style.cssText = this.cssText;
+						this.element.setStyle('display', this.options.display);
+						if (!this.hidden) this.showing = false;
+						if (hideThese) hideThese.setStyle('visibility', 'visible');
+						this.callChain();
+						this.fireEvent('show', this.element);
+					}.bind(this));
+				} else {
+					this.callChain();
+					this.fireEvent('complete', this.element);
+					this.fireEvent('show', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.reveal.bind(this));
+			} else if (this.options.link == 'cancel' && !this.showing){
+				this.cancel();
+				this.reveal();
+			}
+		} catch(e){
+			this.element.setStyles({
+				display: this.options.display,
+				visiblity: 'visible',
+				opacity: 1
+			});
+			this.showing = false;
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('show', this.element);
+		}
+		return this;
+	},
+
+	toggle: function(){
+		if (this.element.getStyle('display') == 'none' ||
+			 this.element.getStyle('visiblity') == 'hidden' ||
+			 this.element.getStyle('opacity') == 0){
+			this.reveal();
+		} else {
+			this.dissolve();
+		}
+		return this;
+	},
+
+	cancel: function(){
+		this.parent.apply(this, arguments);
+		this.element.style.cssText = this.cssText;
+		this.hidding = false;
+		this.showing = false;
+	}
+
+});
+
+Element.Properties.reveal = {
+
+	set: function(options){
+		var reveal = this.retrieve('reveal');
+		if (reveal) reveal.cancel();
+		return this.eliminate('reveal').store('reveal:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('reveal')){
+			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
+			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
+		}
+		return this.retrieve('reveal');
+	}
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+	reveal: function(options){
+		this.get('reveal', options).reveal();
+		return this;
+	},
+
+	dissolve: function(options){
+		this.get('reveal', options).dissolve();
+		return this;
+	},
+
+	nix: function(){
+		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
+		this.get('reveal', params.options).dissolve().chain(function(){
+			this[params.destroy ? 'destroy' : 'dispose']();
+		}.bind(this));
+		return this;
+	},
+
+	wink: function(){
+		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
+		var reveal = this.get('reveal', params.options);
+		reveal.reveal().chain(function(){
+			(function(){
+				reveal.dissolve();
+			}).delay(params.duration || 2000);
+		});
+	}
+
+
+});/*
+---
+
+script: Form.Request.Append.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Request
+- /Fx.Reveal
+- /Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+	Extends: Form.Request,
+
+	options: {
+		//onBeforeEffect: $empty,
+		useReveal: true,
+		revealOptions: {},
+		inject: 'bottom'
+	},
+
+	makeRequest: function(){
+		this.request = new Request.HTML($merge({
+				url: this.element.get('action'),
+				method: this.element.get('method') || 'post',
+				spinnerTarget: this.element
+			}, this.options.requestOptions, {
+				evalScripts: false
+			})
+		).addEvents({
+			success: function(tree, elements, html, javascript){
+				var container;
+				var kids = Elements.from(html);
+				if (kids.length == 1) {
+					container = kids[0];
+				} else {
+					 container = new Element('div', {
+						styles: {
+							display: 'none'
+						}
+					}).adopt(kids);
+				}
+				container.inject(this.update, this.options.inject);
+				if (this.options.requestOptions.evalScripts) $exec(javascript);
+				this.fireEvent('beforeEffect', container);
+				var finish = function(){
+					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
+				}.bind(this);
+				if (this.options.useReveal) {
+					container.get('reveal', this.options.revealOptions).chain(finish);
+					container.reveal();
+				} else {
+					finish();
+				}
+			}.bind(this),
+			failure: function(xhr){
+				this.fireEvent('failure', xhr);
+			}.bind(this)
+		});
+	}
+
+});/*
+---
+
+script: Form.Validator.English.js
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.English]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Form.Validator', {
+
+	required:'This field is required.',
+	minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
+	maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
+	integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+	numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+	digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+	alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
+	alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
+	dateSuchAs:'Please enter a valid date such as {date}',
+	dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+	email:'Please enter a valid email address. For example "fred at domain.com".',
+	url:'Please enter a valid URL such as http://www.google.com.',
+	currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
+	oneRequired:'Please enter something for at least one of these inputs.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Warning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'There can be no spaces in this input.',
+	reqChkByNode: 'No items are selected.',
+	requiredChk: 'This field is required.',
+	reqChkByName: 'Please select a {label}.',
+	match: 'This field needs to match the {matchName} field',
+	startDate: 'the start date',
+	endDate: 'the end date',
+	currendDate: 'the current date',
+	afterDate: 'The date should be the same or after {label}.',
+	beforeDate: 'The date should be the same or before {label}.',
+	startMonth: 'Please select a start month',
+	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+/*
+---
+
+script: Form.Validator.js
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Selectors
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/JSON
+- /Lang- /Class.Binds
+- /Date Element.Forms
+- /Form.Validator.English
+- /Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = new Class({
+
+	Implements: [Options],
+
+	options: {
+		errorMsg: 'Validation failed.',
+		test: function(field){return true;}
+	},
+
+	initialize: function(className, options){
+		this.setOptions(options);
+		this.className = className;
+	},
+
+	test: function(field, props){
+		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
+		else return false;
+	},
+
+	getError: function(field, props){
+		var err = this.options.errorMsg;
+		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
+		return err;
+	},
+
+	getProps: function(field){
+		if (!document.id(field)) return {};
+		return field.get('validatorProps');
+	}
+
+});
+
+Element.Properties.validatorProps = {
+
+	set: function(props){
+		return this.eliminate('validatorProps').store('validatorProps', props);
+	},
+
+	get: function(props){
+		if (props) this.set(props);
+		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
+		if (this.getProperty('validatorProps')){
+			try {
+				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
+			}catch(e){
+				return {};
+			}
+		} else {
+			var vals = this.get('class').split(' ').filter(function(cls){
+				return cls.test(':');
+			});
+			if (!vals.length){
+				this.store('validatorProps', {});
+			} else {
+				props = {};
+				vals.each(function(cls){
+					var split = cls.split(':');
+					if (split[1]) {
+						try {
+							props[split[0]] = JSON.decode(split[1]);
+						} catch(e) {}
+					}
+				});
+				this.store('validatorProps', props);
+			}
+		}
+		return this.retrieve('validatorProps');
+	}
+
+};
+
+Form.Validator = new Class({
+
+	Implements:[Options, Events],
+
+	Binds: ['onSubmit'],
+
+	options: {/*
+		onFormValidate: $empty(isValid, form, event),
+		onElementValidate: $empty(isValid, field, className, warn),
+		onElementPass: $empty(field),
+		onElementFail: $empty(field, validatorsFailed) */
+		fieldSelectors: 'input, select, textarea',
+		ignoreHidden: true,
+		ignoreDisabled: true,
+		useTitles: false,
+		evaluateOnSubmit: true,
+		evaluateFieldsOnBlur: true,
+		evaluateFieldsOnChange: true,
+		serial: true,
+		stopOnFailure: true,
+		warningPrefix: function(){
+			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+		},
+		errorPrefix: function(){
+			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+		}
+	},
+
+	initialize: function(form, options){
+		this.setOptions(options);
+		this.element = document.id(form);
+		this.element.store('validator', this);
+		this.warningPrefix = $lambda(this.options.warningPrefix)();
+		this.errorPrefix = $lambda(this.options.errorPrefix)();
+		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
+		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	getFields: function(){
+		return (this.fields = this.element.getElements(this.options.fieldSelectors));
+	},
+
+	watchFields: function(fields){
+		fields.each(function(el){
+			if (this.options.evaluateFieldsOnBlur)
+				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
+			if (this.options.evaluateFieldsOnChange)
+				el.addEvent('change', this.validationMonitor.pass([el, true], this));
+		}, this);
+	},
+
+	validationMonitor: function(){
+		$clear(this.timer);
+		this.timer = this.validateField.delay(50, this, arguments);
+	},
+
+	onSubmit: function(event){
+		if (!this.validate(event) && event) event.preventDefault();
+		else this.reset();
+	},
+
+	reset: function(){
+		this.getFields().each(this.resetField, this);
+		return this;
+	},
+
+	validate: function(event){
+		var result = this.getFields().map(function(field){
+			return this.validateField(field, true);
+		}, this).every(function(v){ return v;});
+		this.fireEvent('formValidate', [result, this.element, event]);
+		if (this.options.stopOnFailure && !result && event) event.preventDefault();
+		return result;
+	},
+
+	validateField: function(field, force){
+		if (this.paused) return true;
+		field = document.id(field);
+		var passed = !field.hasClass('validation-failed');
+		var failed, warned;
+		if (this.options.serial && !force){
+			failed = this.element.getElement('.validation-failed');
+			warned = this.element.getElement('.warning');
+		}
+		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+			var validators = field.className.split(' ').some(function(cn){
+				return this.getValidator(cn);
+			}, this);
+			var validatorsFailed = [];
+			field.className.split(' ').each(function(className){
+				if (className && !this.test(className, field)) validatorsFailed.include(className);
+			}, this);
+			passed = validatorsFailed.length === 0;
+			if (validators && !field.hasClass('warnOnly')){
+				if (passed){
+					field.addClass('validation-passed').removeClass('validation-failed');
+					this.fireEvent('elementPass', field);
+				} else {
+					field.addClass('validation-failed').removeClass('validation-passed');
+					this.fireEvent('elementFail', [field, validatorsFailed]);
+				}
+			}
+			if (!warned){
+				var warnings = field.className.split(' ').some(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.getValidator(cn.replace(/^warn-/,''));
+					else return null;
+				}, this);
+				field.removeClass('warning');
+				var warnResult = field.className.split(' ').map(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.test(cn.replace(/^warn-/,''), field, true);
+					else return null;
+				}, this);
+			}
+		}
+		return passed;
+	},
+
+	test: function(className, field, warn){
+		field = document.id(field);
+		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+		var validator = this.getValidator(className);
+		if (field.hasClass('ignoreValidation')) return true;
+		warn = $pick(warn, false);
+		if (field.hasClass('warnOnly')) warn = true;
+		var isValid = validator ? validator.test(field) : true;
+		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+		if (warn) return true;
+		return isValid;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (field){
+			field.className.split(' ').each(function(className){
+				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+				field.removeClass('validation-failed');
+				field.removeClass('warning');
+				field.removeClass('validation-passed');
+			}, this);
+		}
+		return this;
+	},
+
+	stop: function(){
+		this.paused = true;
+		return this;
+	},
+
+	start: function(){
+		this.paused = false;
+		return this;
+	},
+
+	ignoreField: function(field, warn){
+		field = document.id(field);
+		if (field){
+			this.enforceField(field);
+			if (warn) field.addClass('warnOnly');
+			else field.addClass('ignoreValidation');
+		}
+		return this;
+	},
+
+	enforceField: function(field){
+		field = document.id(field);
+		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+		return this;
+	}
+
+});
+
+Form.Validator.getMsg = function(key){
+	return MooTools.lang.get('Form.Validator', key);
+};
+
+Form.Validator.adders = {
+
+	validators:{},
+
+	add : function(className, options){
+		this.validators[className] = new InputValidator(className, options);
+		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+		//extend these validators into it
+		//this allows validators to be global and/or per instance
+		if (!this.initialize){
+			this.implement({
+				validators: this.validators
+			});
+		}
+	},
+
+	addAllThese : function(validators){
+		$A(validators).each(function(validator){
+			this.add(validator[0], validator[1]);
+		}, this);
+	},
+
+	getValidator: function(className){
+		return this.validators[className.split(':')[0]];
+	}
+
+};
+
+$extend(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+	errorMsg: false,
+	test: function(element){
+		if (element.type == 'select-one' || element.type == 'select')
+			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+		else
+			return ((element.get('value') == null) || (element.get('value').length == 0));
+	}
+
+});
+
+Form.Validator.addAllThese([
+
+	['required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element){
+			return !Form.Validator.getValidator('IsEmpty').test(element);
+		}
+	}],
+
+	['minLength', {
+		errorMsg: function(element, props){
+			if ($type(props.minLength))
+				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
+			else return true;
+		}
+	}],
+
+	['maxLength', {
+		errorMsg: function(element, props){
+			//props is {maxLength:10}
+			if ($type(props.maxLength))
+				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			//if the value is <= than the maxLength value, element passes test
+			return (element.get('value').length <= $pick(props.maxLength, 10000));
+		}
+	}],
+
+	['validate-integer', {
+		errorMsg: Form.Validator.getMsg.pass('integer'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-numeric', {
+		errorMsg: Form.Validator.getMsg.pass('numeric'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||
+				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-digits', {
+		errorMsg: Form.Validator.getMsg.pass('digits'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+		}
+	}],
+
+	['validate-alpha', {
+		errorMsg: Form.Validator.getMsg.pass('alpha'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-alphanum', {
+		errorMsg: Form.Validator.getMsg.pass('alphanum'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+		}
+	}],
+
+	['validate-date', {
+		errorMsg: function(element, props){
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+			} else {
+				return Form.Validator.getMsg('dateInFormatMDY');
+			}
+		},
+		test: function(element, props){
+			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+			var d;
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				d = Date.parse(element.get('value'));
+				var formatted = d.format(format);
+				if (formatted != 'invalid date') element.set('value', formatted);
+				return !isNaN(d);
+			} else {
+				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
+				if (!regex.test(element.get('value'))) return false;
+				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
+				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
+					(parseInt(RegExp.$2, 10) == d.getDate()) &&
+					(parseInt(RegExp.$3, 10) == d.getFullYear());
+			}
+		}
+	}],
+
+	['validate-email', {
+		errorMsg: Form.Validator.getMsg.pass('email'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-url', {
+		errorMsg: Form.Validator.getMsg.pass('url'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-currency-dollar', {
+		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+		test: function(element){
+			// [$]1[##][,###]+[.##]
+			// [$]1###+[.##]
+			// [$]0.##
+			// [$].##
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-one-required', {
+		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+		test: function(element, props){
+			var p = document.id(props['validate-one-required']) || element.getParent();
+			return p.getElements('input').some(function(el){
+				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+				return el.get('value');
+			});
+		}
+	}]
+
+]);
+
+Element.Properties.validator = {
+
+	set: function(options){
+		var validator = this.retrieve('validator');
+		if (validator) validator.setOptions(options);
+		return this.store('validator:options');
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('validator')){
+			if (options || !this.retrieve('validator:options')) this.set('validator', options);
+			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
+		}
+		return this.retrieve('validator');
+	}
+
+};
+
+Element.implement({
+
+	validate: function(options){
+		this.set('validator', options);
+		return this.get('validator', options).validate();
+	}
+
+});
+//legacy
+var FormValidator = Form.Validator;/*
+---
+
+script: Form.Validator.Inline.js
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+	Extends: Form.Validator,
+
+	options: {
+		scrollToErrorsOnSubmit: true,
+		scrollFxOptions: {
+			transition: 'quad:out',
+			offset: {
+				y: -20
+			}
+		}
+	},
+
+	initialize: function(form, options){
+		this.parent(form, options);
+		this.addEvent('onElementValidate', function(isValid, field, className, warn){
+			var validator = this.getValidator(className);
+			if (!isValid && validator.getError(field)){
+				if (warn) field.addClass('warning');
+				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+				this.insertAdvice(advice, field);
+				this.showAdvice(className, field);
+			} else {
+				this.hideAdvice(className, field);
+			}
+		});
+	},
+
+	makeAdvice: function(className, field, error, warn){
+		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
+			errorMsg += (this.options.useTitles) ? field.title || error:error;
+		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+		var advice = this.getAdvice(className, field);
+		if(advice) {
+			advice = advice.set('html', errorMsg);
+		} else {
+			advice = new Element('div', {
+				html: errorMsg,
+				styles: { display: 'none' },
+				id: 'advice-' + className + '-' + this.getFieldId(field)
+			}).addClass(cssClass);
+		}
+		field.store('advice-' + className, advice);
+		return advice;
+	},
+
+	getFieldId : function(field){
+		return field.id ? field.id : field.id = 'input_' + field.name;
+	},
+
+	showAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && !field.retrieve(this.getPropName(className))
+				&& (advice.getStyle('display') == 'none'
+				|| advice.getStyle('visiblity') == 'hidden'
+				|| advice.getStyle('opacity') == 0)){
+			field.store(this.getPropName(className), true);
+			if (advice.reveal) advice.reveal();
+			else advice.setStyle('display', 'block');
+		}
+	},
+
+	hideAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && field.retrieve(this.getPropName(className))){
+			field.store(this.getPropName(className), false);
+			//if Fx.Reveal.js is present, transition the advice out
+			if (advice.dissolve) advice.dissolve();
+			else advice.setStyle('display', 'none');
+		}
+	},
+
+	getPropName: function(className){
+		return 'advice' + className;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (!field) return this;
+		this.parent(field);
+		field.className.split(' ').each(function(className){
+			this.hideAdvice(className, field);
+		}, this);
+		return this;
+	},
+
+	getAllAdviceMessages: function(field, force){
+		var advice = [];
+		if (field.hasClass('ignoreValidation') && !force) return advice;
+		var validators = field.className.split(' ').some(function(cn){
+			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+			if (warner) cn = cn.replace(/^warn-/, '');
+			var validator = this.getValidator(cn);
+			if (!validator) return;
+			advice.push({
+				message: validator.getError(field),
+				warnOnly: warner,
+				passed: validator.test(),
+				validator: validator
+			});
+		}, this);
+		return advice;
+	},
+
+	getAdvice: function(className, field){
+		return field.retrieve('advice-' + className);
+	},
+
+	insertAdvice: function(advice, field){
+		//Check for error position prop
+		var props = field.get('validatorProps');
+		//Build advice
+		if (!props.msgPos || !document.id(props.msgPos)){
+			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+			else advice.inject(document.id(field), 'after');
+		} else {
+			document.id(props.msgPos).grab(advice);
+		}
+	},
+
+	validateField: function(field, force){
+		var result = this.parent(field, force);
+		if (this.options.scrollToErrorsOnSubmit && !result){
+			var failed = document.id(this).getElement('.validation-failed');
+			var par = document.id(this).getParent();
+			while (par != document.body && par.getScrollSize().y == par.getSize().y){
+				par = par.getParent();
+			}
+			var fx = par.retrieve('fvScroller');
+			if (!fx && window.Fx && Fx.Scroll){
+				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+				par.store('fvScroller', fx);
+			}
+			if (failed){
+				if (fx) fx.toElement(failed);
+				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+			}
+		}
+		return result;
+	}
+
+});
+/*
+---
+
+script: Form.Validator.Extras.js
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+	['validate-enforce-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-ignore-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-nospace', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('noSpace');
+		},
+		test: function(element, props){
+			return !element.get('value').test(/\s/);
+		}
+	}],
+
+	['validate-toggle-oncheck', {
+		test: function(element, props){
+			var fv = element.getParent('form').retrieve('validator');
+			if (!fv) return true;
+			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+			if (!element.checked){
+				eleArr.each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			} else {
+				eleArr.each(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-reqchk-bynode', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('reqChkByNode');
+		},
+		test: function(element, props){
+			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+				return item.checked;
+			});
+		}
+	}],
+
+	['validate-required-check', {
+		errorMsg: function(element, props){
+			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+		},
+		test: function(element, props){
+			return !!element.checked;
+		}
+	}],
+
+	['validate-reqchk-byname', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+		},
+		test: function(element, props){
+			var grpName = props.groupName || element.get('name');
+			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+				return item.checked;
+			});
+			var fv = element.getParent('form').retrieve('validator');
+			if (oneCheckedItem && fv) fv.resetField(element);
+			return oneCheckedItem;
+		}
+	}],
+
+	['validate-match', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+		},
+		test: function(element, props){
+			var eleVal = element.get('value');
+			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+			return eleVal && matchVal ? eleVal == matchVal : true;
+		}
+	}],
+
+	['validate-after-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('afterDate').substitute({
+				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+			var end = Date.parse(element.get('value'));
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-before-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('beforeDate').substitute({
+				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = Date.parse(element.get('value'));
+			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-custom-required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element, props){
+			return element.get('value') != props.emptyValue;
+		}
+	}],
+
+	['validate-same-month', {
+		errorMsg: function(element, props){
+			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+			var eleVal = element.get('value');
+			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+		},
+		test: function(element, props){
+			var d1 = Date.parse(element.get('value'));
+			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+		}
+	}],
+
+
+	['validate-cc-num', {
+		errorMsg: function(element){
+			var ccNum = element.get('value').replace(/[^0-9]/g, '');
+			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+		},
+		test: function(element){
+			// required is a different test
+			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }
+
+			// Clean number value
+			var ccNum = element.get('value');
+			ccNum = ccNum.replace(/[^0-9]/g, '');
+
+			var valid_type = false;
+
+			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
+
+			if (valid_type) {
+				var sum = 0;
+				var cur = 0;
+
+				for(var i=ccNum.length-1; i>=0; --i) {
+					cur = ccNum.charAt(i).toInt();
+					if (cur == 0) { continue; }
+
+					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
+					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }
+
+					sum += cur;
+				}
+				if ((sum % 10) == 0) { return true; }
+			}
+
+			var chunks = '';
+			while (ccNum != '') {
+				chunks += ' ' + ccNum.substr(0,4);
+				ccNum = ccNum.substr(4);
+			}
+
+			element.getParent('form').retrieve('validator').ignoreField(element);
+			element.set('value', chunks.clean());
+			element.getParent('form').retrieve('validator').enforceField(element);
+			return false;
+		}
+	}]
+
+
+]);/*
+---
+
+script: OverText.js
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- /Class.Binds
+- /Class.Occlude
+- /Element.Position
+- /Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+	options: {/*
+		textOverride: null,
+		onFocus: $empty()
+		onTextHide: $empty(textEl, inputEl),
+		onTextShow: $empty(textEl, inputEl), */
+		element: 'label',
+		positionOptions: {
+			position: 'upperLeft',
+			edge: 'upperLeft',
+			offset: {
+				x: 4,
+				y: 2
+			}
+		},
+		poll: false,
+		pollInterval: 250,
+		wrap: false
+	},
+
+	property: 'OverText',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.attach(this.element);
+		OverText.instances.push(this);
+		if (this.options.poll) this.poll();
+		return this;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	attach: function(){
+		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
+		if (!val) return;
+		this.text = new Element(this.options.element, {
+			'class': 'overTxtLabel',
+			styles: {
+				lineHeight: 'normal',
+				position: 'absolute',
+				cursor: 'text'
+			},
+			html: val,
+			events: {
+				click: this.hide.pass(this.options.element == 'label', this)
+			}
+		}).inject(this.element, 'after');
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+
+		if (this.options.wrap) {
+			this.textHolder = new Element('div', {
+				styles: {
+					lineHeight: 'normal',
+					position: 'relative'
+				},
+				'class':'overTxtWrapper'
+			}).adopt(this.text).inject(this.element, 'before');
+		}
+
+		this.element.addEvents({
+			focus: this.focus,
+			blur: this.assert,
+			change: this.assert
+		}).store('OverTextDiv', this.text);
+		window.addEvent('resize', this.reposition.bind(this));
+		this.assert(true);
+		this.reposition();
+	},
+
+	wrap: function(){
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+	},
+
+	startPolling: function(){
+		this.pollingPaused = false;
+		return this.poll();
+	},
+
+	poll: function(stop){
+		//start immediately
+		//pause on focus
+		//resumeon blur
+		if (this.poller && !stop) return this;
+		var test = function(){
+			if (!this.pollingPaused) this.assert(true);
+		}.bind(this);
+		if (stop) $clear(this.poller);
+		else this.poller = test.periodical(this.options.pollInterval, this);
+		return this;
+	},
+
+	stopPolling: function(){
+		this.pollingPaused = true;
+		return this.poll(true);
+	},
+
+	focus: function(){
+		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
+		this.hide();
+	},
+
+	hide: function(suppressFocus, force){
+		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+			this.text.hide();
+			this.fireEvent('textHide', [this.text, this.element]);
+			this.pollingPaused = true;
+			if (!suppressFocus){
+				try {
+					this.element.fireEvent('focus');
+					this.element.focus();
+				} catch(e){} //IE barfs if you call focus on hidden elements
+			}
+		}
+		return this;
+	},
+
+	show: function(){
+		if (this.text && !this.text.isDisplayed()){
+			this.text.show();
+			this.reposition();
+			this.fireEvent('textShow', [this.text, this.element]);
+			this.pollingPaused = false;
+		}
+		return this;
+	},
+
+	assert: function(suppressFocus){
+		this[this.test() ? 'show' : 'hide'](suppressFocus);
+	},
+
+	test: function(){
+		var v = this.element.get('value');
+		return !v;
+	},
+
+	reposition: function(){
+		this.assert(true);
+		if (!this.element.isVisible()) return this.stopPolling().hide();
+		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
+		return this;
+	}
+
+});
+
+OverText.instances = [];
+
+$extend(OverText, {
+
+	each: function(fn) {
+		return OverText.instances.map(function(ot, i){
+			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
+			return null; //the input or the text was destroyed
+		});
+	},
+	
+	update: function(){
+
+		return OverText.each(function(ot){
+			return ot.reposition();
+		});
+
+	},
+
+	hideAll: function(){
+
+		return OverText.each(function(ot){
+			return ot.hide(true, true);
+		});
+
+	},
+
+	showAll: function(){
+		return OverText.each(function(ot) {
+			return ot.show();
+		});
+	}
+
+});
+
+if (window.Fx && Fx.Reveal) {
+	Fx.Reveal.implement({
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
+	});
+}/*
+---
+
+script: Fx.Elements.js
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx.CSS
+- /MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(elements, options){
+		this.elements = this.subject = $$(elements);
+		this.parent(options);
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var i in from){
+			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+		}
+		return now;
+	},
+
+	set: function(now){
+		for (var i in now){
+			var iNow = now[i];
+			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+		}
+		return this;
+	},
+
+	start: function(obj){
+		if (!this.check(obj)) return this;
+		var from = {}, to = {};
+		for (var i in obj){
+			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+			for (var p in iProps){
+				var parsed = this.prepare(this.elements[i], p, iProps[p]);
+				iFrom[p] = parsed.from;
+				iTo[p] = parsed.to;
+			}
+		}
+		return this.parent(from, to);
+	}
+
+});/*
+---
+
+script: Fx.Accordion.js
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Event
+- /Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {/*
+		onActive: $empty(toggler, section),
+		onBackground: $empty(toggler, section),
+		fixedHeight: false,
+		fixedWidth: false,
+		*/
+		display: 0,
+		show: false,
+		height: true,
+		width: false,
+		opacity: true,
+		alwaysHide: false,
+		trigger: 'click',
+		initialDisplayFx: true,
+		returnHeightToAuto: true
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {
+			'container': Element.type, //deprecated
+			'options': Object.type,
+			'togglers': $defined,
+			'elements': $defined
+		});
+		this.parent(params.elements, params.options);
+		this.togglers = $$(params.togglers);
+		this.previous = -1;
+		this.internalChain = new Chain();
+		if (this.options.alwaysHide) this.options.wait = true;
+		if ($chk(this.options.show)){
+			this.options.display = false;
+			this.previous = this.options.show;
+		}
+		if (this.options.start){
+			this.options.display = false;
+			this.options.show = false;
+		}
+		this.effects = {};
+		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
+		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
+		this.elements.each(function(el, i){
+			if (this.options.show === i){
+				this.fireEvent('active', [this.togglers[i], el]);
+			} else {
+				for (var fx in this.effects) el.setStyle(fx, 0);
+			}
+		}, this);
+		if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
+		if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
+		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+	},
+
+	addSection: function(toggler, element){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		this.togglers.include(toggler);
+		this.elements.include(element);
+		var idx = this.togglers.indexOf(toggler);
+		var displayer = this.display.bind(this, idx);
+		toggler.store('accordion:display', displayer);
+		toggler.addEvent(this.options.trigger, displayer);
+		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+		element.fullOpacity = 1;
+		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
+		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
+		element.setStyle('overflow', 'hidden');
+		if (!test){
+			for (var fx in this.effects) element.setStyle(fx, 0);
+		}
+		return this;
+	},
+
+	detach: function(){
+		this.togglers.each(function(toggler) {
+			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+		}, this);
+	},
+
+	display: function(index, useFx){
+		if (!this.check(index, useFx)) return this;
+		useFx = $pick(useFx, true);
+		if (this.options.returnHeightToAuto){
+			var prev = this.elements[this.previous];
+			if (prev && !this.selfHidden){
+				for (var fx in this.effects){
+					prev.setStyle(fx, prev[this.effects[fx]]);
+				}
+			}
+		}
+		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
+		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
+		this.previous = index;
+		var obj = {};
+		this.elements.each(function(el, i){
+			obj[i] = {};
+			var hide;
+			if (i != index){
+				hide = true;
+			} else if (this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0 && this.options.width)){
+				hide = true;
+				this.selfHidden = true;
+			}
+			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
+		}, this);
+		this.internalChain.chain(function(){
+			if (this.options.returnHeightToAuto && !this.selfHidden){
+				var el = this.elements[index];
+				if (el) el.setStyle('height', 'auto');
+			};
+		}.bind(this));
+		return useFx ? this.start(obj) : this.set(obj);
+	}
+
+});
+
+/*
+	Compatibility with 1.2.0
+*/
+var Accordion = new Class({
+
+	Extends: Fx.Accordion,
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		var params = Array.link(arguments, {'container': Element.type});
+		this.container = params.container;
+	},
+
+	addSection: function(toggler, element, pos){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		var len = this.togglers.length;
+		if (len && (!test || pos)){
+			pos = $pick(pos, len - 1);
+			toggler.inject(this.togglers[pos], 'before');
+			element.inject(toggler, 'after');
+		} else if (this.container && !test){
+			toggler.inject(this.container);
+			element.inject(this.container);
+		}
+		return this.parent.apply(this, arguments);
+	}
+
+});/*
+---
+
+script: Fx.Move.js
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Morph
+- /Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {
+		relativeTo: document.body,
+		position: 'center',
+		edge: false,
+		offset: {x: 0, y: 0}
+	},
+
+	start: function(destination){
+		return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
+	}
+
+});
+
+Element.Properties.move = {
+
+	set: function(options){
+		var morph = this.retrieve('move');
+		if (morph) morph.cancel();
+		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('move')){
+			if (options || !this.retrieve('move:options')) this.set('move', options);
+			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
+		}
+		return this.retrieve('move');
+	}
+
+};
+
+Element.implement({
+
+	move: function(options){
+		this.get('move').start(options);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Scroll.js
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+Fx.Scroll = new Class({
+
+	Extends: Fx,
+
+	options: {
+		offset: {x: 0, y: 0},
+		wheelStops: true
+	},
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var cancel = this.cancel.bind(this, false);
+
+		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+		var stopper = this.element;
+
+		if (this.options.wheelStops){
+			this.addEvent('start', function(){
+				stopper.addEvent('mousewheel', cancel);
+			}, true);
+			this.addEvent('complete', function(){
+				stopper.removeEvent('mousewheel', cancel);
+			}, true);
+		}
+	},
+
+	set: function(){
+		var now = Array.flatten(arguments);
+		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
+		this.element.scrollTo(now[0], now[1]);
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(x, y){
+		if (!this.check(x, y)) return this;
+		var scrollSize = this.element.getScrollSize(),
+			scroll = this.element.getScroll(), 
+			values = {x: x, y: y};
+		for (var z in values){
+			var max = scrollSize[z];
+			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
+			else values[z] = scroll[z];
+			values[z] += this.options.offset[z];
+		}
+		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
+	},
+
+	toTop: function(){
+		return this.start(false, 0);
+	},
+
+	toLeft: function(){
+		return this.start(0, false);
+	},
+
+	toRight: function(){
+		return this.start('right', false);
+	},
+
+	toBottom: function(){
+		return this.start(false, 'bottom');
+	},
+
+	toElement: function(el){
+		var position = document.id(el).getPosition(this.element);
+		return this.start(position.x, position.y);
+	},
+
+	scrollIntoView: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x','y'];
+		var to = {};
+		el = document.id(el);
+		var pos = el.getPosition(this.element);
+		var size = el.getSize();
+		var scroll = this.element.getScroll();
+		var containerSize = this.element.getSize();
+		var edge = {
+			x: pos.x + size.x,
+			y: pos.y + size.y
+		};
+		['x','y'].each(function(axis) {
+			if (axes.contains(axis)) {
+				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
+			}
+			if (to[axis] == null) to[axis] = scroll[axis];
+			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	},
+
+	scrollToCenter: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x', 'y'];
+		el = $(el);
+		var to = {},
+			pos = el.getPosition(this.element),
+			size = el.getSize(),
+			scroll = this.element.getScroll(),
+			containerSize = this.element.getSize(),
+			edge = {
+				x: pos.x + size.x,
+				y: pos.y + size.y
+			};
+
+		['x','y'].each(function(axis){
+			if(axes.contains(axis)){
+				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
+			}
+			if(to[axis] == null) to[axis] = scroll[axis];
+			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Slide.js
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx Element.Style
+- /MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+	Extends: Fx,
+
+	options: {
+		mode: 'vertical',
+		wrapper: false,
+		hideOverflow: true
+	},
+
+	initialize: function(element, options){
+		this.addEvent('complete', function(){
+			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
+			if (this.open) this.wrapper.setStyle('height', '');
+			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
+		}, true);
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var wrapper = this.element.retrieve('wrapper');
+		var styles = this.element.getStyles('margin', 'position', 'overflow');
+		if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
+		if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
+		this.wrapper = wrapper || new Element('div', {
+			styles: styles
+		}).wraps(this.element);
+		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
+		this.now = [];
+		this.open = true;
+	},
+
+	vertical: function(){
+		this.margin = 'margin-top';
+		this.layout = 'height';
+		this.offset = this.element.offsetHeight;
+	},
+
+	horizontal: function(){
+		this.margin = 'margin-left';
+		this.layout = 'width';
+		this.offset = this.element.offsetWidth;
+	},
+
+	set: function(now){
+		this.element.setStyle(this.margin, now[0]);
+		this.wrapper.setStyle(this.layout, now[1]);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(how, mode){
+		if (!this.check(how, mode)) return this;
+		this[mode || this.options.mode]();
+		var margin = this.element.getStyle(this.margin).toInt();
+		var layout = this.wrapper.getStyle(this.layout).toInt();
+		var caseIn = [[margin, layout], [0, this.offset]];
+		var caseOut = [[margin, layout], [-this.offset, 0]];
+		var start;
+		switch (how){
+			case 'in': start = caseIn; break;
+			case 'out': start = caseOut; break;
+			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+		}
+		return this.parent(start[0], start[1]);
+	},
+
+	slideIn: function(mode){
+		return this.start('in', mode);
+	},
+
+	slideOut: function(mode){
+		return this.start('out', mode);
+	},
+
+	hide: function(mode){
+		this[mode || this.options.mode]();
+		this.open = false;
+		return this.set([-this.offset, 0]);
+	},
+
+	show: function(mode){
+		this[mode || this.options.mode]();
+		this.open = true;
+		return this.set([0, this.offset]);
+	},
+
+	toggle: function(mode){
+		return this.start('toggle', mode);
+	}
+
+});
+
+Element.Properties.slide = {
+
+	set: function(options){
+		var slide = this.retrieve('slide');
+		if (slide) slide.cancel();
+		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('slide')){
+			if (options || !this.retrieve('slide:options')) this.set('slide', options);
+			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
+		}
+		return this.retrieve('slide');
+	}
+
+};
+
+Element.implement({
+
+	slide: function(how, mode){
+		how = how || 'toggle';
+		var slide = this.get('slide'), toggle;
+		switch (how){
+			case 'hide': slide.hide(mode); break;
+			case 'show': slide.show(mode); break;
+			case 'toggle':
+				var flag = this.retrieve('slide:flag', slide.open);
+				slide[flag ? 'slideOut' : 'slideIn'](mode);
+				this.store('slide:flag', !flag);
+				toggle = true;
+			break;
+			default: slide.start(how, mode);
+		}
+		if (!toggle) this.eliminate('slide:flag');
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Selectors
+- /Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+var SmoothScroll = Fx.SmoothScroll = new Class({
+
+	Extends: Fx.Scroll,
+
+	initialize: function(options, context){
+		context = context || document;
+		this.doc = context.getDocument();
+		var win = context.getWindow();
+		this.parent(this.doc, options);
+		this.links = $$(this.options.links || this.doc.links);
+		var location = win.location.href.match(/^[^#]*/)[0] + '#';
+		this.links.each(function(link){
+			if (link.href.indexOf(location) != 0) {return;}
+			var anchor = link.href.substr(location.length);
+			if (anchor) this.useLink(link, anchor);
+		}, this);
+		if (!Browser.Engine.webkit419) {
+			this.addEvent('complete', function(){
+				win.location.hash = this.anchor;
+			}, true);
+		}
+	},
+
+	useLink: function(link, anchor){
+		var el;
+		link.addEvent('click', function(event){
+			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+			if (el) {
+				event.preventDefault();
+				this.anchor = anchor;
+				this.toElement(el).chain(function(){
+					this.fireEvent('scrolledTo', [link, el]);
+				}.bind(this));
+				link.blur();
+			}
+		}.bind(this));
+	}
+});/*
+---
+
+script: Fx.Sort.js
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Fx.Elements
+- /Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {
+		mode: 'vertical'
+	},
+
+	initialize: function(elements, options){
+		this.parent(elements, options);
+		this.elements.each(function(el){
+			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+		});
+		this.setDefaultOrder();
+	},
+
+	setDefaultOrder: function(){
+		this.currentOrder = this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	sort: function(newOrder){
+		if ($type(newOrder) != 'array') return false;
+		var top = 0,
+			left = 0,
+			next = {},
+			zero = {},
+			vert = this.options.mode == 'vertical';
+		var current = this.elements.map(function(el, index){
+			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+			var val;
+			if (vert){
+				val = {
+					top: top,
+					margin: size['margin-top'],
+					height: size.totalHeight
+				};
+				top += val.height - size['margin-top'];
+			} else {
+				val = {
+					left: left,
+					margin: size['margin-left'],
+					width: size.totalWidth
+				};
+				left += val.width;
+			}
+			var plain = vert ? 'top' : 'left';
+			zero[index] = {};
+			var start = el.getStyle(plain).toInt();
+			zero[index][plain] = start || 0;
+			return val;
+		}, this);
+		this.set(zero);
+		newOrder = newOrder.map(function(i){ return i.toInt(); });
+		if (newOrder.length != this.elements.length){
+			this.currentOrder.each(function(index){
+				if (!newOrder.contains(index)) newOrder.push(index);
+			});
+			if (newOrder.length > this.elements.length)
+				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+		}
+		var margin = top = left = 0;
+		newOrder.each(function(item, index){
+			var newPos = {};
+			if (vert){
+				newPos.top = top - current[item].top - margin;
+				top += current[item].height;
+			} else {
+				newPos.left = left - current[item].left;
+				left += current[item].width;
+			}
+			margin = margin + current[item].margin;
+			next[item]=newPos;
+		}, this);
+		var mapped = {};
+		$A(newOrder).sort().each(function(index){
+			mapped[index] = next[index];
+		});
+		this.start(mapped);
+		this.currentOrder = newOrder;
+		return this;
+	},
+
+	rearrangeDOM: function(newOrder){
+		newOrder = newOrder || this.currentOrder;
+		var parent = this.elements[0].getParent();
+		var rearranged = [];
+		this.elements.setStyle('opacity', 0);
+		//move each element and store the new default order
+		newOrder.each(function(index){
+			rearranged.push(this.elements[index].inject(parent).setStyles({
+				top: 0,
+				left: 0
+			}));
+		}, this);
+		this.elements.setStyle('opacity', 1);
+		this.elements = $$(rearranged);
+		this.setDefaultOrder();
+		return this;
+	},
+
+	getDefaultOrder: function(){
+		return this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	forward: function(){
+		return this.sort(this.getDefaultOrder());
+	},
+
+	backward: function(){
+		return this.sort(this.getDefaultOrder().reverse());
+	},
+
+	reverse: function(){
+		return this.sort(this.currentOrder.reverse());
+	},
+
+	sortByElements: function(elements){
+		return this.sort(elements.map(function(el){
+			return this.elements.indexOf(el);
+		}, this));
+	},
+
+	swap: function(one, two){
+		if ($type(one) == 'element') one = this.elements.indexOf(one);
+		if ($type(two) == 'element') two = this.elements.indexOf(two);
+		
+		var newOrder = $A(this.currentOrder);
+		newOrder[this.currentOrder.indexOf(one)] = two;
+		newOrder[this.currentOrder.indexOf(two)] = one;
+		return this.sort(newOrder);
+	}
+
+});/*
+---
+
+script: Drag.js
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Tom Occhinno
+- Jan Kassens
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Drag]
+
+*/
+
+var Drag = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onBeforeStart: $empty(thisElement),
+		onStart: $empty(thisElement, event),
+		onSnap: $empty(thisElement)
+		onDrag: $empty(thisElement, event),
+		onCancel: $empty(thisElement),
+		onComplete: $empty(thisElement, event),*/
+		snap: 6,
+		unit: 'px',
+		grid: false,
+		style: true,
+		limit: false,
+		handle: false,
+		invert: false,
+		preventDefault: false,
+		stopPropagation: false,
+		modifiers: {x: 'left', y: 'top'}
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
+		this.element = document.id(params.element);
+		this.document = this.element.getDocument();
+		this.setOptions(params.options || {});
+		var htype = $type(this.options.handle);
+		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+		this.mouse = {'now': {}, 'pos': {}};
+		this.value = {'start': {}, 'now': {}};
+
+		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
+
+		this.bound = {
+			start: this.start.bind(this),
+			check: this.check.bind(this),
+			drag: this.drag.bind(this),
+			stop: this.stop.bind(this),
+			cancel: this.cancel.bind(this),
+			eventStop: $lambda(false)
+		};
+		this.attach();
+	},
+
+	attach: function(){
+		this.handles.addEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	detach: function(){
+		this.handles.removeEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	start: function(event){
+		if (event.rightClick) return;
+		if (this.options.preventDefault) event.preventDefault();
+		if (this.options.stopPropagation) event.stopPropagation();
+		this.mouse.start = event.page;
+		this.fireEvent('beforeStart', this.element);
+		var limit = this.options.limit;
+		this.limit = {x: [], y: []};
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
+			else this.value.now[z] = this.element[this.options.modifiers[z]];
+			if (this.options.invert) this.value.now[z] *= -1;
+			this.mouse.pos[z] = event.page[z] - this.value.now[z];
+			if (limit && limit[z]){
+				for (var i = 2; i--; i){
+					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
+				}
+			}
+		}
+		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
+		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
+		this.document.addEvent(this.selection, this.bound.eventStop);
+	},
+
+	check: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+		if (distance > this.options.snap){
+			this.cancel();
+			this.document.addEvents({
+				mousemove: this.bound.drag,
+				mouseup: this.bound.stop
+			});
+			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+		}
+	},
+
+	drag: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		this.mouse.now = event.page;
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+			if (this.options.invert) this.value.now[z] *= -1;
+			if (this.options.limit && this.limit[z]){
+				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
+					this.value.now[z] = this.limit[z][1];
+				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
+					this.value.now[z] = this.limit[z][0];
+				}
+			}
+			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
+			if (this.options.style) {
+				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
+			} else {
+				this.element[this.options.modifiers[z]] = this.value.now[z];
+			}
+		}
+		this.fireEvent('drag', [this.element, event]);
+	},
+
+	cancel: function(event){
+		this.document.removeEvent('mousemove', this.bound.check);
+		this.document.removeEvent('mouseup', this.bound.cancel);
+		if (event){
+			this.document.removeEvent(this.selection, this.bound.eventStop);
+			this.fireEvent('cancel', this.element);
+		}
+	},
+
+	stop: function(event){
+		this.document.removeEvent(this.selection, this.bound.eventStop);
+		this.document.removeEvent('mousemove', this.bound.drag);
+		this.document.removeEvent('mouseup', this.bound.stop);
+		if (event) this.fireEvent('complete', [this.element, event]);
+	}
+
+});
+
+Element.implement({
+
+	makeResizable: function(options){
+		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
+		this.store('resizer', drag);
+		return drag.addEvent('drag', function(){
+			this.fireEvent('resize', drag);
+		}.bind(this));
+	}
+
+});
+/*
+---
+
+script: Drag.Move.js
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Tom Occhinno
+- Jan Kassens
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+	Extends: Drag,
+
+	options: {/*
+		onEnter: $empty(thisElement, overed),
+		onLeave: $empty(thisElement, overed),
+		onDrop: $empty(thisElement, overed, event),*/
+		droppables: [],
+		container: false,
+		precalculate: false,
+		includeMargins: true,
+		checkDroppables: true
+	},
+
+	initialize: function(element, options){
+		this.parent(element, options);
+		element = this.element;
+		
+		this.droppables = $$(this.options.droppables);
+		this.container = document.id(this.options.container);
+		
+		if (this.container && $type(this.container) != 'element')
+			this.container = document.id(this.container.getDocument().body);
+		
+		var styles = element.getStyles('left', 'top', 'position');
+		if (styles.left == 'auto' || styles.top == 'auto')
+			element.setPosition(element.getPosition(element.getOffsetParent()));
+		
+		if (styles.position == 'static')
+			element.setStyle('position', 'absolute');
+
+		this.addEvent('start', this.checkDroppables, true);
+
+		this.overed = null;
+	},
+
+	start: function(event){
+		if (this.container) this.options.limit = this.calculateLimit();
+		
+		if (this.options.precalculate){
+			this.positions = this.droppables.map(function(el){
+				return el.getCoordinates();
+			});
+		}
+		
+		this.parent(event);
+	},
+	
+	calculateLimit: function(){
+		var offsetParent = this.element.getOffsetParent(),
+			containerCoordinates = this.container.getCoordinates(offsetParent),
+			containerBorder = {},
+			elementMargin = {},
+			elementBorder = {},
+			containerMargin = {},
+			offsetParentPadding = {};
+
+		['top', 'right', 'bottom', 'left'].each(function(pad){
+			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
+			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
+			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
+			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
+			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+		}, this);
+
+		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
+			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
+			left = 0,
+			top = 0,
+			right = containerCoordinates.right - containerBorder.right - width,
+			bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+		if (this.options.includeMargins){
+			left += elementMargin.left;
+			top += elementMargin.top;
+		} else {
+			right += elementMargin.right;
+			bottom += elementMargin.bottom;
+		}
+		
+		if (this.element.getStyle('position') == 'relative'){
+			var coords = this.element.getCoordinates(offsetParent);
+			coords.left -= this.element.getStyle('left').toInt();
+			coords.top -= this.element.getStyle('top').toInt();
+			
+			left += containerBorder.left - coords.left;
+			top += containerBorder.top - coords.top;
+			right += elementMargin.left - coords.left;
+			bottom += elementMargin.top - coords.top;
+			
+			if (this.container != offsetParent){
+				left += containerMargin.left + offsetParentPadding.left;
+				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
+			}
+		} else {
+			left -= elementMargin.left;
+			top -= elementMargin.top;
+			
+			if (this.container == offsetParent){
+				right -= containerBorder.left;
+				bottom -= containerBorder.top;
+			} else {
+				left += containerCoordinates.left + containerBorder.left;
+				top += containerCoordinates.top + containerBorder.top;
+			}
+		}
+		
+		return {
+			x: [left, right],
+			y: [top, bottom]
+		};
+	},
+
+	checkAgainst: function(el, i){
+		el = (this.positions) ? this.positions[i] : el.getCoordinates();
+		var now = this.mouse.now;
+		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+	},
+
+	checkDroppables: function(){
+		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
+		if (this.overed != overed){
+			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+			if (overed) this.fireEvent('enter', [this.element, overed]);
+			this.overed = overed;
+		}
+	},
+
+	drag: function(event){
+		this.parent(event);
+		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+	},
+
+	stop: function(event){
+		this.checkDroppables();
+		this.fireEvent('drop', [this.element, this.overed, event]);
+		this.overed = null;
+		return this.parent(event);
+	}
+
+});
+
+Element.implement({
+
+	makeDraggable: function(options){
+		var drag = new Drag.Move(this, options);
+		this.store('dragger', drag);
+		return drag;
+	}
+
+});
+/*
+---
+
+script: Slider.js
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Class.Binds
+- /Drag
+- /Element.Dimensions
+- /Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+	Implements: [Events, Options],
+
+	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+	options: {/*
+		onTick: $empty(intPosition),
+		onChange: $empty(intStep),
+		onComplete: $empty(strStep),*/
+		onTick: function(position){
+			if (this.options.snap) position = this.toPosition(this.step);
+			this.knob.setStyle(this.property, position);
+		},
+		initialStep: 0,
+		snap: false,
+		offset: 0,
+		range: false,
+		wheel: false,
+		steps: 100,
+		mode: 'horizontal'
+	},
+
+	initialize: function(element, knob, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.knob = document.id(knob);
+		this.previousChange = this.previousEnd = this.step = -1;
+		var offset, limit = {}, modifiers = {'x': false, 'y': false};
+		switch (this.options.mode){
+			case 'vertical':
+				this.axis = 'y';
+				this.property = 'top';
+				offset = 'offsetHeight';
+				break;
+			case 'horizontal':
+				this.axis = 'x';
+				this.property = 'left';
+				offset = 'offsetWidth';
+		}
+		
+		this.full = this.element.measure(function(){ 
+			this.half = this.knob[offset] / 2; 
+			return this.element[offset] - this.knob[offset] + (this.options.offset * 2); 
+		}.bind(this));
+		
+		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
+		this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
+		this.range = this.max - this.min;
+		this.steps = this.options.steps || this.full;
+		this.stepSize = Math.abs(this.range) / this.steps;
+		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
+
+		this.knob.setStyle('position', 'relative').setStyle(this.property, this.options.initialStep ? this.toPosition(this.options.initialStep) : - this.options.offset);
+		modifiers[this.axis] = this.property;
+		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];
+
+		var dragOptions = {
+			snap: 0,
+			limit: limit,
+			modifiers: modifiers,
+			onDrag: this.draggedKnob,
+			onStart: this.draggedKnob,
+			onBeforeStart: (function(){
+				this.isDragging = true;
+			}).bind(this),
+			onCancel: function() {
+				this.isDragging = false;
+			}.bind(this),
+			onComplete: function(){
+				this.isDragging = false;
+				this.draggedKnob();
+				this.end();
+			}.bind(this)
+		};
+		if (this.options.snap){
+			dragOptions.grid = Math.ceil(this.stepWidth);
+			dragOptions.limit[this.axis][1] = this.full;
+		}
+
+		this.drag = new Drag(this.knob, dragOptions);
+		this.attach();
+	},
+
+	attach: function(){
+		this.element.addEvent('mousedown', this.clickedElement);
+		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+		this.drag.attach();
+		return this;
+	},
+
+	detach: function(){
+		this.element.removeEvent('mousedown', this.clickedElement);
+		this.element.removeEvent('mousewheel', this.scrolledElement);
+		this.drag.detach();
+		return this;
+	},
+
+	set: function(step){
+		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+		if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+		this.step = Math.round(step);
+		this.checkStep();
+		this.fireEvent('tick', this.toPosition(this.step));
+		this.end();
+		return this;
+	},
+
+	clickedElement: function(event){
+		if (this.isDragging || event.target == this.knob) return;
+
+		var dir = this.range < 0 ? -1 : 1;
+		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+		this.fireEvent('tick', position);
+		this.end();
+	},
+
+	scrolledElement: function(event){
+		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
+		event.stop();
+	},
+
+	draggedKnob: function(){
+		var dir = this.range < 0 ? -1 : 1;
+		var position = this.drag.value.now[this.axis];
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+	},
+
+	checkStep: function(){
+		if (this.previousChange != this.step){
+			this.previousChange = this.step;
+			this.fireEvent('change', this.step);
+		}
+	},
+
+	end: function(){
+		if (this.previousEnd !== this.step){
+			this.previousEnd = this.step;
+			this.fireEvent('complete', this.step + '');
+		}
+	},
+
+	toStep: function(position){
+		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
+	},
+
+	toPosition: function(step){
+		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
+	}
+
+});/*
+---
+
+script: Sortables.js
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+- Tom Occhino
+
+requires:
+- /Drag.Move
+
+provides: [Slider]
+
+...
+*/
+
+var Sortables = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onSort: $empty(element, clone),
+		onStart: $empty(element, clone),
+		onComplete: $empty(element),*/
+		snap: 4,
+		opacity: 1,
+		clone: false,
+		revert: false,
+		handle: false,
+		constrain: false
+	},
+
+	initialize: function(lists, options){
+		this.setOptions(options);
+		this.elements = [];
+		this.lists = [];
+		this.idle = true;
+
+		this.addLists($$(document.id(lists) || lists));
+		if (!this.options.clone) this.options.revert = false;
+		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
+	},
+
+	attach: function(){
+		this.addLists(this.lists);
+		return this;
+	},
+
+	detach: function(){
+		this.lists = this.removeLists(this.lists);
+		return this;
+	},
+
+	addItems: function(){
+		Array.flatten(arguments).each(function(element){
+			this.elements.push(element);
+			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+		}, this);
+		return this;
+	},
+
+	addLists: function(){
+		Array.flatten(arguments).each(function(list){
+			this.lists.push(list);
+			this.addItems(list.getChildren());
+		}, this);
+		return this;
+	},
+
+	removeItems: function(){
+		return $$(Array.flatten(arguments).map(function(element){
+			this.elements.erase(element);
+			var start = element.retrieve('sortables:start');
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+			
+			return element;
+		}, this));
+	},
+
+	removeLists: function(){
+		return $$(Array.flatten(arguments).map(function(list){
+			this.lists.erase(list);
+			this.removeItems(list.getChildren());
+			
+			return list;
+		}, this));
+	},
+
+	getClone: function(event, element){
+		if (!this.options.clone) return new Element('div').inject(document.body);
+		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+		var clone = element.clone(true).setStyles({
+			margin: '0px',
+			position: 'absolute',
+			visibility: 'hidden',
+			'width': element.getStyle('width')
+		});
+		//prevent the duplicated radio inputs from unchecking the real one
+		if (clone.get('html').test('radio')) {
+			clone.getElements('input[type=radio]').each(function(input, i) {
+				input.set('name', 'clone_' + i);
+			});
+		}
+		
+		return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
+	},
+
+	getDroppables: function(){
+		var droppables = this.list.getChildren();
+		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
+		return droppables.erase(this.clone).erase(this.element);
+	},
+
+	insert: function(dragging, element){
+		var where = 'inside';
+		if (this.lists.contains(element)){
+			this.list = element;
+			this.drag.droppables = this.getDroppables();
+		} else {
+			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+		}
+		this.element.inject(element, where);
+		this.fireEvent('sort', [this.element, this.clone]);
+	},
+
+	start: function(event, element){
+		if (!this.idle) return;
+		this.idle = false;
+		this.element = element;
+		this.opacity = element.get('opacity');
+		this.list = element.getParent();
+		this.clone = this.getClone(event, element);
+
+		this.drag = new Drag.Move(this.clone, {
+			snap: this.options.snap,
+			container: this.options.constrain && this.element.getParent(),
+			droppables: this.getDroppables(),
+			onSnap: function(){
+				event.stop();
+				this.clone.setStyle('visibility', 'visible');
+				this.element.set('opacity', this.options.opacity || 0);
+				this.fireEvent('start', [this.element, this.clone]);
+			}.bind(this),
+			onEnter: this.insert.bind(this),
+			onCancel: this.reset.bind(this),
+			onComplete: this.end.bind(this)
+		});
+
+		this.clone.inject(this.element, 'before');
+		this.drag.start(event);
+	},
+
+	end: function(){
+		this.drag.detach();
+		this.element.set('opacity', this.opacity);
+		if (this.effect){
+			var dim = this.element.getStyles('width', 'height');
+			var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
+			this.effect.element = this.clone;
+			this.effect.start({
+				top: pos.top,
+				left: pos.left,
+				width: dim.width,
+				height: dim.height,
+				opacity: 0.25
+			}).chain(this.reset.bind(this));
+		} else {
+			this.reset();
+		}
+	},
+
+	reset: function(){
+		this.idle = true;
+		this.clone.destroy();
+		this.fireEvent('complete', this.element);
+	},
+
+	serialize: function(){
+		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
+		var serial = this.lists.map(function(list){
+			return list.getChildren().map(params.modifier || function(element){
+				return element.get('id');
+			}, this);
+		}, this);
+
+		var index = params.index;
+		if (this.lists.length == 1) index = 0;
+		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+	}
+
+});
+/*
+---
+
+script: Request.JSONP.js
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Guillermo Rauch
+
+requires:
+- core:1.2.4/Element
+- core:1.2.4/Request
+- /Log
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+	Implements: [Chain, Events, Options, Log],
+
+	options: {/*
+		onRetry: $empty(intRetries),
+		onRequest: $empty(scriptElement),
+		onComplete: $empty(data),
+		onSuccess: $empty(data),
+		onCancel: $empty(),
+		log: false,
+		*/
+		url: '',
+		data: {},
+		retries: 0,
+		timeout: 0,
+		link: 'ignore',
+		callbackKey: 'callback',
+		injectScript: document.head
+	},
+
+	initialize: function(options){
+		this.setOptions(options);
+		if (this.options.log) this.enableLog();
+		this.running = false;
+		this.requests = 0;
+		this.triesRemaining = [];
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!$chk(arguments[1]) && !this.check(options)) return this;
+
+		var type = $type(options), 
+				old = this.options, 
+				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		options = $extend({data: old.data, url: old.url}, options);
+
+		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
+		var remaining = this.triesRemaining[index];
+
+		(function(){
+			var script = this.getScript(options);
+			this.log('JSONP retrieving script with url: ' + script.get('src'));
+			this.fireEvent('request', script);
+			this.running = true;
+
+			(function(){
+				if (remaining){
+					this.triesRemaining[index] = remaining - 1;
+					if (script){
+						script.destroy();
+						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
+					}
+				} else if(script && this.options.timeout){
+					script.destroy();
+					this.cancel().fireEvent('failure');
+				}
+			}).delay(this.options.timeout, this);
+		}).delay(Browser.Engine.trident ? 50 : 0, this);
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.fireEvent('cancel');
+		return this;
+	},
+
+	getScript: function(options){
+		var index = Request.JSONP.counter,
+				data;
+		Request.JSONP.counter++;
+
+		switch ($type(options.data)){
+			case 'element': data = document.id(options.data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(options.data);
+		}
+
+		var src = options.url + 
+			 (options.url.test('\\?') ? '&' :'?') + 
+			 (options.callbackKey || this.options.callbackKey) + 
+			 '=Request.JSONP.request_map.request_'+ index + 
+			 (data ? '&' + data : '');
+		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+
+		var script = new Element('script', {type: 'text/javascript', src: src});
+		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
+		return script.inject(this.options.injectScript);
+	},
+
+	success: function(args, script){
+		if (script) script.destroy();
+		this.running = false;
+		this.log('JSONP successfully retrieved: ', args);
+		this.fireEvent('complete', args).fireEvent('success', args).callChain();
+	}
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};/*
+---
+
+script: Request.Queue.js
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- core:1.2.4/Request
+- /Log
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+	options: {/*
+		onRequest: $empty(argsPassedToOnRequest),
+		onSuccess: $empty(argsPassedToOnSuccess),
+		onComplete: $empty(argsPassedToOnComplete),
+		onCancel: $empty(argsPassedToOnCancel),
+		onException: $empty(argsPassedToOnException),
+		onFailure: $empty(argsPassedToOnFailure),
+		onEnd: $empty,
+		*/
+		stopOnFailure: true,
+		autoAdvance: true,
+		concurrent: 1,
+		requests: {}
+	},
+
+	initialize: function(options){
+		if(options){
+			var requests = options.requests;
+			delete options.requests;	
+		}
+		this.setOptions(options);
+		this.requests = new Hash;
+		this.queue = [];
+		this.reqBinders = {};
+		
+		if(requests) this.addRequests(requests);
+	},
+
+	addRequest: function(name, request){
+		this.requests.set(name, request);
+		this.attach(name, request);
+		return this;
+	},
+
+	addRequests: function(obj){
+		$each(obj, function(req, name){
+			this.addRequest(name, req);
+		}, this);
+		return this;
+	},
+
+	getName: function(req){
+		return this.requests.keyOf(req);
+	},
+
+	attach: function(name, req){
+		if (req._groupSend) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			if(!this.reqBinders[name]) this.reqBinders[name] = {};
+			this.reqBinders[name][evt] = function(){
+				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
+			}.bind(this);
+			req.addEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req._groupSend = req.send;
+		req.send = function(options){
+			this.send(name, options);
+			return req;
+		}.bind(this);
+		return this;
+	},
+
+	removeRequest: function(req){
+		var name = $type(req) == 'object' ? this.getName(req) : req;
+		if (!name && $type(name) != 'string') return this;
+		req = this.requests.get(name);
+		if (!req) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			req.removeEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req.send = req._groupSend;
+		delete req._groupSend;
+		return this;
+	},
+
+	getRunning: function(){
+		return this.requests.filter(function(r){
+			return r.running;
+		});
+	},
+
+	isRunning: function(){
+		return !!(this.getRunning().getKeys().length);
+	},
+
+	send: function(name, options){
+		var q = function(){
+			this.requests.get(name)._groupSend(options);
+			this.queue.erase(q);
+		}.bind(this);
+		q.name = name;
+		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+		else q();
+		return this;
+	},
+
+	hasNext: function(name){
+		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+	},
+
+	resume: function(){
+		this.error = false;
+		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
+		return this;
+	},
+
+	runNext: function(name){
+		if (!this.queue.length) return this;
+		if (!name){
+			this.queue[0]();
+		} else {
+			var found;
+			this.queue.each(function(q){
+				if (!found && q.name == name){
+					found = true;
+					q();
+				}
+			});
+		}
+		return this;
+	},
+
+	runAll: function() {
+		this.queue.each(function(q) {
+			q();
+		});
+		return this;
+	},
+
+	clear: function(name){
+		if (!name){
+			this.queue.empty();
+		} else {
+			this.queue = this.queue.map(function(q){
+				if (q.name != name) return q;
+				else return false;
+			}).filter(function(q){ return q; });
+		}
+		return this;
+	},
+
+	cancel: function(name){
+		this.requests.get(name).cancel();
+		return this;
+	},
+
+	onRequest: function(){
+		this.fireEvent('request', arguments);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', arguments);
+		if (!this.queue.length) this.fireEvent('end');
+	},
+
+	onCancel: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('cancel', arguments);
+	},
+
+	onSuccess: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('success', arguments);
+	},
+
+	onFailure: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('failure', arguments);
+	},
+
+	onException: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('exception', arguments);
+	}
+
+});
+/*
+---
+
+script: Request.Periodical.js
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Request
+- /MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+	options: {
+		initialDelay: 5000,
+		delay: 5000,
+		limit: 60000
+	},
+
+	startTimer: function(data){
+		var fn = function(){
+			if (!this.running) this.send({data: data});
+		};
+		this.timer = fn.delay(this.options.initialDelay, this);
+		this.lastDelay = this.options.initialDelay;
+		this.completeCheck = function(response){
+			$clear(this.timer);
+			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+			this.timer = fn.delay(this.lastDelay, this);
+		};
+		return this.addEvent('complete', this.completeCheck);
+	},
+
+	stopTimer: function(){
+		$clear(this.timer);
+		return this.removeEvent('complete', this.completeCheck);
+	}
+
+});/*
+---
+
+script: Assets.js
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Event
+- /MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+	javascript: function(source, properties){
+		properties = $extend({
+			onload: $empty,
+			document: document,
+			check: $lambda(true)
+		}, properties);
+		
+		if (properties.onLoad) properties.onload = properties.onLoad;
+		
+		var script = new Element('script', {src: source, type: 'text/javascript'});
+
+		var load = properties.onload.bind(script), 
+			check = properties.check, 
+			doc = properties.document;
+		delete properties.onload;
+		delete properties.check;
+		delete properties.document;
+
+		script.addEvents({
+			load: load,
+			readystatechange: function(){
+				if (['loaded', 'complete'].contains(this.readyState)) load();
+			}
+		}).set(properties);
+
+		if (Browser.Engine.webkit419) var checker = (function(){
+			if (!$try(check)) return;
+			$clear(checker);
+			load();
+		}).periodical(50);
+
+		return script.inject(doc.head);
+	},
+
+	css: function(source, properties){
+		return new Element('link', $merge({
+			rel: 'stylesheet',
+			media: 'screen',
+			type: 'text/css',
+			href: source
+		}, properties)).inject(document.head);
+	},
+
+	image: function(source, properties){
+		properties = $merge({
+			onload: $empty,
+			onabort: $empty,
+			onerror: $empty
+		}, properties);
+		var image = new Image();
+		var element = document.id(image) || new Element('img');
+		['load', 'abort', 'error'].each(function(name){
+			var type = 'on' + name;
+			var cap = name.capitalize();
+			if (properties['on' + cap]) properties[type] = properties['on' + cap];
+			var event = properties[type];
+			delete properties[type];
+			image[type] = function(){
+				if (!image) return;
+				if (!element.parentNode){
+					element.width = image.width;
+					element.height = image.height;
+				}
+				image = image.onload = image.onabort = image.onerror = null;
+				event.delay(1, element, element);
+				element.fireEvent(name, element, 1);
+			};
+		});
+		image.src = element.src = source;
+		if (image && image.complete) image.onload.delay(1);
+		return element.set(properties);
+	},
+
+	images: function(sources, options){
+		options = $merge({
+			onComplete: $empty,
+			onProgress: $empty,
+			onError: $empty,
+			properties: {}
+		}, options);
+		sources = $splat(sources);
+		var images = [];
+		var counter = 0;
+		return new Elements(sources.map(function(source){
+			return Asset.image(source, $extend(options.properties, {
+				onload: function(){
+					options.onProgress.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				},
+				onerror: function(){
+					options.onError.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				}
+			}));
+		}));
+	}
+
+};/*
+---
+
+script: Color.js
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- core:1.2.4/Number
+- core:1.2.4/Hash
+- core:1.2.4/Function
+- core:1.2.4/$util
+
+provides: [Color]
+
+...
+*/
+
+var Color = new Native({
+
+	initialize: function(color, type){
+		if (arguments.length >= 3){
+			type = 'rgb'; color = Array.slice(arguments, 0, 3);
+		} else if (typeof color == 'string'){
+			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+			else if (color.match(/hsb/)) color = color.hsbToRgb();
+			else color = color.hexToRgb(true);
+		}
+		type = type || 'rgb';
+		switch (type){
+			case 'hsb':
+				var old = color;
+				color = color.hsbToRgb();
+				color.hsb = old;
+			break;
+			case 'hex': color = color.hexToRgb(true); break;
+		}
+		color.rgb = color.slice(0, 3);
+		color.hsb = color.hsb || color.rgbToHsb();
+		color.hex = color.rgbToHex();
+		return $extend(color, this);
+	}
+
+});
+
+Color.implement({
+
+	mix: function(){
+		var colors = Array.slice(arguments);
+		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
+		var rgb = this.slice();
+		colors.each(function(color){
+			color = new Color(color);
+			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+		});
+		return new Color(rgb, 'rgb');
+	},
+
+	invert: function(){
+		return new Color(this.map(function(value){
+			return 255 - value;
+		}));
+	},
+
+	setHue: function(value){
+		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+	},
+
+	setSaturation: function(percent){
+		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+	},
+
+	setBrightness: function(percent){
+		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+	}
+
+});
+
+var $RGB = function(r, g, b){
+	return new Color([r, g, b], 'rgb');
+};
+
+var $HSB = function(h, s, b){
+	return new Color([h, s, b], 'hsb');
+};
+
+var $HEX = function(hex){
+	return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+	rgbToHsb: function(){
+		var red = this[0],
+				green = this[1],
+				blue = this[2],
+				hue = 0;
+		var max = Math.max(red, green, blue),
+				min = Math.min(red, green, blue);
+		var delta = max - min;
+		var brightness = max / 255,
+				saturation = (max != 0) ? delta / max : 0;
+		if(saturation != 0) {
+			var rr = (max - red) / delta;
+			var gr = (max - green) / delta;
+			var br = (max - blue) / delta;
+			if (red == max) hue = br - gr;
+			else if (green == max) hue = 2 + rr - br;
+			else hue = 4 + gr - rr;
+			hue /= 6;
+			if (hue < 0) hue++;
+		}
+		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+	},
+
+	hsbToRgb: function(){
+		var br = Math.round(this[2] / 100 * 255);
+		if (this[1] == 0){
+			return [br, br, br];
+		} else {
+			var hue = this[0] % 360;
+			var f = hue % 60;
+			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+			switch (Math.floor(hue / 60)){
+				case 0: return [br, t, p];
+				case 1: return [q, br, p];
+				case 2: return [p, br, t];
+				case 3: return [p, q, br];
+				case 4: return [t, p, br];
+				case 5: return [br, p, q];
+			}
+		}
+		return false;
+	}
+
+});
+
+String.implement({
+
+	rgbToHsb: function(){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHsb() : null;
+	},
+
+	hsbToRgb: function(){
+		var hsb = this.match(/\d{1,3}/g);
+		return (hsb) ? hsb.hsbToRgb() : null;
+	}
+
+});
+/*
+---
+
+script: Group.js
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Events
+- /MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+var Group = new Class({
+
+	initialize: function(){
+		this.instances = Array.flatten(arguments);
+		this.events = {};
+		this.checker = {};
+	},
+
+	addEvent: function(type, fn){
+		this.checker[type] = this.checker[type] || {};
+		this.events[type] = this.events[type] || [];
+		if (this.events[type].contains(fn)) return false;
+		else this.events[type].push(fn);
+		this.instances.each(function(instance, i){
+			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
+		}, this);
+		return this;
+	},
+
+	check: function(type, instance, i){
+		this.checker[type][i] = true;
+		var every = this.instances.every(function(current, j){
+			return this.checker[type][j] || false;
+		}, this);
+		if (!every) return;
+		this.checker[type] = {};
+		this.events[type].each(function(event){
+			event.call(this, this.instances, instance);
+		}, this);
+	}
+
+});
+/*
+---
+
+script: Hash.Cookie.js
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Aaron Newton
+
+requires:
+- core:1.2.4/Cookie
+- core:1.2.4/JSON
+- /MooTools.More
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+	Extends: Cookie,
+
+	options: {
+		autoSave: true
+	},
+
+	initialize: function(name, options){
+		this.parent(name, options);
+		this.load();
+	},
+
+	save: function(){
+		var value = JSON.encode(this.hash);
+		if (!value || value.length > 4096) return false; //cookie would be truncated!
+		if (value == '{}') this.dispose();
+		else this.write(value);
+		return true;
+	},
+
+	load: function(){
+		this.hash = new Hash(JSON.decode(this.read(), true));
+		return this;
+	}
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+		var value = method.apply(this.hash, arguments);
+		if (this.options.autoSave) this.save();
+		return value;
+	});
+});/*
+---
+
+script: HtmlTable.js
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- /Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		properties: {
+			cellpadding: 0,
+			cellspacing: 0,
+			border: 0
+		},
+		rows: [],
+		headers: [],
+		footers: []
+	},
+
+	property: 'HtmlTable',
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, table: Element.type});
+		this.setOptions(params.options);
+		this.element = params.table || new Element('table', this.options.properties);
+		if (this.occlude()) return this.occluded;
+		this.build();
+	},
+
+	build: function(){
+		this.element.store('HtmlTable', this);
+
+		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+		$$(this.body.rows);
+
+		if (this.options.headers.length) this.setHeaders(this.options.headers);
+		else this.thead = document.id(this.element.tHead);
+		if (this.thead) this.head = document.id(this.thead.rows[0]);
+
+		if (this.options.footers.length) this.setFooters(this.options.footers);
+		this.tfoot = document.id(this.element.tFoot);
+		if (this.tfoot) this.foot = document.id(this.thead.rows[0]);
+
+		this.options.rows.each(function(row){
+			this.push(row);
+		}, this);
+
+		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+				this[method] = this.element[method].bind(this.element);
+		}, this);
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	empty: function(){
+		this.body.empty();
+		return this;
+	},
+
+	set: function(what, items) {
+		var target = (what == 'headers') ? 'tHead' : 'tFoot';
+		this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
+		var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
+		if (what == 'headers') this.head = document.id(this.thead.rows[0]);
+		else this.foot = document.id(this.thead.rows[0]);
+		return data;
+	},
+
+	setHeaders: function(headers){
+		this.set('headers', headers);
+		return this;
+	},
+
+	setFooters: function(footers){
+		this.set('footers', footers);
+		return this;
+	},
+
+	push: function(row, rowProperties, target, tag){
+		var tds = row.map(function(data){
+			var td = new Element(tag || 'td', data.properties),
+				type = data.content || data || '',
+				element = document.id(type);
+			if($type(type) != 'string' && element) td.adopt(element);
+			else td.set('html', type);
+
+			return td;
+		});
+
+		return {
+			tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
+			tds: tds
+		};
+	}
+
+});
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- /HtmlTable
+- /Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		classZebra: 'table-tr-odd',
+		zebra: true
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		if (this.options.zebra) this.updateZebras();
+	},
+
+	updateZebras: function(){
+		Array.each(this.body.rows, this.zebra, this);
+	},
+
+	zebra: function(row, i){
+		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+	},
+
+	push: function(){
+		var pushed = this.previous.apply(this, arguments);
+		if (this.options.zebra) this.updateZebras();
+		return pushed;
+	}
+
+});/*
+---
+
+script: HtmlTable.Sort.js
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- core:1.2.4/Hash
+- /HtmlTable
+- /Class.refactor
+- /Element.Delegation
+- /Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {/*
+		onSort: $empty, */
+		sortIndex: 0,
+		sortReverse: false,
+		parsers: [],
+		defaultParser: 'string',
+		classSortable: 'table-sortable',
+		classHeadSort: 'table-th-sort',
+		classHeadSortRev: 'table-th-sort-rev',
+		classNoSort: 'table-th-nosort',
+		classGroupHead: 'table-tr-group-head',
+		classGroup: 'table-tr-group',
+		classCellSort: 'table-td-sort',
+		classSortSpan: 'table-th-sort-span',
+		sortable: false
+	},
+
+	initialize: function () {
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.sorted = {index: null, dir: 1};
+		this.bound = {
+			headClick: this.headClick.bind(this)
+		};
+		this.sortSpans = new Elements();
+		if (this.options.sortable) {
+			this.enableSort();
+			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+		}
+	},
+
+	attachSorts: function(attach){
+		this.element.removeEvents('click:relay(th)');
+		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
+	},
+
+	setHeaders: function(){
+		this.previous.apply(this, arguments);
+		if (this.sortEnabled) this.detectParsers();
+	},
+	
+	detectParsers: function(force){
+		if (!this.head) return;
+		var parsers = this.options.parsers, 
+				rows = this.body.rows;
+
+		// auto-detect
+		this.parsers = $$(this.head.cells).map(function(cell, index) {
+			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
+			var thDiv = new Element('div');
+			$each(cell.childNodes, function(node) {
+				thDiv.adopt(node);
+			});
+			thDiv.inject(cell);
+			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(thDiv, 'top');
+			
+			this.sortSpans.push(sortSpan);
+
+			var parser = parsers[index], 
+					cancel;
+			switch ($type(parser)) {
+				case 'function': parser = {convert: parser}; cancel = true; break;
+				case 'string': parser = parser; cancel = true; break;
+			}
+			if (!cancel) {
+				HtmlTable.Parsers.some(function(current) {
+					var match = current.match;
+					if (!match) return false;
+					for (var i = 0, j = rows.length; i < j; i++) {
+						var text = $(rows[i].cells[index]).get('html').clean();
+						if (text && match.test(text)) {
+							parser = current;
+							return true;
+						}
+					}
+				});
+			}
+
+			if (!parser) parser = this.options.defaultParser;
+			cell.store('htmltable-parser', parser);
+			return parser;
+		}, this);
+	},
+
+	headClick: function(event, el) {
+		console.log(el);
+		if (!this.head || el.hasClass(this.options.classNoSort)) return;
+		var index = Array.indexOf(this.head.cells, el);
+		this.sort(index);
+		return false;
+	},
+
+	sort: function(index, reverse, pre) {
+		if (!this.head) return;
+		pre = !!(pre);
+		var classCellSort = this.options.classCellSort;
+		var classGroup = this.options.classGroup, 
+				classGroupHead = this.options.classGroupHead;
+
+		if (!pre) {
+			if (index != null) {
+				if (this.sorted.index == index) {
+					this.sorted.reverse = !(this.sorted.reverse);
+				} else {
+					if (this.sorted.index != null) {
+						this.sorted.reverse = false;
+						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+					} else {
+						this.sorted.reverse = true;
+					}
+					this.sorted.index = index;
+				}
+			} else {
+				index = this.sorted.index;
+			}
+
+			if (reverse != null) this.sorted.reverse = reverse;
+
+			var head = document.id(this.head.cells[index]);
+			if (head) {
+				head.addClass(this.options.classHeadSort);
+				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+				else head.removeClass(this.options.classHeadSortRev);
+			}
+
+			this.body.getElements('td').removeClass(this.options.classCellSort);
+		}
+
+		var parser = this.parsers[index];
+		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
+		if (!parser) return;
+
+		if (!Browser.Engine.trident) {
+			var rel = this.body.getParent();
+			this.body.dispose();
+		}
+
+		var data = Array.map(this.body.rows, function(row, i) {
+			var value = parser.convert.call(document.id(row.cells[index]));
+
+			return {
+				position: i,
+				value: value,
+				toString:  function() {
+					return value.toString();
+				}
+			};
+		}, this);
+		data.reverse(true);
+
+		data.sort(function(a, b){
+			if (a.value === b.value) return 0;
+			return a.value > b.value ? 1 : -1;
+		});
+
+		if (!this.sorted.reverse) data.reverse(true);
+
+		var i = data.length, body = this.body;
+		var j, position, entry, group;
+
+		while (i) {
+			var item = data[--i];
+			position = item.position;
+			var row = body.rows[position];
+			if (row.disabled) continue;
+
+			if (!pre) {
+				if (group === item.value) {
+					row.removeClass(classGroupHead).addClass(classGroup);
+				} else {
+					group = item.value;
+					row.removeClass(classGroup).addClass(classGroupHead);
+				}
+				if (this.zebra) this.zebra(row, i);
+
+				row.cells[index].addClass(classCellSort);
+			}
+
+			body.appendChild(row);
+			for (j = 0; j < i; j++) {
+				if (data[j].position > position) data[j].position--;
+			}
+		};
+		data = null;
+		if (rel) rel.grab(body);
+
+		return this.fireEvent('sort', [body, index]);
+	},
+
+	reSort: function(){
+		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+		return this;
+	},
+
+	enableSort: function(){
+		this.element.addClass(this.options.classSortable);
+		this.attachSorts(true);
+		this.detectParsers();
+		this.sortEnabled = true;
+		return this;
+	},
+
+	disableSort: function(){
+		this.element.removeClass(this.options.classSortable);
+		this.attachSorts(false);
+		this.sortSpans.each(function(span) { span.destroy(); });
+		this.sortSpans.empty();
+		this.sortEnabled = false;
+		return this;
+	}
+
+});
+
+HtmlTable.Parsers = new Hash({
+
+	'date': {
+		match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+		convert: function() {
+			return Date.parse(this.get('text')).format('db');
+		},
+		type: 'date'
+	},
+	'input-checked': {
+		match: / type="(radio|checkbox)" /,
+		convert: function() {
+			return this.getElement('input').checked;
+		}
+	},
+	'input-value': {
+		match: /<input/,
+		convert: function() {
+			return this.getElement('input').value;
+		}
+	},
+	'number': {
+		match: /^\d+[^\d.,]*$/,
+		convert: function() {
+			return this.get('text').toInt();
+		},
+		number: true
+	},
+	'numberLax': {
+		match: /^[^\d]+\d+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^0-9]/, '').toInt();
+		},
+		number: true
+	},
+	'float': {
+		match: /^[\d]+\.[\d]+/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '').toFloat();
+		},
+		number: true
+	},
+	'floatLax': {
+		match: /^[^\d]+[\d]+\.[\d]+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '');
+		},
+		number: true
+	},
+	'string': {
+		match: null,
+		convert: function() {
+			return this.get('text');
+		}
+	},
+	'title': {
+		match: null,
+		convert: function() {
+			return this.title;
+		}
+	}
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+- Perrin Westrich
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- /Log
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+	
+	var Keyboard = this.Keyboard = new Class({
+
+		Extends: Events,
+
+		Implements: [Options, Log],
+
+		options: {
+			/*
+			onActivate: $empty,
+			onDeactivate: $empty,
+			*/
+			defaultEventType: 'keydown',
+			active: false,
+			events: {},
+			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this.setup();
+		}, 
+		setup: function(){
+			this.addEvents(this.options.events);
+			//if this is the root manager, nothing manages it
+			if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
+			if (this.options.active) this.activate();
+		},
+
+		handle: function(event, type){
+			//Keyboard.stop(event) prevents key propagation
+			if (event.preventKeyboardPropagation) return;
+			
+			var bubbles = !!this.manager;
+			if (bubbles && this.activeKB){
+				this.activeKB.handle(event, type);
+				if (event.preventKeyboardPropagation) return;
+			}
+			this.fireEvent(type, event);
+			
+			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
+		},
+
+		addEvent: function(type, fn, internal){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+		},
+
+		removeEvent: function(type, fn){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+		},
+
+		toggleActive: function(){
+			return this[this.active ? 'deactivate' : 'activate']();
+		},
+
+		activate: function(instance){
+			if (instance) {
+				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
+				if (instance != this.activeKB) this.previous = this.activeKB;
+				//if we're enabling a child, assign it so that events are now passed to it
+				this.activeKB = instance.fireEvent('activate');
+				Keyboard.manager.fireEvent('changed');
+			} else if (this.manager) {
+				//else we're enabling ourselves, we must ask our parent to do it for us
+				this.manager.activate(this);
+			}
+			return this;
+		},
+
+		deactivate: function(instance){
+			if (instance) {
+				if(instance === this.activeKB) {
+					this.activeKB = null;
+					instance.fireEvent('deactivate');
+					Keyboard.manager.fireEvent('changed');
+				}
+			}
+			else if (this.manager) {
+				this.manager.deactivate(this);
+			}
+			return this;
+		},
+
+		relenquish: function(){
+			if (this.previous) this.activate(this.previous);
+		},
+
+		//management logic
+		manage: function(instance){
+			if (instance.manager) instance.manager.drop(instance);
+			this.instances.push(instance);
+			instance.manager = this;
+			if (!this.activeKB) this.activate(instance);
+			else this._disable(instance);
+		},
+
+		_disable: function(instance){
+			if (this.activeKB == instance) this.activeKB = null;
+		},
+
+		drop: function(instance){
+			this._disable(instance);
+			this.instances.erase(instance);
+		},
+
+		instances: [],
+
+		trace: function(){
+			Keyboard.trace(this);
+		},
+
+		each: function(fn){
+			Keyboard.each(this, fn);
+		}
+
+	});
+	
+	var parsed = {};
+	var modifiers = ['shift', 'control', 'alt', 'meta'];
+	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+	
+	Keyboard.parse = function(type, eventType, ignore){
+		if (ignore && ignore.contains(type.toLowerCase())) return type;
+		
+		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+			eventType = $1;
+			return '';
+		});
+
+		if (!parsed[type]){
+			var key, mods = {};
+			type.split('+').each(function(part){
+				if (regex.test(part)) mods[part] = true;
+				else key = part;
+			});
+
+			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+			
+			var keys = [];
+			modifiers.each(function(mod){
+				if (mods[mod]) keys.push(mod);
+			});
+			
+			if (key) keys.push(key);
+			parsed[type] = keys.join('+');
+		}
+
+		return eventType + ':' + parsed[type];
+	};
+
+	Keyboard.each = function(keyboard, fn){
+		var current = keyboard || Keyboard.manager;
+		while (current){
+			fn.run(current);
+			current = current.activeKB;
+		}
+	};
+
+	Keyboard.stop = function(event){
+		event.preventKeyboardPropagation = true;
+	};
+
+	Keyboard.manager = new Keyboard({
+		active: true
+	});
+	
+	Keyboard.trace = function(keyboard){
+		keyboard = keyboard || Keyboard.manager;
+		keyboard.enableLog();
+		keyboard.log('the following items have focus: ');
+		Keyboard.each(keyboard, function(current){
+			keyboard.log(document.id(current.widget) || current.wiget || current);
+		});
+	};
+	
+	var handler = function(event){
+		var keys = [];
+		modifiers.each(function(mod){
+			if (event[mod]) keys.push(mod);
+		});
+		
+		if (!regex.test(event.key)) keys.push(event.key);
+		Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
+	};
+	
+	document.addEvents({
+		'keyup': handler,
+		'keydown': handler
+	});
+
+	Event.Keys.extend({
+		'shift': 16,
+		'control': 17,
+		'alt': 18,
+		'capslock': 20,
+		'pageup': 33,
+		'pagedown': 34,
+		'end': 35,
+		'home': 36,
+		'numlock': 144,
+		'scrolllock': 145,
+		';': 186,
+		'=': 187,
+		',': 188,
+		'-': Browser.Engine.Gecko ? 109 : 189,
+		'.': 190,
+		'/': 191,
+		'`': 192,
+		'[': 219,
+		'\\': 220,
+		']': 221,
+		"'": 222
+	});
+
+})();
+/*
+---
+
+script: HtmlTable.Select.js
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- /Keyboard
+- /HtmlTable
+- /Class.refactor
+- /Element.Delegation
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		/*onRowFocus: $empty,
+		onRowUnfocus: $empty,*/
+		useKeyboard: true,
+		classRowSelected: 'table-tr-selected',
+		classRowHovered: 'table-tr-hovered',
+		classSelectable: 'table-selectable',
+		allowMultiSelect: true,
+		selectable: false
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.selectedRows = new Elements();
+		this.bound = {
+			mouseleave: this.mouseleave.bind(this),
+			focusRow: this.focusRow.bind(this)
+		};
+		if (this.options.selectable) this.enableSelect();
+	},
+
+	enableSelect: function(){
+		this.selectEnabled = true;
+		this.attachSelects();
+		this.element.addClass(this.options.classSelectable);
+	},
+
+	disableSelect: function(){
+		this.selectEnabled = false;
+		this.attach(false);
+		this.element.removeClass(this.options.classSelectable);
+	},
+
+	attachSelects: function(attach){
+		attach = $pick(attach, true);
+		var method = attach ? 'addEvents' : 'removeEvents';
+		this.element[method]({
+			mouseleave: this.bound.mouseleave
+		});
+		this.body[method]({
+			'click:relay(tr)': this.bound.focusRow
+		});
+		if (this.options.useKeyboard || this.keyboard){
+			if (!this.keyboard) this.keyboard = new Keyboard({
+				events: {
+					down: function(e) {
+						e.preventDefault();
+						this.shiftFocus(1);
+					}.bind(this),
+					up: function(e) {
+						e.preventDefault();
+						this.shiftFocus(-1);
+					}.bind(this),
+					enter: function(e) {
+						e.preventDefault();
+						if (this.hover) this.focusRow(this.hover);
+					}.bind(this)
+				},
+				active: true
+			});
+			this.keyboard[attach ? 'activate' : 'deactivate']();
+		}
+		this.updateSelects();
+	},
+
+	mouseleave: function(){
+		if (this.hover) this.leaveRow(this.hover);
+	},
+
+	focus: function(){
+		if (this.keyboard) this.keyboard.activate();
+	},
+
+	blur: function(){
+		if (this.keyboard) this.keyboard.deactivate();
+	},
+
+	push: function(){
+		var ret = this.previous.apply(this, arguments);
+		this.updateSelects();
+		return ret;
+	},
+
+	updateSelects: function(){
+		Array.each(this.body.rows, function(row){
+			var binders = row.retrieve('binders');
+			if ((binders && this.selectEnabled) || (!binders && !this.selectEnabled)) return;
+			if (!binders){
+				binders = {
+					mouseenter: this.enterRow.bind(this, [row]),
+					mouseleave: this.leaveRow.bind(this, [row])
+				};
+				row.store('binders', binders).addEvents(binders);
+			} else {
+				row.removeEvents(binders);
+			}
+		}, this);
+	},
+
+	enterRow: function(row){
+		if (this.hover) this.hover = this.leaveRow(this.hover);
+		this.hover = row.addClass(this.options.classRowHovered);
+	},
+
+	shiftFocus: function(offset){
+		if (!this.hover) return this.enterRow(this.body.rows[0]);
+		var to = Array.indexOf(this.body.rows, this.hover) + offset;
+		if (to < 0) to = 0;
+		if (to >= this.body.rows.length) to = this.body.rows.length - 1;
+		if (this.hover == this.body.rows[to]) return this;
+		this.enterRow(this.body.rows[to]);
+	},
+
+	leaveRow: function(row){
+		row.removeClass(this.options.classRowHovered);
+	},
+
+	focusRow: function(){
+		var row = arguments[1] || arguments[0]; //delegation passes the event first
+		if (!this.body.getChildren().contains(row)) return;
+		var unfocus = function(row){
+			this.selectedRows.erase(row);
+			row.removeClass(this.options.classRowSelected);
+			this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+		}.bind(this);
+		if (!this.options.allowMultiSelect) this.selectedRows.each(unfocus);
+		if (!this.selectedRows.contains(row)) {
+			this.selectedRows.push(row);
+			row.addClass(this.options.classRowSelected);
+			this.fireEvent('rowFocus', [row, this.selectedRows]);
+		} else {
+			unfocus(row);
+		}
+		return false;
+	},
+
+	selectAll: function(status){
+		status = $pick(status, true);
+		if (!this.options.allowMultiSelect && status) return;
+		if (!status) this.selectedRows.removeClass(this.options.classRowSelected).empty();
+		else this.selectedRows.combine(this.body.rows).addClass(this.options.classRowSelected);
+		return this;
+	},
+
+	selectNone: function(){
+		return this.selectAll(false);
+	}
+
+});
+/*
+---
+
+script: Keyboard.js
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+- Perrin Westrich
+
+requires:
+- core:1.2.4/Function
+- /Keyboard.Extras
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+	/*
+		shortcut should be in the format of:
+		{
+			'keys': 'shift+s', // the default to add as an event.
+			'description': 'blah blah blah', // a brief description of the functionality.
+			'handler': function(){} // the event handler to run when keys are pressed.
+		}
+	*/
+	addShortcut: function(name, shortcut) {
+		this.shortcuts = this.shortcuts || [];
+		this.shortcutIndex = this.shortcutIndex || {};
+		
+		shortcut.getKeyboard = $lambda(this);
+		shortcut.name = name;
+		this.shortcutIndex[name] = shortcut;
+		this.shortcuts.push(shortcut);
+		if(shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+		return this;
+	},
+
+	addShortcuts: function(obj){
+		for(var name in obj) this.addShortcut(name, obj[name]);
+		return this;
+	},
+
+	getShortcuts: function(){
+		return this.shortcuts || [];
+	},
+
+	getShortcut: function(name){
+		return (this.shortcutIndex || {})[name];
+	}
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+	$splat(shortcuts).each(function(shortcut){
+		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+		shortcut.keys = newKeys;
+		shortcut.getKeyboard().fireEvent('rebound');
+	});
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard) {
+	var activeKBS = [], activeSCS = [];
+	Keyboard.each(keyboard, [].push.bind(activeKBS));
+	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+	return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+	opts = opts || {};
+	var shortcuts = opts.many ? [] : null,
+		set = opts.many ? function(kb){
+				var shortcut = kb.getShortcut(name);
+				if(shortcut) shortcuts.push(shortcut);
+			} : function(kb) { 
+				if(!shortcuts) shortcuts = kb.getShortcut(name);
+			};
+	Keyboard.each(keyboard, set);
+	return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard) {
+	return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+/*
+---
+
+script: Scroller.js
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		area: 20,
+		velocity: 1,
+		onChange: function(x, y){
+			this.element.scrollTo(x, y);
+		},
+		fps: 50
+	},
+
+	initialize: function(element, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.docBody = document.id(this.element.getDocument().body);
+		this.listener = ($type(this.element) != 'element') ?  this.docBody : this.element;
+		this.timer = null;
+		this.bound = {
+			attach: this.attach.bind(this),
+			detach: this.detach.bind(this),
+			getCoords: this.getCoords.bind(this)
+		};
+	},
+
+	start: function(){
+		this.listener.addEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+	},
+
+	stop: function(){
+		this.listener.removeEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+		this.detach();
+		this.timer = $clear(this.timer);
+	},
+
+	attach: function(){
+		this.listener.addEvent('mousemove', this.bound.getCoords);
+	},
+
+	detach: function(){
+		this.listener.removeEvent('mousemove', this.bound.getCoords);
+		this.timer = $clear(this.timer);
+	},
+
+	getCoords: function(event){
+		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+	},
+
+	scroll: function(){
+		var size = this.element.getSize(), 
+			scroll = this.element.getScroll(), 
+			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0}, 
+			scrollSize = this.element.getScrollSize(), 
+			change = {x: 0, y: 0};
+		for (var z in this.page){
+			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0) {
+				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
+			} else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
+				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
+			}
+		}
+		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+	}
+
+});/*
+---
+
+script: Tips.js
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		/*
+		onAttach: $empty(element),
+		onDetach: $empty(element),
+		*/
+		onShow: function(){
+			this.tip.setStyle('display', 'block');
+		},
+		onHide: function(){
+			this.tip.setStyle('display', 'none');
+		},
+		title: 'title',
+		text: function(element){
+			return element.get('rel') || element.get('href');
+		},
+		showDelay: 100,
+		hideDelay: 100,
+		className: 'tip-wrap',
+		offset: {x: 16, y: 16},
+		windowPadding: {x:0, y:0},
+		fixed: false
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, elements: $defined});
+		this.setOptions(params.options);
+		if (params.elements) this.attach(params.elements);
+		this.container = new Element('div', {'class': 'tip'});
+	},
+
+	toElement: function(){
+		if (this.tip) return this.tip;
+
+		return this.tip = new Element('div', {
+			'class': this.options.className,
+			styles: {
+				position: 'absolute',
+				top: 0,
+				left: 0
+			}
+		}).adopt(
+			new Element('div', {'class': 'tip-top'}),
+			this.container,
+			new Element('div', {'class': 'tip-bottom'})
+		).inject(document.body);
+	},
+
+	attach: function(elements){
+		$$(elements).each(function(element){
+			var title = read(this.options.title, element),
+				text = read(this.options.text, element);
+			
+			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
+			element.retrieve('tip:text', text);
+			this.fireEvent('attach', [element]);
+			
+			var events = ['enter', 'leave'];
+			if (!this.options.fixed) events.push('move');
+			
+			events.each(function(value){
+				var event = element.retrieve('tip:' + value);
+				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
+				
+				element.store('tip:' + value, event).addEvent('mouse' + value, event);
+			}, this);
+		}, this);
+		
+		return this;
+	},
+
+	detach: function(elements){
+		$$(elements).each(function(element){
+			['enter', 'leave', 'move'].each(function(value){
+				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+			});
+			
+			this.fireEvent('detach', [element]);
+			
+			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+				var original = element.retrieve('tip:native');
+				if (original) element.set('title', original);
+			}
+		}, this);
+		
+		return this;
+	},
+
+	elementEnter: function(event, element){
+		this.container.empty();
+		
+		['title', 'text'].each(function(value){
+			var content = element.retrieve('tip:' + value);
+			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
+		}, this);
+		
+		$clear(this.timer);
+		this.timer = (function(){
+			this.show(this, element);
+			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+		}).delay(this.options.showDelay, this);
+	},
+
+	elementLeave: function(event, element){
+		$clear(this.timer);
+		this.timer = this.hide.delay(this.options.hideDelay, this, element);
+		this.fireForParent(event, element);
+	},
+
+	fireForParent: function(event, element){
+		element = element.getParent();
+		if (!element || element == document.body) return;
+		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+		else this.fireForParent(event, element);
+	},
+
+	elementMove: function(event, element){
+		this.position(event);
+	},
+
+	position: function(event){
+		if (!this.tip) document.id(this);
+
+		var size = window.getSize(), scroll = window.getScroll(),
+			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+			props = {x: 'left', y: 'top'},
+			obj = {};
+		
+		for (var z in props){
+			obj[props[z]] = event.page[z] + this.options.offset[z];
+			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+		}
+		
+		this.tip.setStyles(obj);
+	},
+
+	fill: function(element, contents){
+		if(typeof contents == 'string') element.set('html', contents);
+		else element.adopt(contents);
+	},
+
+	show: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('show', [this.tip, element]);
+	},
+
+	hide: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('hide', [this.tip, element]);
+	}
+
+});
+
+})();/*
+---
+
+script: Date.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+- Alfons Sanchez
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Date', {
+
+	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'fa menys d`un minut',
+	minuteAgo: 'fa un minut',
+	minutesAgo: 'fa {delta} minuts',
+	hourAgo: 'fa un hora',
+	hoursAgo: 'fa unes {delta} hores',
+	dayAgo: 'fa un dia',
+	daysAgo: 'fa {delta} dies',
+	lessThanMinuteUntil: 'menys d`un minut des d`ara',
+	minuteUntil: 'un minut des d`ara',
+	minutesUntil: '{delta} minuts des d`ara',
+	hourUntil: 'un hora des d`ara',
+	hoursUntil: 'unes {delta} hores des d`ara',
+	dayUntil: '1 dia des d`ara',
+	daysUntil: '{delta} dies des d`ara'
+
+});/*
+---
+
+script: Date.Czech.js
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+- Jan Černý chemiX
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Date', {
+
+	months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+	days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+	AM: 'dop.',
+	PM: 'odp.',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return '.';
+	},
+
+    // TODO : in examples use and fix it
+	lessThanMinuteAgo: 'méně než minutou',
+	minuteAgo: 'přibližně před minutou',
+	minutesAgo: 'před {delta} minutami',
+	hourAgo: 'přibližně před hodinou',
+	hoursAgo: 'před {delta} hodinami',
+	dayAgo: 'před dnem',
+	daysAgo: 'před {delta} dni',
+	lessThanMinuteUntil: 'před méně než minutou',
+	minuteUntil: 'asi před minutou',
+	minutesUntil: ' asi před {delta} minutami',
+	hourUntil: 'asi před hodinou',
+	hoursUntil: 'před {delta} hodinami',
+	dayUntil: 'před dnem',
+	daysUntil: 'před {delta} dni',
+	weekUntil: 'před týdnem',
+	weeksUntil: 'před {delta} týdny',
+	monthUntil: 'před měsícem',
+	monthsUntil: 'před {delta} měsíci',
+	yearUntil: 'před rokem',
+	yearsUntil: 'před {delta} lety'
+
+});
+/*
+---
+
+script: Date.Danish.js
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+- Martin Overgaard
+- Henrik Hansen
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Danish]
+
+...
+*/
+ 
+MooTools.lang.set('da-DK', 'Date', {
+
+	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d-%m-%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+	  //1st, 2nd, 3rd, etc.
+	  return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mindre end et minut siden',
+	minuteAgo: 'omkring et minut siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omkring en time siden',
+	hoursAgo: 'omkring {delta} timer siden',
+	dayAgo: '1 dag siden',
+	daysAgo: '{delta} dage siden',
+	weekAgo: '1 uge siden',
+	weeksAgo: '{delta} uger siden',
+	monthAgo: '1 måned siden',
+	monthsAgo: '{delta} måneder siden',
+	yearthAgo: '1 år siden',
+	yearsAgo: '{delta} år siden',
+	lessThanMinuteUntil: 'mindre end et minut fra nu',
+	minuteUntil: 'omkring et minut fra nu',
+	minutesUntil: '{delta} minutter fra nu',
+	hourUntil: 'omkring en time fra nu',
+	hoursUntil: 'omkring {delta} timer fra nu',
+	dayUntil: '1 dag fra nu',
+	daysUntil: '{delta} dage fra nu',
+	weekUntil: '1 uge fra nu',
+	weeksUntil: '{delta} uger fra nu',
+	monthUntil: '1 måned fra nu',
+	monthsUntil: '{delta} måneder fra nu',
+	yearUntil: '1 år fra nu',
+	yearsUntil: '{delta} år fra nu'
+
+});
+/*
+---
+
+script: Date.Dutch.js
+
+description: Date messages in Dutch.
+
+license: MIT-style license
+
+authors:
+- Lennart Pilon
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Date', {
+
+	months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
+	days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: 'e',
+
+	lessThanMinuteAgo: 'minder dan een minuut geleden',
+	minuteAgo: 'ongeveer een minuut geleden',
+	minutesAgo: 'minuten geleden',
+	hourAgo: 'ongeveer een uur geleden',
+	hoursAgo: 'ongeveer {delta} uur geleden',
+	dayAgo: '{delta} dag geleden',
+	daysAgo: 'dagen geleden',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+	lessThanMinuteUntil: 'minder dan een minuut vanaf nu',
+	minuteUntil: 'ongeveer een minuut vanaf nu',
+	minutesUntil: '{delta} minuten vanaf nu',
+	hourUntil: 'ongeveer een uur vanaf nu',
+	hoursUntil: 'ongeveer {delta} uur vanaf nu',
+	dayUntil: '1 dag vanaf nu',
+	daysUntil: '{delta} dagen vanaf nu',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearthAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+
+	weekUntil: 'over een week',
+	weeksUntil: 'over {delta} weken',
+	monthUntil: 'over een maand',
+	monthsUntil: 'over {delta} maanden',
+	yearUntil: 'over een jaar',
+	yearsUntil: 'over {delta} jaar' 
+
+});/*
+---
+
+script: Date.English.GB.js
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.English.GB]
+
+...
+*/
+
+MooTools.lang.set('en-GB', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+	
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M'
+
+}).set('cascade', ['en-US']);/*
+---
+
+script: Date.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+- Kevin Valdek
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Date', {
+
+	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],
+	//culture's date order: MM.DD.YYYY
+	dateOrder: ['month', 'date', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%m.%d.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
+	minuteAgo: 'umbes minut aega tagasi',
+	minutesAgo: '{delta} minutit tagasi',
+	hourAgo: 'umbes tund aega tagasi',
+	hoursAgo: 'umbes {delta} tundi tagasi',
+	dayAgo: '1 päev tagasi',
+	daysAgo: '{delta} päeva tagasi',
+	weekAgo: '1 nädal tagasi',
+	weeksAgo: '{delta} nädalat tagasi',
+	monthAgo: '1 kuu tagasi',
+	monthsAgo: '{delta} kuud tagasi',
+	yearAgo: '1 aasta tagasi',
+	yearsAgo: '{delta} aastat tagasi',
+	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
+	minuteUntil: 'umbes minuti aja pärast',
+	minutesUntil: '{delta} minuti pärast',
+	hourUntil: 'umbes tunni aja pärast',
+	hoursUntil: 'umbes {delta} tunni pärast',
+	dayUntil: '1 päeva pärast',
+	daysUntil: '{delta} päeva pärast',
+	weekUntil: '1 nädala pärast',
+	weeksUntil: '{delta} nädala pärast',
+	monthUntil: '1 kuu pärast',
+	monthsUntil: '{delta} kuu pärast',
+	yearUntil: '1 aasta pärast',
+	yearsUntil: '{delta} aasta pärast'
+
+});/*
+---
+
+script: Date.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+- Frank Rossi
+- Ulrich Petri
+- Fabian Beiner
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Date', {
+
+	months: ['Januar', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+	days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: [ 'date', 'month', 'year', '.'],
+
+	AM: 'vormittags',
+	PM: 'nachmittags',
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '.',
+
+	lessThanMinuteAgo: 'Vor weniger als einer Minute',
+	minuteAgo: 'Vor einer Minute',
+	minutesAgo: 'Vor {delta} Minuten',
+	hourAgo: 'Vor einer Stunde',
+	hoursAgo: 'Vor {delta} Stunden',
+	dayAgo: 'Vor einem Tag',
+	daysAgo: 'Vor {delta} Tagen',
+	weekAgo: 'Vor einer Woche',
+	weeksAgo: 'Vor {delta} Wochen',
+	monthAgo: 'Vor einem Monat',
+	monthsAgo: 'Vor {delta} Monaten',
+	yearAgo: 'Vor einem Jahr',
+	yearsAgo: 'Vor {delta} Jahren',
+	lessThanMinuteUntil: 'In weniger als einer Minute',
+	minuteUntil: 'In einer Minute',
+	minutesUntil: 'In {delta} Minuten',
+	hourUntil: 'In ca. einer Stunde',
+	hoursUntil: 'In ca. {delta} Stunden',
+	dayUntil: 'In einem Tag',
+	daysUntil: 'In {delta} Tagen',
+	weekUntil: 'In einer Woche',
+	weeksUntil: 'In {delta} Wochen',
+	monthUntil: 'In einem Monat',
+	monthsUntil: 'In {delta} Monaten',
+	yearUntil: 'In einem Jahr',
+	yearsUntil: 'In {delta} Jahren'
+});/*
+---
+
+script: Date.German.CH.js
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors: 
+- Michael van der Weg
+
+requires:
+- /Lang
+- /Date.German
+
+provides: [Date.German.CH]
+
+...
+*/
+
+MooTools.lang.set('de-CH', 'cascade', ['de-DE']);/*
+---
+
+script: Date.French.js
+
+description: Date messages in French.
+
+license: MIT-style license
+
+authors:
+- Nicolas Sorosac
+- Antoine Abt
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Date', {
+
+	months: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
+	days: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	getOrdinal: function(dayOfMonth){
+	  return (dayOfMonth > 1) ? '' : 'er';
+	},
+
+	lessThanMinuteAgo: 'il y a moins d\'une minute',
+	minuteAgo: 'il y a une minute',
+	minutesAgo: 'il y a {delta} minutes',
+	hourAgo: 'il y a une heure',
+	hoursAgo: 'il y a {delta} heures',
+	dayAgo: 'il y a un jour',
+	daysAgo: 'il y a {delta} jours',
+	weekAgo: 'il y a une semaine',
+	weeksAgo: 'il y a {delta} semaines',
+	monthAgo: 'il y a 1 mois',
+	monthsAgo: 'il y a {delta} mois',
+	yearthAgo: 'il y a 1 an',
+	yearsAgo: 'il y a {delta} ans',
+	lessThanMinuteUntil: 'dans moins d\'une minute',
+	minuteUntil: 'dans une minute',
+	minutesUntil: 'dans {delta} minutes',
+	hourUntil: 'dans une heure',
+	hoursUntil: 'dans {delta} heures',
+	dayUntil: 'dans un jour',
+	daysUntil: 'dans {delta} jours',
+	weekUntil: 'dans 1 semaine',
+	weeksUntil: 'dans {delta} semaines',
+	monthUntil: 'dans 1 mois',
+	monthsUntil: 'dans {delta} mois',
+	yearUntil: 'dans 1 an',
+	yearsUntil: 'dans {delta} ans'
+
+});
+/*
+---
+
+script: Date.Italian.js
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+- Andrea Novero
+- Valerio Proietti
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Date', {
+ 
+	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H.%M',
+
+	/* Date.Extras */
+	ordinal: '&ordm;',
+
+	lessThanMinuteAgo: 'meno di un minuto fa',
+	minuteAgo: 'circa un minuto fa',
+	minutesAgo: 'circa {delta} minuti fa',
+	hourAgo: 'circa un\'ora fa',
+	hoursAgo: 'circa {delta} ore fa',
+	dayAgo: 'circa 1 giorno fa',
+	daysAgo: 'circa {delta} giorni fa',
+	lessThanMinuteUntil: 'tra meno di un minuto',
+	minuteUntil: 'tra circa un minuto',
+	minutesUntil: 'tra circa {delta} minuti',
+	hourUntil: 'tra circa un\'ora',
+	hoursUntil: 'tra circa {delta} ore',
+	dayUntil: 'tra circa un giorno',
+	daysUntil: 'tra circa {delta} giorni'
+
+});/*
+---
+
+script: Date.Norwegian.js
+
+description: Date messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+- Espen 'Rexxars' Hovlandsdal
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	lessThanMinuteAgo: 'kortere enn et minutt siden',
+	minuteAgo: 'omtrent et minutt siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omtrent en time siden',
+	hoursAgo: 'omtrent {delta} timer siden',
+	dayAgo: '{delta} dag siden',
+	daysAgo: '{delta} dager siden'
+
+});/*
+---
+
+script: Date.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+- Oskar Krawczyk
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Date', {
+	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+	dateOrder: ['year', 'month', 'date'],
+	AM: 'nad ranem',
+	PM: 'po południu',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mniej niż minute temu',
+	minuteAgo: 'około minutę temu',
+	minutesAgo: '{delta} minut temu',
+	hourAgo: 'około godzinę temu',
+	hoursAgo: 'około {delta} godzin temu',
+	dayAgo: 'Wczoraj',
+	daysAgo: '{delta} dni temu',
+	lessThanMinuteUntil: 'za niecałą minutę',
+	minuteUntil: 'za około minutę',
+	minutesUntil: 'za {delta} minut',
+	hourUntil: 'za około godzinę',
+	hoursUntil: 'za około {delta} godzin',
+	dayUntil: 'za 1 dzień',
+	daysUntil: 'za {delta} dni'
+});/*
+---
+
+script: Date.Portuguese.BR.js
+
+description: Date messages in Portuguese-BR (Brazil).
+
+license: MIT-style license
+
+authors:
+- Fabio Miranda Costa
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Date', {
+
+	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1º, 2º, 3º, etc.
+    	return '&ordm;';
+	},
+
+	lessThanMinuteAgo: 'há menos de um minuto',
+	minuteAgo: 'há cerca de um minuto',
+	minutesAgo: 'há {delta} minutos',
+	hourAgo: 'há cerca de uma hora',
+	hoursAgo: 'há cerca de {delta} horas',
+	dayAgo: 'há um dia',
+	daysAgo: 'há {delta} dias',
+    weekAgo: 'há uma semana',
+	weeksAgo: 'há {delta} semanas',
+	monthAgo: 'há um mês',
+	monthsAgo: 'há {delta} meses',
+	yearAgo: 'há um ano',
+	yearsAgo: 'há {delta} anos',
+	lessThanMinuteUntil: 'em menos de um minuto',
+	minuteUntil: 'em um minuto',
+	minutesUntil: 'em {delta} minutos',
+	hourUntil: 'em uma hora',
+	hoursUntil: 'em {delta} horas',
+	dayUntil: 'em um dia',
+	daysUntil: 'em {delta} dias',
+	weekUntil: 'em uma semana',
+	weeksUntil: 'em {delta} semanas',
+	monthUntil: 'em um mês',
+	monthsUntil: 'em {delta} meses',
+	yearUntil: 'em um ano',
+	yearsUntil: 'em {delta} anos'
+
+});/*
+Script: Date.Russian.js
+	Date messages for Russian.
+
+	License:
+		MIT-style license.
+
+	Authors:
+		Evstigneev Pavel
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Date', {
+
+	months: ['Январь', 'Февраль', 'Март', 'Ð?прель', 'Май', 'Июнь', 'Июль', 'Ð?вгуÑ?Ñ‚', 'СентÑ?брь', 'ОктÑ?брь', 'Ð?оÑ?брь', 'Декабрь'],
+	days: ['ВоÑ?креÑ?енье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'ПÑ?тница', 'Суббота'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+
+  /*
+   *  Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+   *
+   *  one -> n mod 10 is 1 and n mod 100 is not 11;
+   *  few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+   *  many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+   *  other -> everything else (example 3.14)
+   */
+
+  pluralize: function (n, one, few, many, other) {
+    var modulo10 = n % 10
+    var modulo100 = n % 100
+
+    if (modulo10 == 1 && modulo100 != 11) {
+      return one;
+    } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return few;
+    } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return many;
+    } else {
+      return other;
+    }
+  },
+
+	/* Date.Extras */
+	ordinal: '',
+	lessThanMinuteAgo: 'меньше минуты назад',
+	minuteAgo: 'минута назад',
+	minutesAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'минута', 'минуты', 'минут') + ' назад'},
+	hourAgo: 'чаÑ? назад',
+	hoursAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ' назад'},
+	dayAgo: 'вчера',
+	daysAgo: function (delta) { return '{delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + ' назад' },
+	lessThanMinuteUntil: 'меньше минуты назад',
+	minuteUntil: 'через минуту',
+	minutesUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	hourUntil: 'через чаÑ?',
+	hoursUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	dayUntil: 'завтра',
+	daysUntil: function (delta) { return 'через {delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + '' }
+
+});/*
+---
+
+script: Date.Spanish.US.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+- Ãlfons Sanchez
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Date', {
+
+	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+	days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'hace menos de un minuto',
+	minuteAgo: 'hace un minuto',
+	minutesAgo: 'hace {delta} minutos',
+	hourAgo: 'hace una hora',
+	hoursAgo: 'hace unas {delta} horas',
+	dayAgo: 'hace un día',
+	daysAgo: 'hace {delta} días',
+	weekAgo: 'hace una semana',
+	weeksAgo: 'hace unas {delta} semanas',
+	monthAgo: 'hace un mes',
+	monthsAgo: 'hace {delta} meses',
+	yearAgo: 'hace un año',
+	yearsAgo: 'hace {delta} años',
+	lessThanMinuteUntil: 'menos de un minuto desde ahora',
+	minuteUntil: 'un minuto desde ahora',
+	minutesUntil: '{delta} minutos desde ahora',
+	hourUntil: 'una hora desde ahora',
+	hoursUntil: 'unas {delta} horas desde ahora',
+	dayUntil: 'un día desde ahora',
+	daysUntil: '{delta} días desde ahora',
+	weekUntil: 'una semana desde ahora',
+	weeksUntil: 'unas {delta} semanas desde ahora',
+	monthUntil: 'un mes desde ahora',
+	monthsUntil: '{delta} meses desde ahora',
+	yearUntil: 'un año desde ahora',
+	yearsUntil: '{delta} años desde ahora'
+
+});/*
+---
+
+script: Date.Swedish.js
+
+description: Date messages for Swedish (SE).
+
+license: MIT-style license
+
+authors:
+- Martin Lundgren
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Date', {
+
+	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+	// culture's date order: YYYY-MM-DD
+	dateOrder: ['year', 'month', 'date'],
+	AM: '',
+	PM: '',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		// Not used in Swedish
+		return '';
+	},
+
+	lessThanMinuteAgo: 'mindre än en minut sedan',
+	minuteAgo: 'ungefär en minut sedan',
+	minutesAgo: '{delta} minuter sedan',
+	hourAgo: 'ungefär en timme sedan',
+	hoursAgo: 'ungefär {delta} timmar sedan',
+	dayAgo: '1 dag sedan',
+	daysAgo: '{delta} dagar sedan',
+	lessThanMinuteUntil: 'mindre än en minut sedan',
+	minuteUntil: 'ungefär en minut sedan',
+	minutesUntil: '{delta} minuter sedan',
+	hourUntil: 'ungefär en timme sedan',
+	hoursUntil: 'ungefär {delta} timmar sedan',
+	dayUntil: '1 dag sedan',
+	daysUntil: '{delta} dagar sedan'
+
+});/*
+---
+
+script: Date.Ukrainian.js
+
+description: Date messages for Ukrainian.
+
+license: MIT-style license
+
+authors:
+- Slik
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Ukrainian]
+
+...
+*/
+
+(function(){
+	var pluralize = function(n, one, few, many, other){
+		var d = (n / 10).toInt();
+		var z = n % 10;
+		var s = (n / 100).toInt();
+
+		if(d == 1 && n > 10) return many;
+		if(z == 1) return one;
+		if(z > 0 && z < 5) return few;
+		return many;
+	};
+
+	MooTools.lang.set('uk-UA', 'Date', {
+			months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'ВереÑ?ень', 'Жовтень', 'ЛиÑ?топад', 'Грудень'],
+			days: ['Ð?еділÑ?', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'Ñ?тницÑ?', 'Субота'],
+			//culture's date order: DD/MM/YYYY
+			dateOrder: ['date', 'month', 'year'],
+			AM: 'до полуднÑ?',
+			PM: 'по полудню',
+
+			shortDate: '%d/%m/%Y',
+			shortTime: '%H:%M',
+
+			/* Date.Extras */
+			ordinal: '',
+			lessThanMinuteAgo: 'меньше хвилини тому',
+			minuteAgo: 'хвилину тому',
+			minutesAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому';
+			},
+			hourAgo: 'годину тому',
+			hoursAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому';
+			},
+			dayAgo: 'вчора',
+			daysAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів') + ' тому';
+			},
+			weekAgo: 'тиждень тому',
+			weeksAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому';
+			},
+			monthAgo: 'міÑ?Ñ?ць тому',
+			monthsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців') + ' тому';
+			},
+			yearAgo: 'рік тому',
+			yearsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому';
+			},
+			lessThanMinuteUntil: 'за мить',
+			minuteUntil: 'через хвилину',
+			minutesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин');
+			},
+			hourUntil: 'через годину',
+			hoursUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин');
+			},
+			dayUntil: 'завтра',
+			daysUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів');
+			},
+			weekUntil: 'через тиждень',
+			weeksUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів');
+			},
+			monthUntil: 'через міÑ?Ñ?ць',
+			monthesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців');
+			},
+			yearUntil: 'через рік',
+			yearsUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років');
+			}
+	});
+
+})();/*
+---
+
+script: Form.Validator.Arabic.js
+
+description: Form.Validator messages in Arabic.
+
+license: MIT-style license
+
+authors:
+- Chafik Barbar
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Arabic]
+
+...
+*/
+
+MooTools.lang.set('ar', 'Form.Validator', {
+	required:'هذا الحقل مطلوب.',
+	minLength:'رجاءً إدخال {minLength}  أحرÙ? على الأقل (تم إدخال {length} أحرÙ?).',
+	maxLength:'الرجاء عدم إدخال أكثر من {maxLength} أحرÙ? (تم إدخال {length} أحرÙ?).',
+	integer:'الرجاء إدخال عدد صحيح Ù?ÙŠ هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
+	numeric:'الرجاء إدخال قيم رقمية Ù?ÙŠ هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+	digits:'الرجاء أستخدام قيم رقمية وعلامات ترقيمية Ù?قط Ù?ÙŠ هذا الحقل (مثال, رقم هاتÙ? مع نقطة أو شحطة)',
+	alpha:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	alphanum:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) أو أرقام (0-9) Ù?قط Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	dateSuchAs:'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+	dateInFormatMDY:'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+	email:'الرجاء إدخال بريد إلكتروني صحيح.',
+	url:'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
+	currencyDollar:'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
+	oneRequired:'الرجاء إدخال قيمة Ù?ÙŠ أحد هذه الحقول على الأقل.',
+	errorPrefix: 'خطأ: ',
+	warningPrefix: 'تحذير: '
+}).set('ar', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+- Miquel Hudin
+- Alfons Sanchez
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Form.Validator', {
+
+	required:'Aquest camp es obligatori.',
+	minLength:'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+	maxLength:'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+	integer:'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+	numeric:'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+	alpha:'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	alphanum:'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	dateSuchAs:'Per favor introdueix una data valida com {date}',
+	dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',
+	url:'Per favor introdueix una URL valida com http://www.google.com.',
+	currencyDollar:'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+	oneRequired:'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Avis: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No poden haver espais en aquesta entrada.',
+	reqChkByNode: 'No hi han elements seleccionats.',
+	requiredChk: 'Aquest camp es obligatori.',
+	reqChkByName: 'Per favor selecciona una {label}.',
+	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+	startDate: 'la data de inici',
+	endDate: 'la data de fi',
+	currendDate: 'la data actual',
+	afterDate: 'La data deu ser igual o posterior a {label}.',
+	beforeDate: 'La data deu ser igual o anterior a {label}.',
+	startMonth: 'Per favor selecciona un mes d´orige',
+	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});/*
+---
+
+script: Form.Validator.Czech.js
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+- Jan Černý chemiX
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Form.Validator', {
+
+	required:'Tato položka je povinná.',
+	minLength:'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+	maxLength:'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).',
+	integer:'Zadejte prosím celé Ä?íslo. Desetinná Ä?ísla (napÅ™. 1.25) nejsou povolena.',
+	numeric:'Zadejte jen Ä?íselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+	digits:'Zadejte prosím pouze Ä?ísla a interpunkÄ?ní znaménka(například telefonní Ä?íslo s pomlÄ?kami nebo teÄ?kami je povoleno).',
+	alpha:'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+	alphanum:'Zadejte prosím pouze písmena (a-z) nebo Ä?íslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+	dateSuchAs:'Zadejte prosím platné datum jako {date}',
+	dateInFormatMDY:'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+	email:'Zadejte prosím platnou e-mailovou adresu. Například "fred at domain.com".',
+	url:'Zadejte prosím platnou URL adresu jako http://www.google.com.',
+	currencyDollar:'Zadejte prosím platnou Ä?ástku. Například $100.00.',
+	oneRequired:'Zadejte prosím alespoň jednu hodnotu pro tyto položky.',
+	errorPrefix: 'Chyba: ',
+	warningPrefix: 'Upozornění: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'V této položce nejsou povoleny mezery',
+	reqChkByNode: 'Nejsou vybrány žádné položky.',
+	requiredChk: 'Tato položka je vyžadována.',
+	reqChkByName: 'Prosím vyberte {label}.',
+	match: 'Tato položka se musí shodovat s položkou {matchName}',
+	startDate: 'datum zahájení',
+	endDate: 'datum ukonÄ?ení',
+	currendDate: 'aktuální datum',
+	afterDate: 'Datum by mělo být stejné nebo větší než {label}.',
+	beforeDate: 'Datum by mělo být stejné nebo menší než {label}.',
+	startMonth: 'Vyberte poÄ?áteÄ?ní mÄ›síc.',
+	sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.',
+    creditcard: 'Zadané Ä?íslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} Ä?ísel.'
+
+});
+/*
+---
+
+script: Form.Validator.Chinese.js
+
+description: Form.Validator messages in chinese (both simplified and traditional).
+
+license: MIT-style license
+
+authors:
+- 陈桂军 - guidy <at> ixuer [dot] net
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Chinese]
+
+...
+*/
+
+/*
+In Chinese:
+------------
+需�指出的是:
+简体中文适用于中国大陆,
+�体中文适用于香港�澳门和�湾�。
+简体中文和�体中文在字体和语法上有很多的��之处。
+
+我�以确�简体中文语言包的准确性,
+但对于�体中文,我�以��用户�以准确的�解,但无法��语�符�他们的阅读习惯。
+如果您�能确认的�,�以�使用简体中文语言包,因为它是最通用的。
+
+In English:
+------------
+It should be noted that:
+Simplified  Chinese apply to mainland Chinese,
+Traditional Chinese apply to Hong Kong, Macao and Taiwan Province.
+There are a lot of different from Simplified  Chinese and Traditional Chinese , Contains font and syntax .
+
+I can assure Simplified Chinese language pack accuracy .
+For Traditional Chinese, I can only guarantee that users can understand, but not necessarily in line with their reading habits.
+If you are unsure, you can only use the simplified Chinese language pack, as it is the most common.
+
+*/
+
+// Simplified Chinese
+MooTools.lang.set('zhs-CN', 'Form.Validator', {
+	required:'这是必填项。',
+	minLength:'请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
+	maxLength:'最多�能输入 {maxLength} 个字符 (已输入 {length} 个)。',
+	integer:'请输入一个整数,�能包��数点。例如:"1", "200"。',
+	numeric:'请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'这里�能接�数字和标点的输入,标点�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'请输入 A-Z 的 26 个字�,�能包�空格或任何其他字符。',
+	alphanum:'请输入 A-Z 的 26 个字�或 0-9 的 10 个数字,�能包�空格或任何其他字符。',
+	dateSuchAs:'请输入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'请输入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'请输入�法的电�信箱地�,例如:"fred at domain.com"。',
+	url:'请输入�法的 Url 地�,例如:http://www.google.com。',
+	currencyDollar:'请输入�法的货�符�,例如:¥',
+	oneRequired:'请至少选择一项。',
+	errorPrefix: '错误:',
+	warningPrefix: '警告:'
+});
+
+// Traditional Chinese
+MooTools.lang.set('zht-CN', 'Form.Validator', {
+	required:'這是必填項。',
+	minLength:'請至少�入 {minLength} 個字符(已�入 {length} 個)。',
+	maxLength:'最多�能�入 {maxLength} 個字符(已�入 {length} 個)。',
+	integer:'請�入一個整數,�能包��數點。例如:"1", "200"。',
+	numeric:'請�入一個數字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'這裡�能接�數字和標點的�入,標點�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'請�入 A-Z 的 26 個字�,�能包�空格或任何其他字符。',
+	alphanum:'請�入 A-Z 的 26 個字�或 0-9 的 10 個數字,�能包�空格或任何其他字符。',
+	dateSuchAs:'請�入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'請�入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'請�入�法的電�信箱地�,例如:"fred at domain.com"。',
+	url:'請�入�法的 Url 地�,例如:http://www.google.com。',
+	currencyYuan:'請�入�法的貨幣符號,例如:¥',
+	oneRequired:'請至少�擇一項。',
+	errorPrefix: '錯誤:',
+	warningPrefix: '警告:'
+});
+
+Form.Validator.add('validate-currency-yuan', {
+	errorMsg: function(){
+		return Form.Validator.getMsg('currencyYuan');
+	},
+	test: function(element) {
+		// [ï¿¥]1[##][,###]+[.##]
+		// [ï¿¥]1###+[.##]
+		// [ï¿¥]0.##
+		// [ï¿¥].##
+		return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+	}
+});
+/*
+---
+
+script: Form.Validator.Dutch.js
+
+description: Form.Validator messages in Dutch.
+
+license: MIT-style license
+
+authors:
+- Lennart Pilon
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Form.Validator', {
+	required:'Dit veld is verplicht.',
+	minLength:'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+	maxLength:'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+	integer:'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1,25) zijn niet toegestaan.',
+	numeric:'Vul alleen numerieke waarden in (bijvoorbeeld. "1" of "1.1" of "-1" of "-1.1").',
+	digits:'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met een streepje).',
+	alpha:'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+	alphanum:'Vul alleen letters in (a-z) of nummers (0-9). Spaties en andere karakters zijn niet toegestaan.',
+	dateSuchAs:'Vul een geldige datum in, zoals {date}',
+	dateInFormatMDY:'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+	email:'Vul een geldig e-mailadres in. Bijvoorbeeld "fred at domein.nl".',
+	url:'Vul een geldige URL in, zoals http://www.google.nl.',
+	currencyDollar:'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+	oneRequired:'Vul iets in bij minimaal een van de invoervelden.',
+	warningPrefix: 'Waarschuwing: ',
+	errorPrefix: 'Fout: '
+});/*
+---
+
+script: Form.Validator.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+- Kevin Valdek
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Form.Validator', {
+
+	required:'Väli peab olema täidetud.',
+	minLength:'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
+	maxLength:'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
+	integer:'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
+	numeric:'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
+	digits:'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+	alpha:'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
+	alphanum:'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
+	dateSuchAs:'Palun sisestage kehtiv kuupäev kujul {date}',
+	dateInFormatMDY:'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
+	email:'Palun sisestage kehtiv e-maili aadress (näiteks: "fred at domain.com").',
+	url:'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
+	currencyDollar:'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
+	oneRequired:'Palun sisestage midagi vähemalt ühele antud väljadest.',
+	errorPrefix: 'Viga: ',
+	warningPrefix: 'Hoiatus: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Väli ei tohi sisaldada tühikuid.',
+	reqChkByNode: 'Ükski väljadest pole valitud.',
+	requiredChk: 'Välja täitmine on vajalik.',
+	reqChkByName: 'Palun valige üks {label}.',
+	match: 'Väli peab sobima {matchName} väljaga',
+	startDate: 'algkuupäev',
+	endDate: 'lõppkuupäev',
+	currendDate: 'praegune kuupäev',
+	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
+	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
+	startMonth: 'Palun valige algkuupäev.',
+	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'
+
+});/*
+---
+
+script: Form.Validator.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors: 
+- Frank Rossi
+- Ulrich Petri
+- Fabian Beiner
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Form.Validator', {
+
+	required: 'Dieses Eingabefeld muss ausgef&uuml;llt werden.',
+	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+	maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+	integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.',
+	numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.',
+	digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+	alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+	alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).',
+	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.1999&quot;).',
+	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max at mustermann.de&quot;).',
+	url: 'Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).',
+	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).',
+	oneRequired: 'Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.',
+	errorPrefix: 'Fehler: ',
+	warningPrefix: 'Warnung: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+	requiredChk: 'Dieses Feld muss ausgef&uuml;llt werden.',
+	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+	match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.',
+	startDate: 'Das Anfangsdatum',
+	endDate: 'Das Enddatum',
+	currendDate: 'Das aktuelle Datum',
+	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.',
+	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.',
+	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.',
+	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+});
+/*
+---
+
+script: Form.Validator.German.CH.js
+
+description: Date messages for German (Switzerland).
+ 
+license: MIT-style license
+ 
+authors:
+- Michael van der Weg
+
+requires:
+- /Lang
+- /Form.Validator.German
+
+provides: [Form.Validator.German.CH]
+
+...
+*/
+ 
+MooTools.lang.set('de-CH', 'Form.Validator', {
+ 
+  required:'Dieses Feld ist obligatorisch.',
+  minLength:'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  maxLength:'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  integer:'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+  numeric:'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+  digits:'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+  alpha:'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  alphanum:'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  dateSuchAs:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+  dateInFormatMDY:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+  email:'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria at bernasconi.ch&quot;.',
+  url:'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.',
+  currencyDollar:'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+  oneRequired:'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+  errorPrefix: 'Fehler: ',
+  warningPrefix: 'Warnung: ',
+ 
+  //Form.Validator.Extras
+ 
+  noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+  reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+  requiredChk: 'Dieses Feld ist obligatorisch.',
+  reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+  match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+  startDate: 'Das Anfangsdatum',
+  endDate: 'Das Enddatum',
+  currendDate: 'Das aktuelle Datum',
+  afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+  beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+  startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+  sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.'
+ 
+});/*
+---
+
+script: Form.Validator.French.js
+
+description: Form.Validator messages in French.
+
+license: MIT-style license
+
+authors: 
+- Miquel Hudin
+- Nicolas Sorosac <nicolas <dot> sorosac <at> gmail <dot> com>
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Form.Validator', {
+  required:'Ce champ est obligatoire.',
+  minLength:'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  maxLength:'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  integer:'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+  numeric:'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+  digits:'Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d\'union est autoris&eacute;).',
+  alpha:'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  alphanum:'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  dateSuchAs:'Veuillez saisir une date correcte comme {date}',
+  dateInFormatMDY:'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+  email:'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred at domaine.com".',
+  url:'Veuillez saisir une URL, comme http://www.google.com.',
+  currencyDollar:'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
+  oneRequired:'Veuillez s&eacute;lectionner au moins une de ces options.',
+  errorPrefix: 'Erreur : ',
+  warningPrefix: 'Attention : ',
+  
+  //Form.Validator.Extras
+ 
+  noSpace: 'Ce champ n\'accepte pas les espaces.',
+  reqChkByNode: 'Aucun &eacute;l&eacute;ment n\'est s&eacute;lectionn&eacute;.',
+  requiredChk: 'Ce champ est obligatoire.',
+  reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+  match: 'Ce champ doit correspondre avec le champ {matchName}.',
+  startDate: 'date de d&eacute;but',
+  endDate: 'date de fin',
+  currendDate: 'date actuelle',
+  afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+  beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+  startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+  sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'
+ 
+});/*
+---
+
+script: Form.Validator.Italian.js
+
+description: Form.Validator messages in Italian.
+
+license: MIT-style license
+
+authors:
+- Leonardo Laureti
+- Andrea Novero
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Form.Validator', {
+
+	required:'Il campo &egrave; obbligatorio.',
+	minLength:'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+	maxLength:'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+	integer:'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+	numeric:'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+	digits:'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+	alpha:'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+	alphanum:'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+	dateSuchAs:'Inserire una data valida del tipo {date}',
+	dateInFormatMDY:'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+	email:'Inserire un indirizzo email valido. Per esempio "nome at dominio.com".',
+	url:'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
+	currencyDollar:'Inserire un importo valido. Per esempio "$100.00".',
+	oneRequired:'Completare almeno uno dei campi richiesti.',
+	errorPrefix: 'Errore: ',
+	warningPrefix: 'Attenzione: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Non sono consentiti spazi.',
+	reqChkByNode: 'Nessuna voce selezionata.',
+	requiredChk: 'Il campo &egrave; obbligatorio.',
+	reqChkByName: 'Selezionare un(a) {label}.',
+	match: 'Il valore deve corrispondere al campo {matchName}',
+	startDate: 'data d\'inizio',
+	endDate: 'data di fine',
+	currendDate: 'data attuale',
+	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+	startMonth: 'Selezionare un mese d\'inizio',
+	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});/*
+---
+
+script: Form.Validator.Norwegian.js
+
+description: Form.Validator messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+- Espen 'Rexxars' Hovlandsdal
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Form.Validator', {
+   required:'Dette feltet er påkrevd.',
+   minLength:'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+   maxLength:'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+   integer:'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+   numeric:'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+   digits:'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+   alpha:'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   alphanum:'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   dateSuchAs:'Vennligst skriv inn en gyldig dato, som {date}',
+   dateInFormatMDY:'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+   email:'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen at domene.no".',
+   url:'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
+   currencyDollar:'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .',
+   oneRequired:'Vennligst fyll ut noe i minst ett av disse feltene.',
+   errorPrefix: 'Feil: ',
+   warningPrefix: 'Advarsel: '
+});/*
+---
+
+script: Form.Validator.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+- Oskar Krawczyk
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Form.Validator', {
+
+	required:'To pole jest wymagane.',
+	minLength:'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+	maxLength:'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})',
+	integer:'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+	numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+	digits:'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+	alpha:'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	alphanum:'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	dateSuchAs:'Prosimy podać prawidłową datę w formacie: {date}',
+	dateInFormatMDY:'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+	email:'Prosimy podać prawidłowy adres e-mail, np. "jan at domena.pl".',
+	url:'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
+	currencyDollar:'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+	oneRequired:'Prosimy wypełnić chociaż jedno z pól.',
+	errorPrefix: 'BÅ‚Ä…d: ',
+	warningPrefix: 'Uwaga: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'W tym polu nie mogą znajdować się spacje.',
+	reqChkByNode: 'Brak zaznaczonych elementów.',
+	requiredChk: 'To pole jest wymagane.',
+	reqChkByName: 'Prosimy wybrać z {label}.',
+	match: 'To pole musi być takie samo jak {matchName}',
+	startDate: 'data poczÄ…tkowa',
+	endDate: 'data końcowa',
+	currendDate: 'aktualna data',
+	afterDate: 'Podana data poinna być taka sama lub po {label}.',
+	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+	startMonth: 'Prosimy wybrać początkowy miesiąc.',
+	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});/*
+---
+
+script: Form.Validator.Portuguese.js
+
+description: Form.Validator messages in Portuguese.
+
+license: MIT-style license
+
+authors:
+- Miquel Hudin
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Portuguese]
+
+...
+*/
+
+MooTools.lang.set('pt-PT', 'Form.Validator', {
+	required:'Este campo é necessário.',
+	minLength:'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+	maxLength:'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+	integer:'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+	numeric:'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits:'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+	alpha:'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+	alphanum:'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+	dateSuchAs:'Digite uma data válida, como {date}',
+	dateInFormatMDY:'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+	email:'Digite um endereço de email válido. Por exemplo "fred at domain.com".',
+	url:'Digite uma URL válida, como http://www.google.com.',
+	currencyDollar:'Digite um valor válido $. Por exemplo $ 100,00. ',
+	oneRequired:'Digite algo para pelo menos um desses insumos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: '
+
+}).set('pt-PT', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Portuguese.BR.js
+
+description: Form.Validator messages in Portuguese-BR.
+
+license: MIT-style license
+
+authors:
+- Fábio Miranda Costa
+
+requires:
+- /Lang
+- /Form.Validator.Portuguese
+
+provides: [Form.Validator.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Form.Validator', {
+
+	required: 'Este campo é obrigatório.',
+	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+	dateSuchAs: 'Digite uma data válida, como {date}',
+	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+	email: 'Digite um endereço de email válido. Por exemplo "nome at dominio.com".',
+	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
+	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+	oneRequired: 'Digite algo para pelo menos um desses campos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Não é possível digitar espaços neste campo.',
+	reqChkByNode: 'Não foi selecionado nenhum item.',
+	requiredChk: 'Este campo é obrigatório.',
+	reqChkByName: 'Por favor digite um {label}.',
+	match: 'Este campo deve ser igual ao campo {matchName}.',
+	startDate: 'a data inicial',
+	endDate: 'a data final',
+	currendDate: 'a data atual',
+	afterDate: 'A data deve ser igual ou posterior a {label}.',
+	beforeDate: 'A data deve ser igual ou anterior a {label}.',
+	startMonth: 'Por favor selecione uma data inicial.',
+	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});/*
+---
+
+script: Form.Validator.Russian.js
+
+description: Form.Validator messages in Russian (utf-8 and cp1251).
+
+license: MIT-style license
+
+authors:
+- Chernodarov Egor
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Russian]
+
+...
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {
+	required:'Это поле обÑ?зательно к заполнению.',
+	minLength:'ПожалуйÑ?та, введите хотÑ? бы {minLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	maxLength:'ПожалуйÑ?та, введите не больше {maxLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	integer:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло. Дробные чиÑ?ла (например 1.25) тут не разрешены.',
+	numeric:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло (например "1" или "1.1", или "-1", или "-1.1").',
+	digits:'Ð’ Ñ?том поле Ð’Ñ‹ можете иÑ?пользовать только цифры и знаки пунктуации (например, телефонный номер Ñ?о знаками дефиÑ?а или Ñ? точками).',
+	alpha:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z). Пробелы и другие Ñ?имволы запрещены.',
+	alphanum:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z) и цифры (0-9). Пробелы и другие Ñ?имволы запрещены.',
+	dateSuchAs:'ПожалуйÑ?та, введите корректную дату {date}',
+	dateInFormatMDY:'ПожалуйÑ?та, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
+	email:'ПожалуйÑ?та, введите корректный емейл-адреÑ?. ДлÑ? примера "fred at domain.com".',
+	url:'ПожалуйÑ?та, введите правильную Ñ?Ñ?ылку вида http://www.google.com.',
+	currencyDollar:'ПожалуйÑ?та, введите Ñ?умму в долларах. Ð?апример: $100.00 .',
+	oneRequired:'ПожалуйÑ?та, выберите хоть что-нибудь в одном из Ñ?тих полей.',
+	errorPrefix: 'Ошибка: ',
+	warningPrefix: 'Внимание: '
+});
+
+//translation in windows-1251 codepage
+MooTools.lang.set('ru-RU', 'Form.Validator', {
+	required:'�òî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
+	minLength:'�îæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	maxLength:'�îæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	integer:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
+	numeric:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
+	digits:' ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
+	alpha:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	alphanum:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	dateSuchAs:'�îæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
+	dateInFormatMDY:'�îæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃà (íàïðèìåð "12/31/1999")',
+	email:'�îæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred at domain.com".',
+	url:'�îæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
+	currencyDollar:'�îæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. �àïðèìåð: $100.00 .',
+	oneRequired:'�îæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
+	errorPrefix: 'Îøèáêà: ',
+	warningPrefix: 'Âíèìàíèå: '
+});/*
+---
+
+script: Form.Validator.Spanish.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+- Ãlfons Sanchez
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Form.Validator', {
+
+	required:'Este campo es obligatorio.',
+	minLength:'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+	maxLength:'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+	integer:'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+	numeric:'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+	alpha:'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+	alphanum:'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+	dateSuchAs:'Por favor introduce una fecha v&aacute;lida como {date}',
+	dateInFormatMDY:'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred at domain.com".',
+	url:'Por favor introduce una URL v&aacute;lida como http://www.google.com.',
+	currencyDollar:'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+	oneRequired:'Por favor introduce algo para por lo menos una de estas entradas.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No pueden haber espacios en esta entrada.',
+	reqChkByNode: 'No hay elementos seleccionados.',
+	requiredChk: 'Este campo es obligatorio.',
+	reqChkByName: 'Por favor selecciona una {label}.',
+	match: 'Este campo necesita coincidir con el campo {matchName}',
+	startDate: 'la fecha de inicio',
+	endDate: 'la fecha de fin',
+	currendDate: 'la fecha actual',
+	afterDate: 'La fecha debe ser igual o posterior a {label}.',
+	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+	startMonth: 'Por favor selecciona un mes de origen',
+	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+/*
+---
+
+script: Form.Validator.Swedish.js
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+- Martin Lundgren
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Form.Validator', {
+
+	required:'Fältet är obligatoriskt.',
+	minLength:'Ange minst {minLength} tecken (du angav {length} tecken).',
+	maxLength:'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+	integer:'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
+	numeric:'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+	digits:'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
+	alpha:'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	alphanum:'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	dateSuchAs:'Ange ett giltigt datum som t.ex. {date}',
+	dateInFormatMDY:'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+	email:'Ange en giltig e-postadress. Till exempel "erik at domain.com".',
+	url:'Ange en giltig webbadress som http://www.google.com.',
+	currencyDollar:'Ange en giltig belopp. Exempelvis 100,00.',
+	oneRequired:'Vänligen ange minst ett av dessa alternativ.',
+	errorPrefix: 'Fel: ',
+	warningPrefix: 'Varning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
+	reqChkByNode: 'Inga objekt är valda.',
+	requiredChk: 'Detta är ett obligatoriskt fält.',
+	reqChkByName: 'Välj en {label}.',
+	match: 'Detta fält måste matcha {matchName}',
+	startDate: 'startdatumet',
+	endDate: 'slutdatum',
+	currendDate: 'dagens datum',
+	afterDate: 'Datumet bör vara samma eller senare än {label}.',
+	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
+	startMonth: 'Välj en start månad',
+	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'
+
+});/*
+---
+
+script: Form.Validator.Ukrainian.js
+
+description: Form.Validator messages in Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+- Slik
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Ukrainian]
+
+...
+*/
+
+MooTools.lang.set('uk-UA', 'Form.Validator', {
+	required:'Це поле повинне бути заповненим.',
+	minLength:'Введіть хоча б {minLength} Ñ?имволів (Ви ввели {length}).',
+	maxLength:'КількіÑ?Ñ‚ÑŒ Ñ?имволів не може бути більше {maxLength} (Ви ввели {length}).',
+	integer:'Введіть в це поле чиÑ?ло. Дробові чиÑ?ла (наприклад 1.25) не дозволені.',
+	numeric:'Введіть в це поле чиÑ?ло (наприклад "1" або "1.1", або "-1", або "-1.1").',
+	digits:'Ð’ цьому полі ви можете викориÑ?товувати лише цифри Ñ– знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).',
+	alpha:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	alphanum:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z) Ñ– цифри (0-9). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	dateSuchAs:'Введіть коректну дату {date}.',
+	dateInFormatMDY:'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',
+	email:'Введіть коректну адреÑ?у електронної пошти (наприклад "name at domain.com").',
+	url:'Введіть коректне інтернет-поÑ?иланнÑ? (наприклад http://www.google.com).',
+	currencyDollar:'Введіть Ñ?уму в доларах (наприклад "$100.00").',
+	oneRequired:'Заповніть одне з полів.',
+	errorPrefix: 'Помилка: ',
+	warningPrefix: 'Увага: '
+});
+// $Id: common.js 746 2010-03-12 12:57:45Z pagameba $
 /**
  * Class: Jx
  * Jx is a global singleton object that contains the entire Jx library
@@ -13860,7 +14595,7 @@
 /* firebug console supressor for IE/Safari/Opera */
 window.addEvent('load',
 function() {
-    if (! ("console" in window) || !("firebug" in window.console)) {
+    if (! ("console" in window)) {
         var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
         "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
 
@@ -13885,6 +14620,8 @@
     }
 };
 
+// this replaces the mootools $unlink method with our own version that
+// avoids infinite recursion on Jx objects.
 function $unlink(object) {
     if (object && object.jxFamily) {
         return object;
@@ -13908,67 +14645,141 @@
     return unlinked;
 }
 
-/* Setup global namespace
- * If jxcore is loaded by jx.js, then the namespace and baseURL are
- * already established
+/* Setup global namespace.  It is possible to set the global namespace
+ * prior to including jxlib.  This would typically be required only if
+ * the auto-detection of the jxlib base URL would fail.  For instance,
+ * if you combine jxlib with other javascript libraries into a single file
+ * build and call it something without jxlib in the file name, then the
+ * detection of baseURL would fail.  If this happens to you, try adding
+ * Jx = { baseURL: '/path/to/jxlib/'; }
+ * where the path to jxlib contains a file called a_pixel.png (it doesn't
+ * have to include jxlib, just the a_pixel.png file).
  */
 if (typeof Jx === 'undefined') {
-    var Jx = {};
-    (function() {
-        var aScripts = document.getElementsByTagName('SCRIPT');
-        for (var i = 0; i < aScripts.length; i++) {
-            var s = aScripts[i].src;
-            var matches = /(.*[jx|js|lib])\/jxlib(.*)/.exec(s);
-            if (matches && matches[0]) {
-                /**
-                 * APIProperty: {String} baseURL
-                 * This is the URL that Jx was loaded from, it is 
-                 * automatically calculated from the script tag
-                 * src property that included Jx.
-                 *
-                 * Note that this assumes that you are loading Jx
-                 * from a js/ or lib/ folder in parallel to the
-                 * images/ folder that contains the various images
-                 * needed by Jx components.  If you have a different
-                 * folder structure, you can define Jx's base
-                 * by including the following before including
-                 * the jxlib javascript file:
-                 *
-                 * (code)
-                 * Jx = {
-                 *    baseURL: 'some/path'
-                 * }
-                 * (end)
-                 */
-                Jx.aPixel = document.createElement('img', {
-                    alt: '',
-                    title: ''
-                });
-                Jx.aPixel.src = matches[1] + '/a_pixel.png';
-                Jx.baseURL = Jx.aPixel.src.substring(0,
-                Jx.aPixel.src.indexOf('a_pixel.png'));
-
-            }
-        }
-
-    })();
+  var Jx = {};
 }
 
- (function() {
-    /**
-     * Determine if we're running in Adobe AIR. Run this regardless of whether
-     * the above runs or not.
-     */
+/**
+ * APIProperty: {String} baseURL
+ * This is the URL that Jx was loaded from, it is 
+ * automatically calculated from the script tag
+ * src property that included Jx.
+ *
+ * Note that this assumes that you are loading Jx
+ * from a js/ or lib/ folder in parallel to the
+ * images/ folder that contains the various images
+ * needed by Jx components.  If you have a different
+ * folder structure, you can define Jx's base
+ * by including the following before including
+ * the jxlib javascript file:
+ *
+ * (code)
+ * Jx = {
+ *    baseURL: 'some/path'
+ * }
+ * (end)
+ */
+if (!$defined(Jx.baseURL)) {
+  (function() {
     var aScripts = document.getElementsByTagName('SCRIPT');
-    var src = aScripts[0].src;
-    if (src.contains('app:')) {
-        Jx.isAir = true;
-    } else {
-        Jx.isAir = false;
+    for (var i = 0; i < aScripts.length; i++) {
+      var s = aScripts[i].src;
+      var n = s.lastIndexOf('/');
+      var file = s.slice(n+1,s.length-1);
+      if (file.contains('jxlib')) {
+        Jx.baseURL = s.slice(0,n);
+        break;
+      }
     }
-})();
+  })();
+}
+/** 
+ * APIProperty: {Image} aPixel
+ * aPixel is a single transparent pixel and is the only image we actually
+ * use directly in JxLib code.  If you want to use your own transparent pixel
+ * image or use it from a different location than the install of jxlib
+ * javascript files, you can manually declare it before including jxlib code
+ * (code)
+ * Jx = {
+ *   aPixel: new Element('img', {
+ *     alt: '',
+ *     title: '',
+ *     width: 1,
+ *     height: 1,
+ *     src: 'path/to/a/transparent.png'
+ *   });
+ * }
+ * (end)
+ */
+if (!$defined(Jx.aPixel)) {
+  Jx.aPixel = new Element('img', {
+    alt:'',
+    title:'',
+    src: Jx.baseURL +'/a_pixel.png'
+  });
+}
 
+/** 
+ * APIProperty: {Boolean} isAir
+ * indicates if JxLib is running in an Adobe Air environment.  This is
+ * normally auto-detected but you can manually set it by declaring the Jx
+ * namespace before including jxlib:
+ * (code)
+ * Jx = {
+ *   isAir: true
+ * }
+ * (end)
+ */
+if (!$defined(Jx.isAir)) {
+	(function() {
+		/**
+		 * Determine if we're running in Adobe AIR. Run this regardless of whether
+		 * the above runs or not.
+		 */
+		var aScripts = document.getElementsByTagName('SCRIPT');
+		var src = aScripts[0].src;
+		if (src.contains('app:')) {
+			Jx.isAir = true;
+		} else {
+			Jx.isAir = false;
+		}
+	})();
+}
+
 /**
+ * APIMethod: setLanguage
+ * set the current language to be used by Jx widgets.  This uses the MooTools
+ * lang module.  If an invalid or missing language is requested, the default
+ * rules of MooTools.lang will be used (revert to en-US at time of writing).
+ *
+ * Parameters:
+ * {String} language identifier, the language to set.
+ */
+Jx.setLanguage = function(lang) {
+	Jx.lang = lang;
+	MooTools.lang.setLanguage(Jx.lang);
+};
+
+/**
+ * APIProperty: {String} lang
+ * Checks to see if Jx.lang is already set. If not, it sets it to the default
+ * 'en-US'. We will then set the Motools.lang language to this setting 
+ * automatically.
+ * 
+ * The language can be changed on the fly at anytime by calling
+ * Jx.setLanguage().
+ * By default all Jx.Widget subclasses will listen for the langChange event of
+ * the Mootools.lang class. It will then call a method, changeText(), if it
+ * exists on the particular widget. You will be able to disable listening for
+ * these changes by setting the Jx.Widget option useLang to false.
+ */
+if (!$defined(Jx.lang)) {
+	Jx.lang = 'en-US';
+}
+
+Jx.setLanguage(Jx.lang);
+
+/**
  * APIMethod: applyPNGFilter
  *
  * Static method that applies the PNG Filter Hack for IE browsers
@@ -14060,26 +14871,6 @@
 };
 
 /**
- * APIMethod: createIframeShim
- * Creates a new iframe element that is intended to fill a container
- * to mask out other operating system controls (scrollbars, inputs, 
- * buttons, etc) when HTML elements are supposed to be above them.
- *
- * Returns:
- * an HTML iframe element that can be inserted into the DOM.
- */
-/**
- * NOTE: This could be replaced by Mootools-more's IFrameShim class.
- */
-Jx.createIframeShim = function() {
-    return new Element('iframe', {
-        'class': 'jxIframeShim',
-        'scrolling': 'no',
-        'frameborder': 0,
-        'src': Jx.baseURL + '/empty.html'
-    });
-};
-/**
  * APIMethod: getNumber
  * safely parse a number and return its integer value.  A NaN value 
  * returns 0.  CSS size values are also parsed correctly.
@@ -14111,7 +14902,7 @@
 };
 
 /**
- * APIMethod: Jx.type
+ * APIMethod: type
  * safely return the type of an object using the mootools type system
  *
  * Returns:
@@ -14125,27 +14916,26 @@
     return obj.jxFamily || $type(obj);
 };
 
-/**
- * Class: Element
- *
- * Element is a global object provided by the mootools library.  The
- * functions documented here are extensions to the Element object provided
- * by Jx to make cross-browser compatibility easier to achieve.  Most of the
- * methods are measurement related.
- *
- * While the code in these methods has been converted to use MooTools methods,
- * there may be better MooTools methods to use to accomplish these things.
- * Ultimately, it would be nice to eliminate most or all of these and find the
- * MooTools equivalent or convince MooTools to add them.
- * 
- * NOTE: Many of these methods can be replaced with mootools-more's 
- * Element.Measure
- */
-
-
 (function($) {
     // Wrapper for document.id
 
+    /**
+     * Class: Element
+     *
+     * Element is a global object provided by the mootools library.  The
+     * functions documented here are extensions to the Element object provided
+     * by Jx to make cross-browser compatibility easier to achieve.  Most of
+     * the methods are measurement related.
+     *
+     * While the code in these methods has been converted to use MooTools
+     * methods, there may be better MooTools methods to use to accomplish
+     * these things.
+     * Ultimately, it would be nice to eliminate most or all of these and find
+     * the MooTools equivalent or convince MooTools to add them.
+     * 
+     * NOTE: Many of these methods can be replaced with mootools-more's 
+     * Element.Measure
+     */
     Element.implement({
         /**
          * APIMethod: getBoxSizing
@@ -14401,9 +15191,11 @@
             return o.tagName == type ? o: false;
         }
     });
-
+    /**
+     * Class: Array
+     * Extensions to the javascript array object
+     */
     Array.implement({
-
         /**
          * APIMethod: swap
          * swaps 2 elements of an array
@@ -14413,14 +15205,11 @@
          * b - the second position to swap
          */
         'swap': function(a, b) {
-            var temp;
-            temp = this[a];
+            var temp = this[a];
             this[a] = this[b];
             this[b] = temp;
         }
-
     });
-
 })(document.id || $);
 // End Wrapper for document.id
 /**
@@ -14452,14 +15241,14 @@
  */
 Jx.Styles = new(new Class({
     /**
-     * dynamicStyleMap - <Hash> used to keep a reference to dynamically created
-     * style sheets for quick access
+     * dynamicStyleMap - <Hash> used to keep a reference to dynamically
+     * created style sheets for quick access
      */
     dynamicStyleMap: new Hash(),
     /**
-     * Method: getCssRule
-     * retrieve a reference to a CSS rule in a specific style sheet based on its
-     * selector.  If the rule does not exist, create it.
+     * APIMethod: getCssRule
+     * retrieve a reference to a CSS rule in a specific style sheet based on
+     * its selector.  If the rule does not exist, create it.
      *
      * Parameters:
      * selector - <String> the CSS selector for the rule
@@ -14486,22 +15275,22 @@
         return rule;
     },
     /**
-     * Method: insertCssRule
+     * APIMethod: insertCssRule
      * insert a new dynamic rule into the given stylesheet.  If no name is
      * given for the stylesheet then the default stylesheet is used.
      *
      * Parameters:
      * selector - <String> the CSS selector for the rule
-     * declaration - <String> CSS-formatted rules to include.  May be empty, in
-     * which case you may want to use the returned rule object to manipulate
-     * styles
+     * declaration - <String> CSS-formatted rules to include.  May be empty,
+     * in which case you may want to use the returned rule object to
+     * manipulate styles
      * styleSheetName - <String> the name of the sheet to place the rules in, 
      * or empty to put them in a default sheet.
      *
      * Returns:
      * <CSSRule> - a CSS Rule object with properties that are browser
-     * dependent.  In general, you can use rule.styles to set any CSS properties
-     * in the same way that you would set them on a DOM object.
+     * dependent.  In general, you can use rule.styles to set any CSS
+     * properties in the same way that you would set them on a DOM object.
      */
     insertCssRule: function (selector, declaration, styleSheetName) {
         var ss = this.getDynamicStyleSheet(styleSheetName);
@@ -14518,13 +15307,13 @@
         return rule;
     },
     /**
-     * Method: removeCssRule
+     * APIMethod: removeCssRule
      * removes a CSS rule from the named stylesheet.
      *
      * Parameters:
      * selector - <String> the CSS selector for the rule
-     * styleSheetName - <String> the name of the sheet to remove the rule from, 
-     * or empty to remove them from the default sheet.
+     * styleSheetName - <String> the name of the sheet to remove the rule
+     * from,  or empty to remove them from the default sheet.
      *
      * Returns:
      * <Boolean> true if the rule was removed, false if it was not.
@@ -14543,7 +15332,7 @@
         return false;
     },
     /**
-     * Method: getDynamicStyleSheet
+     * APIMethod: getDynamicStyleSheet
      * return a reference to a styleSheet based on its title.  If the sheet
      * does not already exist, it is created.
      *
@@ -14556,13 +15345,14 @@
     getDynamicStyleSheet: function (name) {
         name = (name) ? name : 'default';
         if (!this.dynamicStyleMap.has(name)) {
-            var sheet = new Element('style').set('type', 'text/css').set('title', name).inject(document.head);
+            var sheet = new Element('style').set('type', 'text/css').inject(document.head);
             sheet.indicies = [];
             this.dynamicStyleMap.set(name, sheet);
         }
         return this.dynamicStyleMap.get(name);
     },
-    /* Method: enableStyleSheet
+    /**
+     * APIMethod: enableStyleSheet
      * enable a style sheet
      *
      * Parameters:
@@ -14571,7 +15361,8 @@
     enableStyleSheet: function (name) {
         this.getDynamicStyleSheet(name).disabled = false;
     },
-    /* Method: disableStyleSheet
+    /**
+     * APIMethod: disableStyleSheet
      * enable a style sheet
      *
      * Parameters:
@@ -14579,18 +15370,208 @@
      */
     disableStyleSheet: function (name) {
         this.getDynamicStyleSheet(name).disabled = true;
+    },
+    /**
+     * APIMethod: removeStyleSheet
+     * Removes a style sheet
+     * 
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    removeStyleSheet: function (name) {
+      this.disableStyleSheet(name);
+      this.getDynamicStyleSheet(name).dispose();
+      this.dynamicStyleMap.erase(name);
+    },
+    /**
+     * APIMethod: isStyleSheetDefined
+     * Determined if the passed in name is a defined dynamic style sheet.
+     * 
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    isStyleSheetDefined: function (name) {
+      return this.dynamicStyleMap.has(name);
     }
-}))();// $Id: $
+}))();// $Id: object.js 777 2010-03-24 06:00:30Z jonlb at comcast.net $
 /**
  * Class: Jx.Object
  * Base class for all other object in the JxLib framework. This class
  * implements both mootools mixins Events and Options so the rest of the
  * classes don't need to.
  *
- * Example:
+ * The Initialization Pipeline:
+ * Jx.Object provides a default initialize method to construct new instances
+ * of objects that inherit from it.  No sub-class should override initialize
+ * unless you know exactly what you're doing.  Instead, the initialization
+ * pipeline provides an init() method that is intended to be overridden in
+ * sub-classes to provide class-specific initialization as part of the
+ * initialization pipeline.
+ *
+ * The basic initialization pipeline for a Jx.Object is to parse the
+ * parameters provided to initialize(), separate out options from other formal
+ * parameters based on the parameters property of the class, call init() and
+ * initialize plugins.  
+ *
+ * Parsing Parameters:
+ * Because each sub-class no longer has an initialize method, it no longer has
+ * direct access to parameters passed to the constructor.  Instead, a 
+ * sub-class is expected to provide a parameters attribute with an array of
+ * parameter names in the order expected.  Jx.Object will enumerate the 
+ * attributes passed to its initialize method and automatically place them
+ * in the options object under the appropriate key (the value from the 
+ * array).  Parameters not found will not be present or will be null.
+ *
+ * The default parameters are a single options object which is merged with
+ * the options attribute of the class.
+ *
+ * Calling Init:
+ * Jx.Object fires the event 'preInit' before calling the init() method,
+ * calls the init() method, then fires the 'postInit' event.  It is expected
+ * that most sub-class specific initialization will happen in the init()
+ * method.  A sub-class may hook preInit and postInit events to perform tasks
+ * in one of two ways. 
+ * 
+ * First, simply send onPreInit and onPostInit functions via the options
+ * object as follows (they could be standalone functions or functions of
+ * another object setup using .bind())
+ * 
  * (code)
+ * var preInit = function () {}
+ * var postInit = function () {}
+ * 
+ * var options = {
+ *   onPreInit: preInit,
+ *   onPostInit: postInit,
+ *   ...other options...
+ * };
+ * 
+ * var dialog = new Jx.Dialog(options);
  * (end)
+ * 
+ * The second method you can use is to override the initialize method
  *
+ * (code)
+ * var MyClass = new Class({
+ *   Family: 'MyClass',
+ *   initialize: function() {
+ *     this.addEvent('preInit', this.preInit.bind(this));
+ *     this.addEvent('postInit', this.postInit.bind(this));
+ *     this.parent.apply(this, arguments);
+ *   },
+ *   preInit: function() {
+ *     // something just before init() is called
+ *   },
+ *   postInit: function() {
+ *     // something just after init() is called
+ *   },
+ *   init: function() {
+ *     this.parent();
+ *     // initialization code here
+ *   }
+ * });
+ * (end)
+ * 
+ * When the object finishes initializing itself (including the plugin
+ * initialization) it will fire off the initializeDone event. You can hook
+ * into this event in the same way as the events mentioned above.
+ *
+ * Plugins:
+ * Plugins provide pieces of additional, optional, functionality. They are not 
+ * necessary for the proper function of an object. All plugins should be
+ * located in the Jx.Plugin namespace and they should be further segregated by
+ * applicable object. While all objects can support plugins, not all of them
+ * have the automatic instantiation of applicable plugins turned on. In order
+ * to turn this feature on for an object you need to set the pluginNamespace
+ * property of the object. The following is an example of setting the
+ * property:
+ * 
+ * (code)
+ * var MyClass = new Class({
+ *   Extends: Jx.Object,
+ *   pluginNamespace: 'MyClass'
+ * };
+ * (end)
+ * 
+ * The absence of this property does not mean you cannot attach a plugin to an 
+ * object. It simply means that you can't have Jx.Object create the
+ * plugin for you.
+ * 
+ * There are four ways to attach a plugin to an object. First, simply
+ * instantiate the plugin yourself and call its attach() method (other class
+ * options left out for the sake of simplicity):
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid();
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * APlugin.attach(MyGrid);
+ * (end)
+ * 
+ * Second, you can instantiate the plugin first and pass it to the object
+ * through the plugins array in the options object.
+ * 
+ * (code)
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * var MyGrid = new Jx.Grid({plugins: [APlugin]});
+ * (end)
+ * 
+ * The third way is to pass the information needed to instantiate the plugin
+ * in the plugins array of the options object:
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: [{
+ *      name: 'Selector',
+ *      options: {}    //options needed to create this plugin
+ *   },{
+ *      name: 'Sorter',
+ *      options: {}
+ *   }]
+ * });
+ * (end)
+ * 
+ * The final way, if the plugin has no options, is to pass the name of the
+ * plugin as a simple string in the plugins array.
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: ['Selector','Sorter']
+ * });
+ * (end)
+ * 
+ * Part of the process of initializing plugins is to call prePluginInit() and
+ * postPluginInit(). These events provide you access to the object just before 
+ * and after the plugins are initialized and/or attached to the object using
+ * methods 2 and 3 above. You can hook into these in the same way that you
+ * hook into the preInit() and postInit() events.  
+ * 
+ * Destroying Jx.Object Instances:
+ * Jx.Object provides a destroy method that cleans up potential memory leaks
+ * when you no longer need an object.  Sub-classes are expected to implement
+ * a cleanup() method that provides specific cleanup code for each
+ * sub-class.  Remember to call this.parent() when providing a cleanup()
+ * method. Destroy will also fire off 2 events: preDestroy and postDestroy.
+ * You can hook into these methods in the same way as the init or plugin
+ * events. 
+ *
+ * The Family Attribute:
+ * the Family attribute of a class is used internally by JxLib to identify Jx
+ * objects within mootools.  The actual value of Family is unimportant to Jx.
+ * If you do not provide a Family, a class will inherit it's base class family
+ * up to Jx.Object.  Family is useful when debugging as you will be able to
+ * identify the family in the firebug inspector, but is not as useful for
+ * coding purposes as it does not allow for inheritance.
+ *
+ * Events:
+ *
+ * preInit
+ * postInit
+ * prePluginInit
+ * postPluginInit
+ * initializeDone
+ * preDestroy
+ * postDestroy
+ *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
  *
@@ -14599,9 +15580,33 @@
 Jx.Object = new Class({
     Family: "Jx.Object",
     Implements: [Options, Events],
+    Binds: ['changeText'],
     plugins: new Hash(),
     pluginNamespace: 'Other',
+    /**
+     * Constructor: Jx.Object
+     * create a new instance of Jx.Object
+     *
+     * Parameters:
+     * options - {Object} optional parameters for creating an object.
+     */
     parameters: ['options'],
+    
+    options: {
+      /**
+       * Option: useLang
+       * Turns on this widget's ability to react to changes in
+       * the default language. Handy for changing text out on the fly.
+       * 
+       * TODO: Should this be enabled or disabled by default? 
+       */
+      useLang: true,
+      /**
+       * Option: plugins
+       * {Array} an array of plugins to add to the object.
+       */
+      plugins: null
+    },
 
     initialize: function(){
         //normalize arguments
@@ -14634,6 +15639,9 @@
         }
 
         this.setOptions(options);
+        if (this.options.useLang) {
+            MooTools.lang.addEvent('langChange', this.changeText)
+        }
         this.fireEvent('preInit');
         this.init();
         this.fireEvent('postInit');
@@ -14643,8 +15651,13 @@
         this.fireEvent('initializeDone');
     },
 
+    /**
+     * Method: initPlugins
+     * internal function to initialize plugins on object creation
+     */
     initPlugins: function () {
-        //pluginNamespace must be defined in order to pass plugins to the object
+        // pluginNamespace must be defined in order to pass plugins to the
+        // object
         if ($defined(this.pluginNamespace)) {
             if ($defined(this.options.plugins)
                     && Jx.type(this.options.plugins) === 'array') {
@@ -14653,24 +15666,52 @@
                         plugin.attach(this);
                         this.plugins.set(plugin.name, plugin);
                     } else if (Jx.type(plugin) === 'object') {
-                        //All plugin-enabled objects should define a pluginNamespace member variable
-                        //that is used for locating the plugins. The default namespace is 'Other' for
-                        //now until we come up with a better idea
-                        var p = new Jx.Plugin[this.pluginNamespace][plugin.name](plugin.options);
+                        // All plugin-enabled objects should define a
+                        // pluginNamespace member variable
+                        // that is used for locating the plugins. The default
+                        // namespace is 'Other' for
+                        // now until we come up with a better idea
+                    	var p;
+                    	if ($defined(Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()])) {
+                    		p = new Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                    	} else {
+                    		p = new Jx.Adaptor[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                    	}
                         p.attach(this);
-                        this.plugins.set(p.name, p);
+                    } else if (Jx.type(plugin) === 'string') {
+                        //this is a name for a plugin.
+                    	var p;
+                    	if ($defined(Jx.Plugin[this.pluginNamespace][plugin.capitalize()])) {
+                    		p = new Jx.Plugin[this.pluginNamespace][plugin.capitalize()]();
+                    	} else {
+                    		p = new Jx.Adaptor[this.pluginNamespace][plugin.capitalize()]();
+                    	}
+                        p.attach(this);
                     }
                 }, this);
             }
         }
     },
 
+    /**
+     * APIMethod: destroy
+     * destroy a Jx.Object, safely cleaning up any potential memory
+     * leaks along the way.  Uses the cleanup method of an object to
+     * actually do the cleanup.
+     * Emits the preDestroy event before cleanup and the postDestroy event
+     * after cleanup.
+     */
     destroy: function () {
         this.fireEvent('preDestroy');
         this.cleanup();
         this.fireEvent('postDestroy');
     },
 
+    /**
+     * Method: cleanup
+     * to be implemented by subclasses to do the actual work of destroying
+     * an object. 
+     */
     cleanup: function () {
         //detach plugins
         if (this.plugins.getLength > 0) {
@@ -14681,22 +15722,140 @@
         }
     },
 
+    /**
+     * Method: init
+     * virtual initialization method to be implemented by sub-classes
+     */
     init: $empty,
-
+    
+    /**
+     * APIMethod: registerPlugin
+     * This method is called by a plugin that has its attach method
+     * called.
+     * 
+     * Parameters:
+     * plugin - the plugin to register with this object
+     */
     registerPlugin: function (plugin) {
         if (!this.plugins.has(plugin.name)) {
             this.plugins.set(plugin.name,  plugin);
         }
     },
-
+    /**
+     * APIMethod: deregisterPlugin
+     * his method is called by a plugin that has its detach method
+     * called.
+     * 
+     * Parameters:
+     * plugin - the plugin to deregister.
+     */
     deregisterPlugin: function (plugin) {
         if (this.plugins.has(plugin.name)) {
             this.plugins.erase(plugin.name);
         }
-    }
+    },
+    
+    /**
+     * APIMethod: getPlugin
+     * Allows a developer to get a reference to a plugin with only the
+     * name of the plugin.
+     * 
+     * Parameters:
+     * name - the name of the plugin as defined in the plugin's name property
+     */
+    getPlugin: function (name) {
+        if (this.plugins.has(name)) {
+            return this.plugins.get(name);
+        }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: $empty
 
 });
-// $Id: $
+
+MooTools.lang.set('en-US', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Working ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'close this notice'
+	},
+	progressbar: {
+		messageText: 'Loading...',
+		progressText: '{progress} of {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Browse...'
+	},
+	'formatter.boolean': {
+		'true': 'Yes',
+		'false': 'No'
+	},
+	'formatter.currency': {
+		sign: '$'
+	},
+	'formatter.number': {
+		decimalSeparator: '.',
+    thousandsSeparator: ','
+	},
+	splitter: {
+		barToolTip: 'drag this bar to resize'
+	},
+	panel: {
+		collapseTooltip: 'Collapse/Expand Panel',
+    collapseLabel: 'Collapse',
+    expandLabel: 'Expand',
+    maximizeTooltip: 'Maximize Panel',
+    maximizeLabel: 'Maximize',
+    restoreTooltip: 'Restore Panel',
+    restoreLabel: 'Restore',
+    closeTooltip: 'Close Panel',
+    closeLabel: 'Close'
+	},
+	confirm: {
+		affirmitiveLabel: 'Yes',
+    negativeLabel: 'No'
+	},
+	dialog: {
+		resizeToolTip: 'Resize dialog'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	panelset: {
+		barTooltip: 'drag this bar to resize'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Cancel'
+	},
+	upload: {
+		buttonText: 'Upload Files'
+	},
+	'plugin.resize': {
+	  tooltip: 'Drag to resize, double click to auto-size.'
+	},
+  'plugin.editor': {
+    submitButton: 'Save',
+    cancelButton: 'Cancel'
+  }
+});// $Id: widget.js 781 2010-03-25 13:24:27Z zak4ms $
 /**
  * Class: Jx.Widget
  * Base class for all widgets (visual classes) in the JxLib Framework. This
@@ -14719,10 +15878,10 @@
  *
  * Chrome:
  *
- * Chrome is the extraneous visual element that provides the look and feel to some elements
- * i.e. dialogs.  Chrome is added inside the element specified but may
- * bleed outside the element to provide drop shadows etc.  This is done by
- * absolutely positioning the chrome objects in the container based on
+ * Chrome is the extraneous visual element that provides the look and feel to
+ * some elements i.e. dialogs.  Chrome is added inside the element specified
+ * but may bleed outside the element to provide drop shadows etc.  This is
+ * done by absolutely positioning the chrome objects in the container based on
  * calculations using the margins, borders, and padding of the jxChrome
  * class and the element it is added to.
  *
@@ -14734,18 +15893,37 @@
  * and height.  The images are positioned and clipped such that the
  * appropriate corners of the chrome image are displayed in those locations.
  *
+ * Busy States:
+ * 
+ * Any widget can be set as temporarily busy by calling the setBusy(true)
+ * method and then as idle by calling setBusy(false).  By default, busy 
+ * widgets display an event mask that prevents them from being clicked and
+ * a spinner image with a message.  By default, there are two configurations
+ * for the spinner image and message, one for 'small' widgets like buttons
+ * and inputs, and one for larger widgets like panels and dialogs.  The
+ * framework automatically chooses the most appropriate configuration so you
+ * don't need to worry about it unless you want to customize it.
  *
+ * You can disable this behaviour entirely by setting busyMask: false in the
+ * widget options when creating the widget.
+ *
+ * The mask and spinner functionality is provided by the MooTools Spinner
+ * class.  You can use any options documented for Spinner or Mask by setting
+ * the maskOptions option when creating a widget.
+ * 
+ * MooTools.Lang Keys:
+ * widget.busyMessage - sets the message of the waiter component when used
  */
 Jx.Widget = new Class({
     Family: "Jx.Widget",
     Extends: Jx.Object,
-
+    
     options: {
         /**
          * Option: content
          * content may be an HTML element reference, the id of an HTML element
-         *      already in the DOM, or an HTML string that becomes the inner HTML of
-         *      the element.
+         * already in the DOM, or an HTML string that becomes the inner HTML
+         * of the element.
          */
         content: null,
         /**
@@ -14753,12 +15931,55 @@
          * the URL to load content from
          */
         contentURL: null,
-        template: '<div class="jxWidget"></div>'
+        /**
+         * Option: template
+         * the default HTML structure of this widget.  The default template
+         * is just a div with a class of jxWidget in the base class
+         */
+        template: '<div class="jxWidget"></div>',
+        /**
+         * Option: busyClass
+         * {String} a CSS class name to apply to busy mask when a widget is
+         * set as busy.  The default is 'jxBusy'.
+         */
+        busyClass: 'jxBusy',
+        /**
+         * Option: busyMask
+         * {Object} an object of options to pass to the MooTools Spinner
+         * when masking a busy object.  Set to false if you do not want
+         * to use the busy mask.
+         */
+        busyMask: {
+          'class': 'jxSpinner jxSpinnerLarge',
+          img: {'class':'jxSpinnerImage'},
+          content: {'class':'jxSpinnerContent'},
+          messageContainer: {'class':'jxSpinnerMessage'},
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          },
+          fx: true
+        }
     },
 
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
     classes: new Hash({
         domObj: 'jxWidget'
     }),
+    
+    /**
+     * Property: busy
+     * {Boolean} is the widget currently busy?  This should be considered
+     * an internal property, use the API methods <Jx.Widget::setBusy> and
+     * <Jx.Widget::isBusy> to manage the busy state of a widget.
+     */
+    busy: false,
 
     /**
      * Property: domObj
@@ -14768,8 +15989,7 @@
 
     /**
      * Property: contentIsLoaded
-     *
-     * tracks the load state of the content, specifically useful
+     * {Boolean} tracks the load state of the content, specifically useful
      * in the case of remote content.
      */
     contentIsLoaded: false,
@@ -14780,10 +16000,11 @@
      */
     chrome: null,
 
-
     /**
-     * APIMethod: init
-     * sets up the base widget code and runs the render function.
+     * Method: init
+     * sets up the base widget code and runs the render function.  Called
+     * by the Jx.Object framework for object initialization, should not be
+     * called directly.
      */
     init: function(){
         if (!this.options.deferRender) {
@@ -14795,9 +16016,8 @@
         }
     },
 
-
     /**
-     * Method: loadContent
+     * APIMethod: loadContent
      *
      * triggers loading of content based on options set for the current
      * object.
@@ -14874,28 +16094,8 @@
         }
     },
 
-    processContent: function(element) {
-        $A(element.childNodes).each(function(node){
-            if (node.tagName == 'INPUT' || node.tagName == 'SELECT' || node.tagName == 'TEXTAREA') {
-                if (node.type == 'button') {
-                    node.addEvent('click', function(){
-                        this.fireEvent('click', this, node);
-                    });
-                } else {
-                    node.addEvent('change', function(){
-                        this.fireEvent('change',node);
-                    });
-                }
-            } else {
-                if (node.childNodes) {
-                    this.processContent(node);
-                }
-            }
-        }, this);
-    },
-
     /**
-     * Method: position
+     * APIMethod: position
      * positions an element relative to another element
      * based on the provided options.  Positioning rules are
      * a string with two space-separated values.  The first value
@@ -14946,8 +16146,8 @@
      * vertical - the vertical positioning rule to use to position the
      *    element.  Valid values are 'top', 'center', 'bottom', and a numeric
      *    value.  The default value is 'center center'.
-     * offsets - an object containing numeric pixel offset values for the object
-     *    being positioned as top, right, bottom and left properties.
+     * offsets - an object containing numeric pixel offset values for the
+     *    object being positioned as top, right, bottom and left properties.
      */
     position: function(element, relative, options) {
         element = document.id(element);
@@ -14957,8 +16157,7 @@
         var offsets = $merge({top:0,right:0,bottom:0,left:0}, options.offsets || {});
 
         var coords = relative.getCoordinates(); //top, left, width, height
-        var page;
-        var scroll;
+        var page, scroll;
         if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
             page = Jx.getPageDimensions();
             scroll = document.id(document.body).getScroll();
@@ -14979,11 +16178,7 @@
             coords.top = 0;
         }
         var size = element.getMarginBoxSize(); //width, height
-        var left;
-        var right;
-        var top;
-        var bottom;
-        var n;
+        var left, right, top, bottom, n;
         if (!hor.some(function(opt) {
             var parts = opt.split(' ');
             if (parts.length != 2) {
@@ -15054,73 +16249,73 @@
         element.setStyle('left', left);
 
         if (!ver.some(function(opt) {
-                var parts = opt.split(' ');
-                if (parts.length != 2) {
-                    return false;
-                }
-                if (!isNaN(parseInt(parts[0],10))) {
-                    top = parseInt(parts[0],10);
-                } else {
-                    switch(parts[0]) {
-                        case 'bottom':
-                            top = coords.top + coords.height;
-                            break;
-                        case 'center':
-                            top = coords.top + Math.round(coords.height/2);
-                            break;
-                        case 'top':
-                        default:
-                            top = coords.top;
-                            break;
-                    }
-                }
-                if (!isNaN(parseInt(parts[1],10))) {
-                    var n = parseInt(parts[1],10);
-                    if (n>=0) {
-                        top += n;
-                        bottom = top + size.height;
-                    } else {
-                        bottom = top + n;
-                        top = bottom - size.height;
-                    }
-                } else {
-                    switch(parts[1]) {
-                        case 'top':
-                            top -= offsets.top;
-                            bottom = top + size.height;
-                            break;
-                        case 'bottom':
-                            top += offsets.bottom;
-                            bottom = top;
-                            top = top - size.height;
-                            break;
-                        case 'center':
-                        default:
-                            top = top - Math.round(size.height/2);
-                            bottom = top + size.height;
-                            break;
-                    }
-                }
-                return (top >= scroll.y && bottom <= scroll.y + page.height);
-            })) {
-                // all failed, snap the last position onto the page as best
-                // we can - can't do anything if the element is higher than the
-                // space available.
-                if (bottom > page.height) {
-                    top = scroll.y + page.height - size.height;
-                }
-                if (top < 0) {
-                    top = 0;
-                }
+          var parts = opt.split(' ');
+          if (parts.length != 2) {
+            return false;
+          }
+          if (!isNaN(parseInt(parts[0],10))) {
+            top = parseInt(parts[0],10);
+          } else {
+            switch(parts[0]) {
+              case 'bottom':
+                top = coords.top + coords.height;
+                break;
+              case 'center':
+                top = coords.top + Math.round(coords.height/2);
+                break;
+              case 'top':
+              default:
+                top = coords.top;
+                break;
             }
-            element.setStyle('top', top);
+          }
+          if (!isNaN(parseInt(parts[1],10))) {
+              var n = parseInt(parts[1],10);
+              if (n>=0) {
+                  top += n;
+                  bottom = top + size.height;
+              } else {
+                  bottom = top + n;
+                  top = bottom - size.height;
+              }
+          } else {
+              switch(parts[1]) {
+                  case 'top':
+                      top -= offsets.top;
+                      bottom = top + size.height;
+                      break;
+                  case 'bottom':
+                      top += offsets.bottom;
+                      bottom = top;
+                      top = top - size.height;
+                      break;
+                  case 'center':
+                  default:
+                      top = top - Math.round(size.height/2);
+                      bottom = top + size.height;
+                      break;
+              }
+          }
+          return (top >= scroll.y && bottom <= scroll.y + page.height);
+      })) {
+          // all failed, snap the last position onto the page as best
+          // we can - can't do anything if the element is higher than the
+          // space available.
+          if (bottom > page.height) {
+              top = scroll.y + page.height - size.height;
+          }
+          if (top < 0) {
+              top = 0;
+          }
+      }
+      element.setStyle('top', top);
 
-            /* update the jx layout if necessary */
-            var jxl = element.retrieve('jxLayout');
-            if (jxl) {
-                jxl.options.left = left;
-                jxl.options.top = top;
-            }
+      /* update the jx layout if necessary */
+      var jxl = element.retrieve('jxLayout');
+      if (jxl) {
+          jxl.options.left = left;
+          jxl.options.top = top;
+      }
     },
 
     /**
@@ -15153,36 +16348,39 @@
         /* get the chrome image from the background image of the element */
         /* the app: protocol check is for adobe air support */
         var src = c.getStyle('backgroundImage');
-        if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
-            src = null;
-        } else {
-            src = src.slice(4,-1);
-            /* this only seems to be IE and Opera, but they add quotes
-             * around the url - yuck
-             */
-            if (src.charAt(0) == '"') {
-                src = src.slice(1,-1);
-            }
+        if (src != null) {
+          if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
+              src = null;
+          } else {
+              src = src.slice(4,-1);
+              /* this only seems to be IE and Opera, but they add quotes
+               * around the url - yuck
+               */
+              if (src.charAt(0) == '"') {
+                  src = src.slice(1,-1);
+              }
 
-            /* and remove the background image */
-            c.setStyle('backgroundImage', 'none');
+              /* and remove the background image */
+              c.setStyle('backgroundImage', 'none');
 
-            /* make chrome */
-            ['TR','TL','BL','BR'].each(function(s){
-                c.adopt(
-                    new Element('div',{
-                        'class':'jxChrome'+s
-                    }).adopt(
-                    new Element('img',{
-                        'class':'png24',
-                        src:src,
-                        alt: '',
-                        title: ''
-                    })));
-            }, this);
+              /* make chrome */
+              ['TR','TL','BL','BR'].each(function(s){
+                  c.adopt(
+                      new Element('div',{
+                          'class':'jxChrome'+s
+                      }).adopt(
+                      new Element('img',{
+                          'class':'png24',
+                          src:src,
+                          alt: '',
+                          title: ''
+                      })));
+              }, this);
+          }
         }
-        if (!window.opera) {
-            c.adopt(Jx.createIframeShim());
+        /* create a shim so selects don't show through the chrome */
+        if ($defined(window.IframeShim)) {
+          this.shim = new IframeShim(c, {className: 'jxIframeShim'});
         }
 
         /* remove from DOM so the other resizing logic works as expected */
@@ -15191,7 +16389,7 @@
     },
 
     /**
-     * Method: showChrome
+     * APIMethod: showChrome
      * show the chrome on an element.  This creates the chrome if necessary.
      * If the chrome has been previously created and not removed, you can
      * call this without an element and it will just resize the chrome within
@@ -15210,32 +16408,49 @@
                 element.addClass('jxHasChrome');
             }
             this.resizeChrome(element);
+            if (this.shim) {
+              this.shim.show();
+            }
             if (element && this.chrome.parentNode !== element) {
                 element.adopt(this.chrome);
+                this.chrome.setStyle('z-index',-1);
             }
         }
     },
 
     /**
-     * Method: hideChrome
+     * APIMethod: hideChrome
      * removes the chrome from the DOM.  If you do this, you can't
      * call showChrome with no arguments.
      */
     hideChrome: function() {
         if (this.chrome) {
+            if (this.shim) { 
+              this.shim.hide(); 
+            }
             this.chrome.parentNode.removeClass('jxHasChrome');
             this.chrome.dispose();
         }
     },
 
+    /**
+     * APIMethod: resizeChrome
+     * manually resize the chrome on an element.
+     *
+     * Parameters:
+     * element: {DOMElement} the element to resize the chrome for
+     */
     resizeChrome: function(o) {
         if (this.chrome && Browser.Engine.trident4) {
             this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
+            if (this.shim) {
+              this.shim.position();
+            }
         }
     },
 
     /**
-     * Method: addTo
+     * APIMethod: addTo
      * adds the object to the DOM relative to another element.  If you use
      * 'top' or 'bottom' then the element is added to the relative
      * element (becomes a child node).  If you use 'before' or 'after'
@@ -15254,13 +16469,29 @@
     addTo: function(reference, where) {
         var el = document.id(this.addable) || document.id(this.domObj);
         if (el) {
-            ref = document.id(reference);
-            el.inject(ref,where);
+            if (reference instanceof Jx.Widget && $defined(reference.add)) {
+                reference.add(el);
+            } else {
+                ref = document.id(reference);
+                el.inject(ref,where);
+            }
             this.fireEvent('addTo',this);
         }
         return this;
     },
 
+    /**
+     * APIMethod: toElement
+     * return a DOM element reference for this widget, by default this
+     * returns the local domObj reference.  This is used by the mootools
+     * framework with the document.id() or $() methods allowing you to
+     * manipulate a Jx.Widget sub class as if it were a DOM element.
+     *
+     * (code)
+     * var button = new Jx.Button({label: 'test'});
+     * $(button).inject('someElement');
+     * (end)
+     */
     toElement: function() {
         return this.domObj;
     },
@@ -15275,10 +16506,10 @@
      * container - the container to add the template into
      *
      * Returns:
-     * a hash object containing the requested Elements keyed by the class names
+     * a hash object containing the requested Elements keyed by the class
+     * names
      */
     processTemplate: function(template,classes,container){
-
         var h = new Hash();
         var element;
         if ($defined(container)){
@@ -15292,9 +16523,7 @@
                 h.set(klass,el);
             }
         });
-
         return h;
-
     },
 
     /**
@@ -15308,13 +16537,21 @@
         return prefix + uid;
     },
 
-    remove: function(){
+    /**
+     * APIMethod: dispose
+     * remove the widget from the DOM
+     */
+    dispose: function(){
         var el = document.id(this.addable) || document.id(this.domObj);
         if (el) {
             el.dispose();
         }
     },
 
+    /**
+     * Method: cleanup
+     * destroy the widget and clean up any potential memory leaks
+     */
     cleanup: function(){
         if ($defined(this.domObj)) {
             this.domObj.destroy();
@@ -15328,13 +16565,27 @@
         this.parent();
     },
 
+    /**
+     * Method: render
+     * render the widget, internal function called by the framework.
+     */
     render: function() {
         this.elements = this.processElements(this.options.template,
             this.classes);
     },
 
+    /**
+     * Property: elements
+     * a hash of elements extracted by processing the widget template
+     */
     elements: null,
 
+    /**
+     * Method: processElements
+     * process the template of the widget and populate the elements hash
+     * with any objects.  Also set any object references based on the classes
+     * hash.
+     */
     processElements: function(template, classes) {
         var keys = classes.getValues();
         elements = this.processTemplate(template, keys);
@@ -15344,6 +16595,118 @@
             }
         }, this);
         return elements;
+    },
+    
+    /**
+     * APIMethod: isBusy
+     * indicate if the widget is currently busy or not
+     *
+     * Returns:
+     * {Boolean} true if busy, false otherwise.
+     */
+    isBusy: function() {
+      return this.busy;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        if (this.options.busyClass) {
+          this.domObj.addClass(this.options.busyClass);
+        }
+        if (this.options.busyMask && this.domObj.spin) {
+          /* put the spinner above the element in the z-index */
+          var z = Jx.getNumber(this.domObj.getStyle('z-index'));
+          var opts = {
+            style: {
+              'z-index': z+1
+            }
+          };
+          /* switch to the small size if the element is less than
+           * 60 pixels high
+           */
+          var size = this.domObj.getBorderBoxSize();
+          if (size.height < 60) {
+            opts['class'] = 'jxSpinner jxSpinnerSmall';
+            opts.img = null;
+            opts.message = new Element('p',{
+              'class':'jxSpinnerMessage',
+              html: '<span class="jxSpinnerImage"></span>'+MooTools.lang.get('Jx','widget').busyMessage
+            });
+          }
+          opts = $merge(this.options.busyMask, opts);
+          
+          this.domObj.get('spinner', opts).show(!this.options.busyMask.fx);
+  		
+        }
+      } else {
+        if (this.options.busyClass) {
+          this.domObj.removeClass(this.options.busyClass);
+        }
+        if (this.options.busyMask && this.domObj.unspin) {
+          this.domObj.get('spinner').hide(!this.options.busyMask.fx);
+    	
+        }
+      }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    },
+    
+    /**
+     * APIMethod: stack
+     * stack this widget in the z-index of the DOM relative to other stacked
+     * objects.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to stack.  By default, the
+     * element to stack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    stack: function(el) {
+      el = el || document.id(this);
+      Jx.Stack.stack(el);
+    },
+    
+    /**
+     * APIMethod: unstack
+     * remove this widget from the stack.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to unstack.  By default, the
+     * element to unstack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    unstack: function(el) {
+      el = el || document.id(this);
+      Jx.Stack.unstack(el);
     }
 });
 
@@ -15377,8 +16740,32 @@
             }
         }
     });
-}
+}// $Id: selection.js 762 2010-03-16 13:48:29Z pagameba $
+/**
+ * Class: Jx.Selection
+ * 
+ * Manage selection of objects.
+ *
+ * Example:
+ * (code)
+ * var selection = new Jx.Selection();
+ * (end)
+ *
+ * Events:
+ * select - fired when an item is added to the selection.  This event may be
+ *    changed by passing the eventToFire option when creating the selection 
+ *    object.
+ * unselect - fired when an item is removed from the selection.  This event
+ *    may be changed by passing the eventToFire option when creating the
+ *    selection object.
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
 
+
 Jx.Selection = new Class({
     Family: 'Jx.Selection',
     Extends: Jx.Object,
@@ -15386,7 +16773,17 @@
         /**
          * Option: eventToFire
          * Allows the developer to change the event that is fired in case one
-         * object is using multiple selectionManager instances.
+         * object is using multiple selectionManager instances.  The default
+         * is to use 'select' and 'unselect'.  To modify the event names,
+         * pass different values:
+         * (code)
+         * new Jx.Selection({
+         *   eventToFire: {
+         *     select: 'newSelect',
+         *     unselect: 'newUnselect'
+         *   }
+         * });
+         * (end)
          */
         eventToFire: { 
             select: 'select',
@@ -15394,14 +16791,15 @@
         },
         /**
          * APIProperty: selectClass
-         * the CSS class name to add to the wrapper element when it is selected
+         * the CSS class name to add to the wrapper element when it is
+         * selected
          */
         selectClass: 'jxSelected',
         /**
          * Option: selectMode
-         * {string} default single.  May be single or multiple.  In single mode
-         * only one item may be selected.  Selecting a new item will implicitly
-         * unselect the currently selected item.
+         * {string} default single.  May be single or multiple.  In single
+         * mode only one item may be selected.  Selecting a new item will
+         * implicitly unselect the currently selected item.
          */
         selectMode: 'single',
         /**
@@ -15415,25 +16813,51 @@
          * {Integer} Default 0.  The minimum number of items that must be
          * selected.  If set to a number higher than 0, items added to a list
          * are automatically selected until this minimum is met.  The user may
-         * not unselect items if unselecting them will drop the total number of
-         * items selected below the minimum.
+         * not unselect items if unselecting them will drop the total number
+         * of items selected below the minimum.
          */
         minimumSelection: 0
     },
     
+    /**
+     * Property: selection
+     * {Array} an array holding the current selection
+     */
     selection: null,
     
+    /**
+     * Constructor: Jx.Selection
+     * create a new instance of Jx.Selection
+     * 
+     * Parameters:
+     * options - {Object} options for the new instance
+     */
     init: function () {
         this.selection = [];
     },
     
+    /**
+     * APIMethod: defaultSelect
+     * select an item if the selection does not yet contain the minimum
+     * number of selected items.  Uses <Jx.Selection::select> to select
+     * the item, so the same criteria is applied to the item if it is
+     * to be selected.
+     */
     defaultSelect: function(item) {
         if (this.selection.length < this.options.minimumSelection) {
             this.select(item);
         }
     },
     
+    /**
+     * APIMethod: select
+     * select an item.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
     select: function (item) {
+        item = document.id(item);
         if (this.options.selectMode === 'multiple') {
             if (this.selection.contains(item)) {
                 this.unselect(item);
@@ -15456,6 +16880,14 @@
         }
     },
     
+    /**
+     * APIMethod: unselect
+     * remove an item from the selection.  The item must already be in the
+     * selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
     unselect: function (item) {
         if (this.selection.contains(item) && 
             this.selection.length > this.options.minimumSelection) {
@@ -15465,15 +16897,32 @@
         }
     },
     
+    /**
+     * APIMethod: selected
+     * returns the items in the current selection.
+     *
+     * Returns:
+     * {Array} an array of DOM elements in the current selection
+     */
     selected: function () {
         return this.selection;
     },
     
+    /**
+     * APIMethod: isSelected
+     * test if an item is in the current selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     *
+     * Returns:
+     * {Boolean} true if the current selection contains the item, false
+     * otherwise
+     */
     isSelected: function(item) {
         return this.selection.contains(item);
     }
-
-});// $Id: $
+});// $Id: list.js 762 2010-03-16 13:48:29Z pagameba $
 /**
  * Class: Jx.List
  * 
@@ -15488,6 +16937,17 @@
  *
  * Example:
  * (code)
+ * var list = new Jx.List('container',{
+ *   hover: true,
+ *   select: true,
+ *   onSelect: function(el) {
+ *     alert(el.get('html'));
+ *   }
+ * });
+ * list.add(new Element('li', {html:'1'}));
+ * list.add(new Element('li', {html:'2'}));
+ * list.add(new Element('li', {html:'3'}));
+ *
  * (end)
  *
  * Events:
@@ -15506,11 +16966,24 @@
 Jx.List = new Class({
     Family: 'Jx.List',
     Extends: Jx.Object,
+    /**
+     * Constructor: Jx.List
+     * create a new instance of Jx.List
+     *
+     * Parameters:
+     * container - {Mixed} an element reference or id of an element that will
+     * contain the items in the list
+     * options - {Object} an object containing optional parameters
+     * selection - {<Jx.Selection>} null or a Jx.Selection object. If the
+     * select option is set to true, then list will use this selection object
+     * to track selections or create its own if no selection object is 
+     * supplied.
+     */
     parameters: ['container', 'options', 'selection'],
     /* does this object own the selection object (and should clean it up) */
     ownsSelection: false,
     /**
-     * APIProperty: itemContainer
+     * APIProperty: container
      * the element that will contain items as they are added
      */
     container: null,
@@ -15527,7 +17000,7 @@
         items: null,
         /**
          * Option: hover
-         * {Boolean} default true.  If set to true, the wrapper element will
+         * {Boolean} default false.  If set to true, the wrapper element will
          * obtain the defined hoverClass if set and mouseenter/mouseleave
          * events will be emitted when the user hovers over and out of elements
          */
@@ -15541,7 +17014,7 @@
 
         /**
          * Option: press
-         * {Boolean} default true.  If set to true, the wrapper element will
+         * {Boolean} default false.  If set to true, the wrapper element will
          * obtain the defined pressClass if set and mousedown/mouseup
          * events will be emitted when the user clicks on elements
          */
@@ -15555,7 +17028,7 @@
         
         /**
          * Option: select
-         * {Boolean} default true.  If set to true, the wrapper element will
+         * {Boolean} default false.  If set to true, the wrapper element will
          * obtain the defined selectClass if set and select/unselect events
          * will be emitted when items are selected and unselected.  For other
          * selection objects, see <Jx.Selection>
@@ -15643,6 +17116,8 @@
             this.selection = new Jx.Selection(this.options);
             this.ownsSelection = true;
         }
+        
+        this.setSelection(this.selection);
             
         if ($defined(this.options.items)) {
             this.add(this.options.items);
@@ -15705,9 +17180,9 @@
                 });
             }
             if ($defined(position)) {
-                if ($type(position) == 'integer') {
+                if ($type(position) == 'number') {
                     if (position < this.container.childNodes.length) {
-                        el.inject(this.container.childNodes[position],' before');
+                        el.inject(this.container.childNodes[position],'before');
                     } else {
                         el.inject(this.container, 'bottom');
                     }
@@ -15801,9 +17276,11 @@
      * Parameters:
      * func - {function} the function to apply, it will receive the item and
      * index of the item as parameters
+     * context - {object} the context to execute the function in, null by
+     * default.
      */
-    each: function(f) {
-        $A(this.container.childNodes).each(f);
+    each: function(f, context) {
+        $A(this.container.childNodes).each(f, context);
     },
     /**
      * APIMethod: select
@@ -15880,7 +17357,162 @@
         }
     }
 
-});// $Id: $
+});/**
+ * Class: Jx.Stack
+ * Manage the zIndex of widgets
+ *
+ * This is a singleton and should be called directly, like so:
+ *
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010 Paul Spencer
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Stack = new(new Class({
+  /**
+   * Property: els
+   * {Array} the elements in the stack
+   */
+  els: [],
+  
+  /**
+   * Property: base
+   * {Integer} the base z-index value of the first element in the stack
+   */
+  base: 1000,
+  
+  /**
+   * Property: increment
+   * {Integer} the amount to increment the z-index between elements of the
+   * stack
+   */
+  increment: 100,
+  
+  /**
+   * APIMethod: stack
+   * push an element onto the stack and set its z-index appropriately
+   *
+   * Parameters:
+   * el - {DOMElement} a DOM element to push on the stack
+   */
+  stack: function(el) {
+    this.unstack(el);
+    this.els.push(el);
+    this.setZIndex(el, this.els.length-1);
+  },
+
+  /**
+   * APIMethod: unstack
+   * pull an element off the stack and reflow the z-index of the remaining
+   * elements in the stack if necessary
+   *
+   * Parameters:
+   * el - {DOMElement} the DOM element to pull off the stack
+   */
+  unstack: function(el) {
+    if (this.els.contains(el)) {
+      el.setStyle('z-index', '');
+      var idx = this.els.indexOf(el);
+      this.els.erase(el);
+      for (var i=idx; i<this.els.length; i++) {
+        this.setZIndex(this.els[i], i);
+      }
+    }
+  },
+
+  /**
+   * Method: setZIndex
+   * set the z-index of an element based on its position in the stack
+   *
+   * Parameters:
+   * el - {DOMElement} the element to set the z-index for
+   * idx - {Integer} optional, the index to assume for this object
+   */
+  setZIndex: function(obj, idx) {
+    idx = idx || this.els.indexOf(obj);
+    if (idx !== false) {
+      document.id(obj).setStyle('z-index', this.base + (idx*this.increment));
+    }
+  }
+  
+}))();
+MooTools.lang.set('de-DE', 'Jx', {
+
+	'widget': {
+		busyMessage: 'Arbeite ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'Notiz schließen'
+	},
+	progressbar: {
+		messageText: 'Lade...',
+		progressText: '{progress} von {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Durchsuchen...'
+	},
+	'formatter.boolean': {
+		'true': 'Ja',
+		'false': 'Nein'
+	},
+	'formatter.currency': {
+		sign: '€'
+	},
+	'formatter.number': {
+		decimalSeparator: '.',
+        thousandsSeparator: ','
+	},
+	splitter: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panel: {
+		collapseTooltip: 'Panel ein-/ausklappen', //colB
+        collapseLabel: 'Einklappen',  //colM
+        expandLabel: 'Ausklappen', //colM
+        maximizeTooltip: 'Panel maximieren',
+        maximizeLabel: 'maximieren',
+        restoreTooltip: 'Panel wieder herstellen', //maxB
+        restoreLabel: 'wieder herstellen', //maxM
+        closeTooltip: 'Panel schließen', //closeB
+        closeLabel: 'Schließen' //closeM
+	},
+	confirm: {
+		affirmitiveLabel: 'Ja',
+        negativeLabel: 'Nein'
+	},
+	dialog: {
+		label: 'Neues Fenster'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	panelset: {
+		barTooltip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Abbrechen'
+	},
+	upload: {
+		buttonText: 'Dateien hochladen'
+	},
+	'plugin.resize': {
+	  tooltip: 'Klicken um Größe zu verändern. Doppelklick für automatische Größe.'
+	},
+  'plugin.editor': {
+    submitButton: 'Speichern',
+    cancelButton: 'Abbrechen'
+  }
+});// $Id: record.js 686 2010-02-01 05:45:28Z jonlb at comcast.net $
 /**
  * Class: Jx.record
  * 
@@ -16008,7 +17640,8 @@
         var oldValue = this.get(column);
         this.data.set(column.name, data);
         this.state = Jx.Record.UPDATE;
-        this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);
+        return [column.name, oldValue, data];
+        //this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);
             
     },
     /**
@@ -16023,7 +17656,11 @@
      * True | False depending on the outcome of the comparison.
      */
     equals: function (column, value) {
-        var column = this.resolveCol(column);
+        if (column === 'primaryKey') {
+            column = this.resolveCol(this.options.primaryKey);
+        } else {
+            column = this.resolveCol(column);
+        }
         if (!this.data.has(column.name)) {
             return null;
         } else {
@@ -16084,7 +17721,7 @@
 
 Jx.Record.UPDATE = 1;
 Jx.Record.DELETE = 2;
-Jx.Record.INSERT = 3;// $Id: $
+Jx.Record.INSERT = 3;// $Id: store.js 797 2010-03-26 20:23:32Z jonlb at comcast.net $
 /**
  * Class: Jx.Store 
  * 
@@ -16188,6 +17825,10 @@
      */
     ready: false,
     
+    /**
+     * Method: init
+     * initialize the store, should be called by sub-classes
+     */
     init: function () {
         this.parent();
         
@@ -16222,6 +17863,11 @@
         
     },
     
+    /**
+     * Method: cleanup
+     * avoid memory leaks when a store is destroyed, should be called
+     * by sub-classes if overridden
+     */
     cleanup: function () {
         this.strategies.each(function(strategy){
             strategy.destroy();
@@ -16259,12 +17905,12 @@
     },
     /**
      * APIMethod: load
-     * used to load the store. It simply fires an event that the strategies are
-     * listening for.
+     * used to load the store. It simply fires an event that the strategies
+     * are listening for.
      * 
      * Parameters:
-     * params - a hash of parameters passed to the strategy for determining what records
-     *          to load.
+     * params - a hash of parameters passed to the strategy for determining
+     *     what records to load.
      */
     load: function (params) {
         this.fireEvent('storeLoad', params);
@@ -16288,14 +17934,9 @@
      */
     hasNext : function () {
         if ($defined(this.data)) {
-            if (this.index < this.data.length - 1) {
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return null;
+            return this.index < this.data.length - 1;
         }
+        return null;
     },
 
     /**
@@ -16307,14 +17948,9 @@
      */
     hasPrevious : function () {
         if ($defined(this.data)) {
-            if (this.index > 0) {
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return null;
+            return this.index > 0;
         }
+        return null;
     },
 
     /**
@@ -16410,9 +18046,8 @@
     count : function () {
         if ($defined(this.data)) {
             return this.data.length;
-        } else {
-            return null;
         }
+        return null;
     },
 
     /**
@@ -16425,9 +18060,8 @@
     getPosition : function () {
         if ($defined(this.data)) {
             return this.index;
-        } else {
-            return null;
         }
+        return null;
     },
 
     /**
@@ -16452,7 +18086,8 @@
     },
     /**
      * APIMethod: each
-     * allows iteration through the store's records.
+     * allows iteration through the store's records. 
+     * NOTE: this function is untested
      * 
      * Parameters:
      * fn - the function to execute for each record
@@ -16506,7 +18141,12 @@
         if (!$defined(index)) {
             index = this.index;
         }
-        return this.data[index].set(column, data);
+        var ret = this.data[index].set(column, data);
+        ret.reverse();
+        ret.push(index);
+        ret.reverse();
+        //fire event with array [index, column, oldvalue, newValue]
+        this.fireEvent('storeColumnChanged', ret);
     },
     /**
      * APIMethod: refresh
@@ -16522,12 +18162,17 @@
      * Parameters:
      * data - The data to use in creating a record. This should be in whatever
      *        form Jx.Store.Record, or the current subclass, needs it in.
+     * position - whether the record is added to the 'top' or 'bottom' of the 
+     *      store.
      * insert - flag whether this is an "insert"
      */
-    addRecord: function (data, insert) {
+    addRecord: function (data, position, insert) {
         if (!$defined(this.data)) {
             this.data = [];
         }
+        
+        position = $defined(position)? position : 'bottom';
+        
         var record;
         if (data instanceof Jx.Record) {
             record = data;
@@ -16537,8 +18182,16 @@
         if (insert) {
             record.state = Jx.Record.INSERT;
         }
-        this.data.push(record);
-        this.fireEvent('storeRecordAdded', [record, this]);
+        if (position === 'top') {
+            //some literature claims that .shift() and .unshift() don't work reliably in IE
+            //so we do it this way.
+            this.data.reverse();
+            this.data.push(record);
+            this.data.reverse();
+        } else {
+            this.data.push(record);
+        }
+        this.fireEvent('storeRecordAdded', [this, record, position]);
     },
     /**
      * APIMethod: addRecords
@@ -16546,14 +18199,21 @@
      * 
      * Parameters:
      * data - an array of data to add.
+     * position - 'top' or 'bottom'. Indicates whether to add at the top or
+     * the bottom of the store
      */
-    addRecords: function (data) {
+    addRecords: function (data, position) {
         var def = $defined(data);
         var type = Jx.type(data);
         if (def && type === 'array') {
             this.fireEvent('storeBeginAddRecords', this);
+            //if position is top, reverse the array or we'll add them in the
+            // wrong order.
+            if (position === 'top') {
+                data.reverse();
+            }
             data.each(function(d){
-                this.addRecord(d);
+                this.addRecord(d, position);
             },this);
             this.fireEvent('storeEndAddRecords', this);
             return true;
@@ -16566,8 +18226,8 @@
      * Returns the record at the given index or the current store index
      * 
      * Parameters:
-     * index - the index from which to return the record. Optional. Defaults to
-     *          the current store index
+     * index - the index from which to return the record. Optional. Defaults
+     * to the current store index
      */
     getRecord: function (index) {
         if (!$defined(index)) {
@@ -16579,6 +18239,9 @@
                 return this.data[index];
             }
         } else {
+            //Not sure what the point of this part is. It compares the 
+            //record to the index directly as if we passed in the record which 
+            //means we already have the record... huh???
             var r;
             this.data.each(function(record){
                 if (record === index) {
@@ -16629,7 +18292,8 @@
         var record = this.data[index];
         record.state = Jx.Record.DELETE;
         // Set to Null or slice it out and compact the array???
-        this.data[index] = null;
+        //this.data[index] = null;
+        this.data.splice(index,1);
         if (!$defined(this.deleted)) {
             this.deleted = [];
         }
@@ -16640,12 +18304,14 @@
      * APIMethod: insertRecord
      * Shortcut to addRecord which facilitates marking a record as inserted.
      * 
-     * Paremeters:
+     * Parameters:
      * data - the data to use in creating this inserted record. Should be in
      *          whatever form the current implementation of Jx.Record needs
+     * position - where to place the record. Should be either 'top' or
+     *    'bottom'.
      */
-    insertRecord: function (data) {
-        this.addRecord(data, true);
+    insertRecord: function (data, position) {
+        this.addRecord(data, position, true);
     },
     
     /**
@@ -16659,8 +18325,8 @@
     /**
      * APIMethod: findByColumn
      * Used to find a specific record by the value in a specific column. This
-     * is particularly useful for finding records by a unique id column. The search
-     * will stop on the first instance of the value
+     * is particularly useful for finding records by a unique id column. The
+     * search will stop on the first instance of the value
      * 
      * Parameters:
      * column - the name (or index) of the column to search by
@@ -16686,8 +18352,82 @@
             return index;
         }
         return null;
-    }
-});// $Id: $
+    },
+    /**
+     * APIMethod: removeRecord
+     * removes (but does not mark for deletion) a record at the given index
+     * or the current store index if none is passed in.
+     * 
+     * Parameters: 
+     * index - Optional. The store index of the record to remove.
+     */
+    removeRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        this.data.splice(index,1);
+        this.fireEvent('storeRecordRemoved', [this, index])
+    },
+    /**
+     * APIMethod: removeRecords
+     * Used to remove multiple contiguous records from a store. 
+     * 
+     * Parameters:
+     * first - where to start removing records (zero-based)
+     * last - where to stop removing records (zero-based, inclusive)
+     */
+    removeRecords: function (first, last) {
+        for (var i = first; i <= last; i++) {
+            this.removeRecord(first);
+        }
+        this.fireEvent('storeMultipleRecordsRemoved', [this, first, last]);
+    },
+    
+    /**
+	 * APIMethod: parseTemplate
+	 * parses the provided template to determine which store columns are
+	 * required to complete it.
+	 *
+	 * Parameters:
+	 * template - the template to parse
+	 */
+	parseTemplate: function (template) {
+	    //we parse the template based on the columns in the data store looking
+	    //for the pattern {column-name}. If it's in there we add it to the
+	    //array of ones to look fo
+	    var arr = [];
+	    this.options.columns.each(function (col) {
+	        var s = '{' + col.name + '}';
+	        if (template.contains(s)) {
+	            arr.push(col.name);
+	        }
+	    }, this);
+	    return arr;
+	},
+	
+	/**
+	 * APIMethod: fillTemplate
+	 * Actually does the work of getting the data from the store
+	 * and creating a single item based on the provided template
+	 * 
+	 * Parameters: 
+	 * index - the index of the data in the store to use in populating the
+	 *          template.
+	 * template - the template to fill
+	 * columnsNeeded - the array of columns needed by this template. should be 
+	 * 			obtained by calling parseTemplate().
+	 */
+	fillTemplate: function (index, template, columnsNeeded) {
+		index = $defined(index)? index : this.index;
+		
+	    //create the item
+	    var itemObj = {};
+	    columnsNeeded.each(function (col) {
+	        itemObj[col] = this.get(col, index);
+	    }, this);
+	    return template.substitute(itemObj);
+	}
+});// $Id: compare.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Compare
  *
@@ -16809,7 +18549,7 @@
         return (a === true && b === false) ? -1 : (a === b) ? 0 : 1;
     }
 
-});// $Id: $
+});// $Id: sort.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Sort Base class for all of the sorting algorithm classes.
  *
@@ -16947,7 +18687,7 @@
         this.comparator = fn;
     }
 });
-// $Id: $
+// $Id: mergesort.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * class: Jx.Sort.Mergesort
  *
@@ -17044,7 +18784,7 @@
     }
 
 });
-// $Id: $
+// $Id: heapsort.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Sort.Heapsort
  *
@@ -17146,7 +18886,7 @@
     }
 
 });
-// $Id: $
+// $Id: quicksort.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Sort.Quicksort
  *
@@ -17300,7 +19040,7 @@
         return k;
     }
 });
-// $Id: $
+// $Id: nativesort.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Sort.Nativesort
  *
@@ -17345,7 +19085,7 @@
     }
 
 });
-// $Id: $
+// $Id: response.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Response
  * 
@@ -17407,7 +19147,7 @@
 Jx.Store.Response.WAITING = 2;
 Jx.Store.Response.SUCCESS = 1;
 Jx.Store.Response.FAILURE = 0;
-// $Id: $
+// $Id: protocol.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Protocol
  * 
@@ -17490,7 +19230,7 @@
      * that subclasses should implement.
      */
     abort: $empty
-});// $Id: $
+});// $Id: protocol.local.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Protocol.Local
  * 
@@ -17575,7 +19315,7 @@
      * - commit
      * - abort
      */
-});// $Id: $
+});// $Id: protocol.ajax.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Protocol.Ajax
  * 
@@ -17785,7 +19525,7 @@
         return resp;
     }
     
-});// $Id: $
+});// $Id: strategy.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Store.Strategy
  * 
@@ -17813,6 +19553,10 @@
      */
     active: null,
     
+    /**
+     * Method: init
+     * initialize the strategy, should be called by subclasses
+     */
     init: function () {
         this.parent();
         this.active = false;
@@ -17851,10 +19595,7 @@
         }
         return false;
     }
-    
-    
-    
-});// $Id: $
+});// $Id: strategy.full.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Store.Strategy.Full
  * 
@@ -17875,7 +19616,10 @@
     name: 'full',
     
     options:{},
-    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
     init: function () {
         this.parent();
         this.bound = {
@@ -17884,19 +19628,27 @@
         }
     },
     
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
     activate: function () {
         this.parent();
         this.store.addEvent('storeLoad', this.bound.load);
         
     },
     
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
     deactivate: function () {
         this.parent();
         this.store.removeEvent('storeLoad', this.bound.load);
         
     },
     /**
-     * Method: load
+     * APIMethod: load
      * Called as the eventhandler for the store load method. Can also
      * be called independently to load data into the current store.
      * 
@@ -17916,6 +19668,7 @@
         opts.data.itemsPerPage = -1;
         this.store.protocol.read(opts);
     },
+    
     /**
      * Method: loadStore
      * Called as the event hanlder for the protocol's dataLoaded event. Checks 
@@ -17955,9 +19708,9 @@
             this.store.options.recordOptions.primaryKey = meta.primaryKey;
         }
     }
-});// $Id: $
+});// $Id: strategy.paginate.js 776 2010-03-22 14:35:16Z pagameba $
 /**
- * Class: Jx.Store.Strategy.Paginat
+ * Class: Jx.Store.Strategy.Paginate
  * 
  * Extends: <Jx.Store.Strategy>
  * 
@@ -17984,7 +19737,7 @@
             return {
                 page: this.page,
                 itemsPerPage: this.itemsPerPage
-            }
+            };
         },
         /**
          * Option: startingItemsPerPage
@@ -18000,15 +19753,16 @@
         startingPage: 1,
         /**
          * Option: expirationInterval
-         * The interval, in milliseconds (1000 = 1 sec), to hold a page of data
-         * before it expires. If the page is expired, the next time the page
-         * is accessed it must be retrieved again. Default is 5 minutes (1000 * 60 * 5)
+         * The interval, in milliseconds (1000 = 1 sec), to hold a page of
+         * data before it expires. If the page is expired, the next time the
+         * page is accessed it must be retrieved again. Default is 5 minutes
+         * (1000 * 60 * 5)
          */
         expirationInterval: (1000 * 60 * 5),
         /**
          * Option: ignoreExpiration
-         * Set to TRUE to ignore the expirationInterval setting and never expire
-         * pages.
+         * Set to TRUE to ignore the expirationInterval setting and never
+         * expire pages.
          */
         ignoreExpiration: false
     },
@@ -18034,6 +19788,10 @@
      */
     itemsPerPage: null,
     
+    /**
+     * Method: init
+     * initialize this strategy
+     */
     init: function () {
         this.parent();
         //set up bindings that we need here
@@ -18045,11 +19803,19 @@
         this.page = this.options.startingPage;
     },
     
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
     activate: function () {
         this.parent();
         this.store.addEvent('storeLoad', this.bound.load);
     },
     
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
     deactivate: function () {
         this.parent();
         this.store.removeEvent('storeLoad', this.bound.load);
@@ -18064,13 +19830,14 @@
     load: function (params) {
         this.store.fireEvent('storeBeginDataLoad', this.store);
         this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        this.params = params;
         var opts = {
             data: $merge(params, this.options.getPaginationParams.apply(this))
         };
         this.store.protocol.read(opts);
     },
     /**
-     * MethodL loadStore
+     * Method: loadStore
      * Used to assist in the loading of data into the store. This is 
      * called as a response to the protocol finishing.
      * 
@@ -18091,9 +19858,9 @@
     },
     /**
      * Method: loadData
-     * This method does the actual work of loading data to the store. It is called
-     * when either the protocol finishes or setPage() has the data and it's not
-     * expired.
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
      * 
      * Parameters:
      * data - the data to load into the store.
@@ -18111,8 +19878,8 @@
     },
     /**
      * Method: parseMetaData
-     * Takes the metadata returned from the protocol and places it in the appropriate
-     * places.
+     * Takes the metadata returned from the protocol and places it in the
+     * appropriate Vplaces.
      * 
      * Parameters:
      * meta - the meta data object returned from the protocol.
@@ -18230,9 +19997,139 @@
     getTotalCount: function () {
         return this.totalItems;
     }
+});
+Jx.Store.Strategy.Progressive = new Class({
     
+    Extends: Jx.Store.Strategy.Paginate,
     
-});// $Id: $
+    name: 'progressive',
+    
+    options: {
+        /**
+         * Option: maxRecords
+         * The maximum number of records we want in the store at any one time.
+         */
+        maxRecords: 1000,
+        /**
+         * Option: dropRecords
+         * Whether the strategy should drop records when the maxRecords limit 
+         * is reached. if this is false then maxRecords is ignored and data is
+         * always added to the bottom of the store. 
+         */
+        dropRecords: true
+    },
+    /**
+     * Property: startingPage
+     */
+    startingPage: 0,
+    /**
+     * Property: maxPages
+     */
+    maxPages: null,
+    /**
+     * Property: loadedPages
+     */
+    loadedPages: 0,
+    /**
+     * Property: loadAt
+     */
+    loadAt: 'bottom',
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        if (this.options.dropPages) {
+            this.maxPages = Math.ceil(this.options.maxRecords/this.itemsPerPage);
+        }
+    },
+    
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.loaded = false;
+        this.store.addRecords(data, this.loadAt);
+        this.store.loaded = true;
+        this.loadedPages++;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    
+    /**
+     * APIMethod: nextPage
+     * Allows a caller (i.e. a paging toolbar) to load more data to the end of 
+     * the store
+     * 
+     * Parameters:
+     * end - which end to load to. Either 'top' or 'bottom'.
+     */
+    nextPage: function (params) {
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.options.dropPages && this.totalPages > this.startingPage + this.loadedPages) {
+            this.loadAt = 'bottom';
+            if (this.loadedPages >= this.maxPages) {
+                //drop records before getting more
+                this.startingPage++;
+                this.store.removeRecords(0,this.itemsPerPage - 1);
+                this.loadedPages--;
+            }
+        }
+        this.page = this.startingPage + this.loadedPages + 1;
+        this.load($merge(this.params, params));
+    },
+    
+    previousPage: function (params) {
+        //if we're not dropping pages there's nothing todo
+        if (!this.options.dropPages) {
+            return;
+        }
+        
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.startingPage > 0) {
+            this.loadAt = 'top';
+            if (this.loadedPages >= this.maxPages) {
+                //drop off end before loading previous pages
+                this.startingPage--;
+                this.store.removeRecords(this.options.maxRecords - this.itemsPerPage, this.options.maxRecords);
+                this.loadedPages--;
+            }
+            this.page = this.startingPage;
+            this.load($merge(this.params, params));
+        }
+    }
+});// $Id: strategy.save.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Store.Strategy.Save 
  * 
@@ -18277,6 +20174,10 @@
      */
     totalChanges: 0,
     
+    /**
+     * Method: init
+     * initialize this strategy
+     */
     init: function () {
         this.parent();
         this.bound = {
@@ -18285,6 +20186,10 @@
         };
     },
     
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
     activate: function () {
         this.parent();
         if (Jx.type(this.options.autoSave) === 'number') {
@@ -18297,6 +20202,10 @@
         
     },
     
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
     deactivate: function () {
         this.parent();
         if ($defined(this.periodicalId)) {
@@ -18311,8 +20220,8 @@
     
     /**
      * APIMethod: saveRecord
-     * Called by event handlers when store data is changed, updated, or deleted.
-     * If deleted, the record will be removed from the deleted array.
+     * Called by event handlers when store data is changed, updated, or
+     * deleted. If deleted, the record will be removed from the deleted array.
      * 
      * Parameters:
      * record - The Jx.Record instance that was changed
@@ -18348,21 +20257,23 @@
      */
     save: function () {
         //go through all of the data and figure out what needs to be acted on
-        var records = [];
-        records[Jx.Record.UPDATE] = [];
-        records[Jx.Record.INSERT] = [];
+        if (this.store.loaded) {
+            var records = [];
+            records[Jx.Record.UPDATE] = [];
+            records[Jx.Record.INSERT] = [];
+            
+            this.store.data.each(function (record) {
+                if ($defined(record) && $defined(record.state)) {
+                    records[record.state].push(record);
+                }
+            }, this);
+            records[Jx.Record.DELETE] = this.store.deleted;
+            
+            records.flatten().each(function (record) {
+                this.saveRecord(record);
+            }, this);
+        }
         
-        this.store.data.each(function (record) {
-            if ($defined(record) && $defined(record.state)) {
-                records[record.state].push(record);
-            }
-        }, this);
-        records[Jx.Record.DELETE] = this.store.deleted;
-        
-        records.flatten().each(function (record) {
-            this.saveRecord(record);
-        }, this);
-        
     },
     /**
      * Method: onComplete
@@ -18371,9 +20282,9 @@
      * come back failed we will hold that response and send it to the caller
      * via the fired event. This method is responsible for updating the status
      * of each record as it returns and on inserts, it updates the primary key
-     * of the record. If it was a delete it will remove it permanently from the
-     * store's deleted array (provided it returns successful - based on the 
-     * success attribute of the meta object). When all changes have been 
+     * of the record. If it was a delete it will remove it permanently from
+     * the store's deleted array (provided it returns successful - based on
+     * the success attribute of the meta object). When all changes have been 
      * accounted for the method fires a finished event and passes all of the 
      * failed responses to the caller so they can be handled appropriately.
      * 
@@ -18410,10 +20321,8 @@
                 failed: this.failedChanges
             });
         }
-            
     }
-    
-});// $Id: $
+});// $Id: strategy.sort.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Store.Strategy.Sort
  * 
@@ -18442,8 +20351,8 @@
         sortOnStoreEvents: ['storeColumnChanged','storeDataLoaded'],
         /**
          * Option: defaultSort
-         * The default sorting type, currently set to merge but can be any of the
-         * sorters available
+         * The default sorting type, currently set to merge but can be any of
+         * the sorters available
          */
         defaultSort : 'merge',
         /**
@@ -18471,6 +20380,10 @@
         'native' : "Nativesort"
     },
     
+    /**
+     * Method: init
+     * initialize this strategy
+     */
     init: function () {
         this.parent();
         this.bound = {
@@ -18478,6 +20391,10 @@
         };
     },
     
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
     activate: function () {
         if ($defined(this.options.sortOnStoreEvents)) {
             this.options.sortOnStoreEvents.each(function (ev) {
@@ -18486,6 +20403,10 @@
         }
     },
     
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
     deactivate: function () {
         if ($defined(this.options.sortOnStoreEvents)) {
             this.options.sortOnStoreEvents.each(function (ev) {
@@ -18500,16 +20421,14 @@
      * 
      * Parameters: 
      * cols - Optional. An array of columns to sort/group by 
-     * sort - the sort type (quick,heap,merge,native),defaults to options.defaultSort
+     * sort - the sort type (quick,heap,merge,native),defaults to
+     *     options.defaultSort
      * dir - the direction to sort. Set to "desc" for descending,
      * anything else implies ascending (even null). 
      */
     sort : function (cols, sort, dir) {
-        
         if (this.store.count()) {
-        
             this.store.fireEvent('sortStart', this);
-            
             var c;
             if ($defined(cols) && Jx.type(cols) === 'array') {
                 c = this.options.sortCols = cols;
@@ -18668,7 +20587,7 @@
         }
         return col;   
     }
-});// $Id: $
+});// $Id: parser.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Parser
  * 
@@ -18705,7 +20624,7 @@
      * object - an object to encode
      */
     encode: $empty
-});// $Id: $
+});// $Id: parser.json.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Store.Parser.JSON
  * 
@@ -18765,7 +20684,7 @@
             
         return JSON.encode(data);
     }
-});// $Id: button.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: button.js 808 2010-03-27 18:07:49Z pagameba $
 /**
  * Class: Jx.Button
  *
@@ -18774,41 +20693,11 @@
  * Jx.Button creates a clickable element that can be added to a web page.
  * When the button is clicked, it fires a 'click' event.
  *
- * The CSS styling for a button is controlled by several classes related
- * to the various objects in the button's HTML structure:
- *
- * (code)
- * <div class="jxButtonContainer">
- *  <a class="jxButton">
- *   <span class="jxButtonContent">
- *    <img class="jxButtonIcon" src="image_url">
- *    <span class="jxButtonLabel">button label</span>
- *   </span>
- *  </a>
- * </div>
- * (end)
- *
- * The CSS classes will change depending on the type option passed to the
- * constructor of the button.  The default type is Button.  Passing another
- * value such as Tab will cause all the CSS classes to change from jxButton
- * to jxTab.  For example:
- *
- * (code)
- * <div class="jxTabContainer">
- *  <a class="jxTab">
- *   <span class="jxTabContent">
- *    <img class="jxTabIcon" src="image_url">
- *    <span class="jxTabLabel">tab label</span>
- *   </span>
- *  </a>
- * </div>
- * (end)
- *
  * When you construct a new instance of Jx.Button, the button does not
  * automatically get inserted into the web page.  Typically a button
  * is used as part of building another capability such as a Jx.Toolbar.
  * However, if you want to manually insert the button into your application,
- * you may use the addTo method to append or insert the button into the
+ * you may use the <Jx.Button::addTo> method to append or insert the button into the
  * page.
  *
  * There are two modes for a button, normal and toggle.  A toggle button
@@ -18862,13 +20751,6 @@
     Family: 'Jx.Button',
     Extends: Jx.Widget,
 
-    /**
-     * the HTML element that is inserted into the DOM for this button.  You
-     * may reference this object to append it to the DOM or remove it from
-     * the DOM if necessary.
-     */
-    domObj: null,
-
     options: {
         /* Option: id
          * optional.  A string value to use as the ID of the button
@@ -18897,9 +20779,20 @@
          * default true, whether the button is a toggle button or not.
          */
         toggle: false,
-
+        /* Option: toggleClass
+         * A class to apply to the button if it is a toggle button,
+         * 'jxButtonToggle' by default.
+         */
         toggleClass: 'jxButtonToggle',
+        /* Option: pressedClass
+         * A class to apply to the button when it is pressed,
+         * 'jxButtonPressed' by default.
+         */
         pressedClass: 'jxButtonPressed',
+        /* Option: activeClass
+         * A class to apply to the buttonwhen it is active,
+         * 'jxButtonActive' by default.
+         */
         activeClass: 'jxButtonActive',
 
         /* Option: active
@@ -18911,15 +20804,26 @@
          * whether the button is enabled or not.
          */
         enabled: true,
+        /* Option: href
+         * set an href on the button's action object, typically an <a> tag.
+         * Default is javascript:void(0) and use onClick.
+         */
+        href: 'javascript:void(0);',
         /* Option: template
          * the HTML structure of the button.  As a minimum, there must be a
-         * containing element with a class of jxButtonContainer and an internal
-         * element with a class of jxButton.  jxButtonIcon and jxButtonLabel are
-         * used if present to put the image and label into the button.
+         * containing element with a class of jxButtonContainer and an
+         * internal element with a class of jxButton.  jxButtonIcon and
+         * jxButtonLabel are used if present to put the image and label into
+         * the button.
          */
         template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
     },
 
+    /**
+     * Property: classes
+     * used to auto-populate this object with element references when
+     * processing templates
+     */
     classes: new Hash({
         domObj: 'jxButtonContainer',
         domA: 'jxButton',
@@ -18928,7 +20832,7 @@
     }),
 
     /**
-     * APIMethod: render
+     * Method: render
      * create a new button.
      */
     render: function() {
@@ -18944,7 +20848,7 @@
             var hasFocus;
             var mouseDown;
             this.domA.set({
-                href: 'javascript:void(0)',
+                href: this.options.href,
                 title: this.options.tooltip,
                 alt: this.options.tooltip
             });
@@ -18995,7 +20899,7 @@
                     title: this.options.tooltip,
                     alt: this.options.tooltip
                 });
-                if (this.options.image && this.options.image.indexOf('a_pixel.png') == -1) {
+                if (this.options.image && this.options.image.indexOf(Jx.aPixel.src) == -1) {
                     this.domImg.setStyle('backgroundImage',"url("+this.options.image+")");
                 }
                 if (this.options.imageClass) {
@@ -19031,7 +20935,7 @@
 
     },
     /**
-     * Method: clicked
+     * APIMethod: clicked
      * triggered when the user clicks the button, processes the
      * actionPerformed event
      *
@@ -19039,7 +20943,7 @@
      * evt - {Event} the user click event
      */
     clicked : function(evt) {
-        if (this.options.enabled) {
+        if (this.options.enabled && !this.isBusy()) {
             if (this.options.toggle) {
                 this.setActive(!this.options.active);
             } else {
@@ -19049,7 +20953,7 @@
         //return false;
     },
     /**
-     * Method: isEnabled
+     * APIMethod: isEnabled
      * This returns true if the button is enabled, false otherwise
      *
      * Returns:
@@ -19060,7 +20964,7 @@
     },
 
     /**
-     * Method: setEnabled
+     * APIMethod: setEnabled
      * enable or disable the button.
      *
      * Parameters:
@@ -19075,7 +20979,7 @@
         }
     },
     /**
-     * Method: isActive
+     * APIMethod: isActive
      * For toggle buttons, this returns true if the toggle button is
      * currently active and false otherwise.
      *
@@ -19086,28 +20990,30 @@
         return this.options.active;
     },
     /**
-     * Method: setActive
+     * APIMethod: setActive
      * Set the active state of the button
      *
      * Parameters:
      * active - {Boolean} the new active state of the button
      */
     setActive: function(active) {
-        if (this.options.active == active) {
-            return;
+        if (this.options.enabled && !this.isBusy()) {
+          if (this.options.active == active) {
+              return;
+          }
+          this.options.active = active;
+          if (this.domA) {
+              if (this.options.active) {
+                  this.domA.addClass(this.options.activeClass);
+              } else {
+                  this.domA.removeClass(this.options.activeClass);
+              }
+          }
+          this.fireEvent(active ? 'down':'up', this);
         }
-        this.options.active = active;
-        if (this.domA) {
-            if (this.options.active) {
-                this.domA.addClass(this.options.activeClass);
-            } else {
-                this.domA.removeClass(this.options.activeClass);
-            }
-        }
-        this.fireEvent(active ? 'down':'up', this);
     },
     /**
-     * Method: setImage
+     * APIMethod: setImage
      * set the image of this button to a new image URL
      *
      * Parameters:
@@ -19122,12 +21028,10 @@
         }
     },
     /**
-     * Method: setLabel
-     *
+     * APIMethod: setLabel
      * sets the text of the button.
      *
      * Parameters:
-     *
      * label - {String} the new label for the button
      */
     setLabel: function(label) {
@@ -19138,15 +21042,14 @@
         }
     },
     /**
-     * Method: getLabel
-     *
+     * APIMethod: getLabel
      * returns the text of the button.
      */
     getLabel: function() {
         return this.options.label;
     },
     /**
-     * Method: setTooltip
+     * APIMethod: setTooltip
      * sets the tooltip displayed by the button
      *
      * Parameters:
@@ -19159,9 +21062,21 @@
                 'alt':tooltip
             });
         }
+        //need to account for the tooltip on the image as well
+        if (this.domImg) {
+            //check if title and alt are set...
+            var t = this.domImg.get('title');
+            if ($defined(t)) {
+                //change it...
+                this.domImg.set({
+                    'title':tooltip,
+                    'alt':tooltip
+                });
+            }
+        }
     },
     /**
-     * Method: focus
+     * APIMethod: focus
      * capture the keyboard focus on this button
      */
     focus: function() {
@@ -19170,7 +21085,7 @@
         }
     },
     /**
-     * Method: blur
+     * APIMethod: blur
      * remove the keyboard focus from this button
      */
     blur: function() {
@@ -19179,14 +21094,12 @@
         }
     }
 });
-// $Id: flyout.js 602 2009-11-10 19:41:36Z pagameba $
+// $Id: flyout.js 768 2010-03-18 16:22:50Z fred.warnock $
 /**
  * Class: Jx.Button.Flyout
  *
  * Extends: <Jx.Button>
  *
- * Implements: <Jx.ContentLoader>, <Jx.AutoPosition>, <Jx.Chrome>
- *
  * Flyout buttons expose a panel when the user clicks the button.  The
  * panel can have arbitrary content.  You must provide any necessary 
  * code to hook up elements in the panel to your application.
@@ -19213,9 +21126,6 @@
  * flyout buttons inside the content area of another flyout button.  In this
  * case, opening the inner flyout will not close the outer flyout but it will
  * close any other flyouts that are siblings.
- * 
- * The options argument takes a combination of options that apply to <Jx.Button>,
- * <Jx.ContentLoader>, and <Jx.AutoPosition>.
  *
  * Example:
  * (code)
@@ -19243,12 +21153,22 @@
 Jx.Button.Flyout = new Class({
     Family: 'Jx.Button.Flyout',
     Extends: Jx.Button,
-    
+    Binds: ['keypressHandler', 'clickHandler'],
     options: {
+        /* Option: template
+         * the HTML structure of the flyout button
+         */
         template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',
+        /* Option: contentTemplate
+         * the HTML structure of the flyout content area
+         */
         contentTemplate: '<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>'
     },
     
+    /**
+     * Property: contentClasses
+     * the classes array for processing the contentTemplate
+     */
     contentClasses: new Hash({
         contentContainer: 'jxFlyout',
         content: 'jxFlyoutContent'
@@ -19260,7 +21180,7 @@
      */
     content: null,
     /**
-     * APIMethod: render
+     * Method: render
      * construct a new instance of a flyout button.  
      */
     render: function() {
@@ -19276,11 +21196,9 @@
         
         this.content.store('jxFlyout', this);
         this.loadContent(this.content);
-        this.keypressWatcher = this.keypressHandler.bindWithEvent(this);
-        this.hideWatcher = this.clickHandler.bindWithEvent(this);
     },
     /**
-     * Method: clicked
+     * APIMethod: clicked
      * Override <Jx.Button::clicked> to hide/show the content area of the
      * flyout.
      *
@@ -19331,6 +21249,7 @@
         }
         // now we go on the stack.
         Jx.Button.Flyout.Stack.push(this);
+        this.fireEvent('beforeOpen');
 
         this.options.active = true;
         this.domA.addClass(this.options.activeClass);
@@ -19354,14 +21273,15 @@
          */
         this.contentContainer.setContentBoxSize(document.id(this.content).getMarginBoxSize());
         
+        this.stack(this.contentContainer);
         this.contentContainer.setStyle('visibility','');
 
-        document.addEvent('keydown', this.keypressWatcher);
-        document.addEvent('click', this.hideWatcher);
+        document.addEvent('keydown', this.keypressHandler);
+        document.addEvent('click', this.clickHandler);
         this.fireEvent('open', this);
     },
     /**
-     * Method: hide
+     * APIMethod: hide
      * Closes the flyout if open
      */
     hide: function() {
@@ -19371,11 +21291,15 @@
         Jx.Button.Flyout.Stack.pop();
         this.setActive(false);
         this.contentContainer.dispose();
-        document.removeEvent('keydown', this.keypressWatcher);    
-        document.removeEvent('click', this.hideWatcher);
+        this.unstack(this.contentContainer);
+        document.removeEvent('keydown', this.keypressHandler);    
+        document.removeEvent('click', this.clickHandler);
         this.fireEvent('close', this);
     },
-    /* hide flyout if the user clicks outside of the flyout */
+    /**
+     * Method: clickHandler
+     * hide flyout if the user clicks outside of the flyout 
+     */
     clickHandler: function(e) {
         e = new Event(e);
         var elm = document.id(e.target);
@@ -19385,14 +21309,17 @@
             flyout.hide();
         }
     },
-    /* hide flyout if the user presses the ESC key */
+    /**
+     * Method: keypressHandler
+     * hide flyout if the user presses the ESC key 
+     */
     keypressHandler: function(e) {
         e = new Event(e);
         if (e.key == 'esc') {
             Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
         }
     }
-});// $Id: layout.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: layout.js 718 2010-03-02 16:22:06Z pagameba $
 /**
  * Class: Jx.Layout
  *
@@ -19426,6 +21353,12 @@
     Extends: Jx.Object,
 
     options: {
+        /* Option: resizeWithWindow
+         * boolean, automatically resize this layout when the window size
+         * changes, even if the element is not a direct descendant of the
+         * BODY.  False by default.
+         */
+        resizeWithWindow: false,
         /* Option: propagate
          * boolean, controls propogation of resize to child nodes.
          * True by default. If set to false, changes in size will not be
@@ -19520,7 +21453,7 @@
         this.domObj.setStyle('position', this.options.position);
         this.domObj.store('jxLayout', this);
 
-        if (document.body == this.domObj.parentNode) {
+        if (this.options.resizeWithWindow || document.body == this.domObj.parentNode) {
             window.addEvent('resize', this.windowResize.bindWithEvent(this));
             window.addEvent('load', this.windowResize.bind(this));
         }
@@ -19818,7 +21751,7 @@
 
         this.fireEvent('sizeChange',this);
     }
-});// $Id: tab.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: tab.js 762 2010-03-16 13:48:29Z pagameba $
 /**
  * Class: Jx.Button.Tab
  *
@@ -19866,18 +21799,69 @@
     Extends: Jx.Button,
     /**
      * Property: content
-     * {HTMLElement} The content area that is displayed when the tab is active.
+     * {HTMLElement} The content area that is displayed when the tab is
+     * active.
      */
     content: null,
 
     options: {
+        /* Option: toggleClass
+         * the CSS class to use for the button, 'jxTabToggle' by default
+         */
         toggleClass: 'jxTabToggle',
+        /* Option: pressedClass
+         * the CSS class to use when the tab is pressed, 'jxTabPressed' by
+         * default
+         */
         pressedClass: 'jxTabPressed',
+        /* Option: activeClass
+         * the CSS class to use when the tab is active, 'jxTabActive' by 
+         * default.
+         */
         activeClass: 'jxTabActive',
+        /* Option: activeTabClass
+         * the CSS class to use on the content area of the active tab,
+         * 'tabContentActive' by default.
+         */
         activeTabClass: 'tabContentActive',
+        /* Option: template
+         * the HTML template for a tab
+         */
         template: '<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></span>',
-        contentTemplate: '<div class="tabContent"></div>'
+        /* Option: contentTemplate
+         * the HTML template for a tab's content area
+         */
+        contentTemplate: '<div class="tabContent"></div>',
+        /* Option: close
+         * {Boolean} can the tab be closed by the user?  False by default.
+         */
+        close: false,
+        /* Option: shouldClose
+         * {Mixed} when a tab is closeable, the shouldClose option is checked
+         * first to see if the tab should close.  You can provide a function
+         * for this option that can be used to return a boolean value.  This
+         * is useful if your tab contains something the user can edit and you
+         * want to see if they want to discard the changes before closing.
+         * The default value is true, meaning the tab will close immediately.
+         * (code)
+         * new Jx.Tab({
+         *   label: 'test close',
+         *   close: true,
+         *   shouldClose: function() {
+         *     return window.confirm('Are you sure?');
+         *   }
+         * });
+         * (end)
+         */
+        shouldClose: true
     },
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
     classes: new Hash({
         domObj: 'jxTabContainer',
         domA: 'jxTab',
@@ -19888,7 +21872,7 @@
     }),
 
     /**
-     * APIMethod: render
+     * Method: render
      * Create a new instance of Jx.Button.Tab.  Any layout options passed are used
      * to create a <Jx.Layout> for the tab content area.
      */
@@ -19911,7 +21895,17 @@
             if (this.options.close) {
                 this.domObj.addClass('jxTabClose');
                 this.domClose.addEvent('click', (function(){
+                  var shouldClose = true;
+                  if ($defined(this.options.shouldClose)) {
+                    if (typeof this.options.shouldClose == 'function') {
+                      shouldClose = this.options.shouldClose();
+                    } else {
+                      shouldClose = this.options.shouldClose;
+                    }
+                  }
+                  if (shouldClose) {
                     this.fireEvent('close');
+                  }
                 }).bind(this));
             } else {
                 this.domClose.dispose();
@@ -19919,7 +21913,7 @@
         }
     },
     /**
-     * Method: clicked
+     * APIMethod: clicked
      * triggered when the user clicks the button, processes the
      * actionPerformed event
      */
@@ -19928,7 +21922,7 @@
             this.setActive(true);
         }
     }
-});// $Id: colorpalette.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: colorpalette.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.ColorPalette
  *
@@ -19939,8 +21933,9 @@
  * palette of web-safe colours.  The user can also enter an opacity value.
  *
  * A Jx.ColorPalette can be embedded anywhere in a web page using its addTo
- * method.  However, a <Jx.Button> suJx.Tooltipbclass is provided (<Jx.Button.Color>)
- * that embeds a colour panel inside a button for easy use in toolbars.
+ * method.  However, a <Jx.Button> suJx.Tooltipbclass is provided
+ * (<Jx.Button.Color>) that embeds a colour panel inside a button for easy use
+ * in toolbars.
  *
  * Colour changes are propogated via a change event.  To be notified
  * of changes in a Jx.ColorPalette, use the addEvent method.
@@ -19953,6 +21948,10 @@
  * change - triggered when the color changes.
  * click - the user clicked on a color swatch (emitted after a change event)
  *
+ * MooTools.lang keys:
+ * - colorpalette.alphaLabel
+ * 
+ * 
  * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
  *
@@ -19983,14 +21982,10 @@
          * an array of hex colors for creating the palette, defaults to a
          * set of web safe colors.
          */
-        hexColors: ['00', '33', '66', '99', 'CC', 'FF'],
-        /* Option: alphaLabel
-         * the text to display next to the alpha input for i18n.
-         */
-        alphaLabel: 'alpha (%)'
+        hexColors: ['00', '33', '66', '99', 'CC', 'FF']
     },
     /**
-     * APIMethod: render
+     * Method: render
      * initialize a new instance of Jx.ColorPalette
      */
     render: function() {
@@ -20009,7 +22004,10 @@
 
         top.adopt(d);
 
-        this.colorInputLabel = new Element('label', {'class':'jxColorLabel', html:'#'});
+        this.colorInputLabel = new Element('label', {
+          'class':'jxColorLabel', 
+          html:'#'
+        });
         top.adopt(this.colorInputLabel);
 
         var cc = this.changed.bind(this);
@@ -20026,7 +22024,7 @@
 
         top.adopt(this.colorInput);
 
-        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':this.options.alphaLabel});
+        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':MooTools.lang.get('Jx','colorpalette').alphaLabel});
         top.adopt(this.alphaLabel);
 
         this.alphaInput = new Element('input', {
@@ -20088,7 +22086,9 @@
                     g = j%6;
                     b = i%6;
                 }
-                var bgColor = '#'+this.options.hexColors[r]+this.options.hexColors[g]+this.options.hexColors[b];
+                var bgColor = '#'+this.options.hexColors[r]+
+                                  this.options.hexColors[g]+
+                                  this.options.hexColors[b];
 
                 var td = new Element('td');
                 if (!bSkip) {
@@ -20180,7 +22180,7 @@
     },
 
     /**
-     * Method: setColor
+     * APIMethod: setColor
      * set the colour represented by this colour panel
      *
      * Parameters:
@@ -20192,7 +22192,7 @@
     },
 
     /**
-     * Method: setAlpha
+     * APIMethod: setAlpha
      * set the alpha represented by this colour panel
      *
      * Parameters:
@@ -20217,17 +22217,39 @@
         if (this.options.alpha < 1) {
             styles.opacity = this.options.alpha;
             styles.filter = 'Alpha(opacity='+(this.options.alpha*100)+')';
+            
         } else {
-            styles.opacity = '';
-            styles.filter = '';
+            styles.opacity = 1;
+            //not sure what the proper way to remove the filter would be since
+            // I don't have IE to test against.
+            styles.filter = '';  
         }
         this.selectedSwatch.setStyles(styles);
         this.previewSwatch.setStyles(styles);
+        
         this.fireEvent('change', this);
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the
+     * widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	
+    	if ($defined(this.alphaLabel)) {
+    		this.alphaLabel.set('html', MooTools.lang.get('Jx','colorpalette').alphaLabel);
+    	}
     }
 });
 
-// $Id: color.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: color.js 762 2010-03-16 13:48:29Z pagameba $
 /**
  * Class: Jx.Button.Color
  *
@@ -20265,6 +22287,11 @@
     Family: 'Jx.Button.Color',
     Extends: Jx.Button.Flyout,
 
+    /**
+     * Property: swatch
+     * the color swatch element used to portray the currently selected
+     * color
+     */
     swatch: null,
 
     options: {
@@ -20281,9 +22308,20 @@
          *
          */
         alpha: 100,
+        /*
+         * Option: template
+         * the HTML template for the color button
+         */
         template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span class="jxButtonLabel"></span></span></a></span>'
     },
 
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
     classes: new Hash({
         domObj: 'jxButtonContainer',
         domA: 'jxButton',
@@ -20292,7 +22330,7 @@
     }),
 
     /**
-     * APIMethod: render
+     * Method: render
      * creates a new color button.
      */
     render: function() {
@@ -20315,7 +22353,7 @@
     },
 
     /**
-     * Method: clicked
+     * APIMethod: clicked
      * override <Jx.Button.Flyout> to use a singleton color palette.
      */
     clicked: function() {
@@ -20339,7 +22377,7 @@
 },
 
     /**
-     * Method: hide
+     * APIMethod: hide
      * hide the color panel
      */
     hide: function() {
@@ -20351,7 +22389,7 @@
     },
 
     /**
-     * Method: setColor
+     * APIMethod: setColor
      * set the color represented by this color panel
      *
      * Parameters:
@@ -20363,7 +22401,7 @@
     },
 
     /**
-     * Method: setAlpha
+     * APIMethod: setAlpha
      * set the alpha represented by this color panel
      *
      * Parameters:
@@ -20409,13 +22447,13 @@
             styles.opacity = this.options.alpha / 100;
 
         } else {
-            styles.opacity = '';
+            styles.opacity = 1;
             styles.filter = '';
         }
         this.swatch.setStyles(styles);
     }
 });
-// $Id: menu.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: menu.js 817 2010-03-30 12:13:04Z pagameba $
 /**
  * Class: Jx.Menu
  *
@@ -20521,9 +22559,9 @@
      *
      * Parameters:
      * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
-     * can be added by passing multiple arguments to this function.
-     * position -
-     * owner -
+     *     can be added by passing an array of menu items.
+     * position - the index to add the item at, defaults to the end of the
+     *     menu
      */
     add: function(item, position, owner) {
         if (Jx.type(item) == 'array') {
@@ -20560,6 +22598,18 @@
         return this;
     },
     /**
+     * APIMethod: empty
+     * Empty the menu of items
+     */
+    empty: function() {
+      this.list.each(function(item){
+        if (item.empty) {
+          item.empty();
+        }
+      }, this);
+      this.list.empty();
+    },
+    /**
      * Method: deactivate
      * Deactivate the menu by hiding it.
      */
@@ -20652,6 +22702,7 @@
         this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
         document.removeEvent('mousedown', this.bound.mousedown);
         document.removeEvent('keydown', this.bound.keypress);
+        this.unstack(this.contentContainer);
         this.contentContainer.dispose();
         this.visibleItem = null;
         this.fireEvent('hide', this);
@@ -20694,7 +22745,7 @@
         this.position(this.contentContainer, this.domObj, $merge({
             offsets: this.chromeOffsets
         }, this.options.position));
-
+        this.stack(this.contentContainer);
         this.contentContainer.setStyle('visibility','visible');
 
         if (this.button && this.button.domA) {
@@ -20828,25 +22879,25 @@
 
 });
 
-// $Id: set.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: set.js 762 2010-03-16 13:48:29Z pagameba $
 /**
  * Class: Jx.ButtonSet
  *
  * Extends: <Jx.Object>
  *
- * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only one
- * of the buttons is active.  All the buttons need to have been created with
- * the toggle option set to true for this to work.
+ * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only
+ * one of the buttons is active.  All the buttons need to have been created
+ * with the toggle option set to true for this to work.
  *
  * Example:
  * (code)
  * var toolbar = new Jx.Toolbar('bar');
  * var buttonSet = new Jx.ButtonSet();
  *
- * var tab1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
- * var tab2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
- * var tab3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
- * var tab4 = new Jx.Button({label: 'b4', toggle:true, contentURL: 'test_content.html'});
+ * var b1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
+ * var b2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
+ * var b3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
+ * var b4 = new Jx.Button({label: 'b4', toggle:true, contentID: 'content4'});
  *
  * buttonSet.add(b1,b2,b3,b4);
  * (end)
@@ -20862,27 +22913,21 @@
 Jx.ButtonSet = new Class({
     Family: 'Jx.ButtonSet',
     Extends: Jx.Object,
+    Binds: ['buttonChanged'],
     /**
      * Property: buttons
      * {Array} array of buttons that are managed by this button set
      */
-    buttons: null,
-    /**
-     * APIMethod: init
-     * initializes the button set.
-     */
-    init : function() {
-        this.buttons = [];
-        this.buttonChangedHandler = this.buttonChanged.bind(this);
-    },
+    buttons: [],
 
     /**
-     * Method: add
+     * APIMethod: add
      * Add one or more <Jx.Button>s to the ButtonSet.
      *
      * Parameters:
-     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button set.  More
-     * than one button can be added by passing extra parameters to this method.
+     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button
+     * set.  More than one button can be added by passing extra parameters to
+     * this method.
      */
     add : function() {
         $A(arguments).each(function(button) {
@@ -20890,7 +22935,7 @@
                 button.domObj.removeClass(button.options.toggleClass);
                 button.domObj.addClass(button.options.toggleClass+'Set');
             }
-            button.addEvent('down',this.buttonChangedHandler);
+            button.addEvent('down',this.buttonChanged);
             button.setActive = function(active) {
                 if (button.options.active && this.activeButton == button) {
                     return;
@@ -20907,7 +22952,7 @@
         return this;
     },
     /**
-     * Method: remove
+     * APIMethod: remove
      * Remove a button from this Button.
      *
      * Parameters:
@@ -20919,12 +22964,20 @@
             if (this.buttons.length) {
                 this.buttons[0].setActive(true);
             }
-            button.removeEvent('down',this.buttonChangedHandler);
+            button.removeEvent('down',this.buttonChanged);
             button.setActive = Jx.Button.prototype.setActive;
         }
     },
     /**
-     * Method: setActiveButton
+     * APIMethod: empty
+     * empty the button set and clear the active button
+     */
+    empty: function() {
+      this.buttons = [];
+      this.activeButton = null;
+    },
+    /**
+     * APIMethod: setActiveButton
      * Set the active button to the one passed to this method
      *
      * Parameters:
@@ -20938,7 +22991,7 @@
         }
     },
     /**
-     * Method: selectionChanged
+     * Method: buttonChanged
      * Handle selection changing on the buttons themselves and activate the
      * appropriate button in response.
      *
@@ -20949,7 +23002,7 @@
         this.setActiveButton(button);
         this.fireEvent('change', this);
     }
-});// $Id: multi.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: multi.js 762 2010-03-16 13:48:29Z pagameba $
 /**
  * Class: Jx.Button.Multi
  *
@@ -21008,13 +23061,14 @@
  */
 Jx.Button.Multi = new Class({
     Family: 'Jx.Button.Multi',
+    Extends: Jx.Button,
 
-    Extends: Jx.Button,
     /**
      * Property: {<Jx.Button>} activeButton
      * the currently selected button
      */
     activeButton: null,
+
     /**
      * Property: buttons
      * {Array} the buttons added to this multi button
@@ -21022,8 +23076,19 @@
     buttons: null,
 
     options: {
+        /* Option: template
+         * the button template for a multi button
+         */
         template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>'
     },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
     classes: new Hash({
         domObj: 'jxButtonContainer',
         domA: 'jxButton',
@@ -21032,9 +23097,8 @@
         domDisclose: 'jxButtonDisclose'
     }),
 
-
     /**
-     * APIMethod: render
+     * Method: render
      * construct a new instance of Jx.Button.Multi.
      */
     render: function() {
@@ -21079,8 +23143,8 @@
 
                     this.contentContainer.setStyle('visibility','');
 
-                    document.addEvent('mousedown', this.hideWatcher);
-                    document.addEvent('keyup', this.keypressWatcher);
+                    document.addEvent('mousedown', this.bound.mousedown);
+                    document.addEvent('keyup', this.bound.keypress);
 
                     this.fireEvent('show', this);
                 }).bindWithEvent(this.menu),
@@ -21137,7 +23201,7 @@
         }
     },
     /**
-     * Method: add
+     * APIMethod: add
      * adds one or more buttons to the Multi button.  The first button
      * added becomes the active button initialize.  This function
      * takes a variable number of arguments, each of which is expected
@@ -21179,7 +23243,7 @@
         }, this);
     },
     /**
-     * Method: remove
+     * APIMethod: remove
      * remove a button from a multi button
      *
      * Parameters:
@@ -21212,7 +23276,14 @@
         }
     },
     /**
-     * Method: setActiveButton
+     * APIMethod: empty
+     * remove all buttons from the multi button
+     */
+    empty: function() {
+      this.buttons.each(function(b){this.remove(b);}, this);
+    },
+    /**
+     * APIMethod: setActiveButton
      * update the menu item to be the requested button.
      *
      * Parameters:
@@ -21366,7 +23437,7 @@
     }
 });
 
-// $Id: combo.js 647 2009-11-26 16:23:24Z pagameba $
+// $Id: combo.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Button.Combo
  *
@@ -21465,7 +23536,7 @@
             }
             this.contentContainer.setStyle('visibility','hidden');
             this.contentContainer.setStyle('display','block');
-            $(document.body).adopt(this.contentContainer);
+            document.id(document.body).adopt(this.contentContainer);
             /* we have to size the container for IE to render the chrome correctly
              * but just in the menu/sub menu case - there is some horrible peekaboo
              * bug in IE related to ULs that we just couldn't figure out
@@ -21515,8 +23586,12 @@
         return this.options.label;
     },
 
-    setValue: function() {
-
+    setValue: function(value) {
+        this.buttonSet.buttons.each(function(button){
+            if (button.options.label === value) {
+              button.setActive(true);
+            }
+        },this);
     },
 
     /**
@@ -21561,8 +23636,19 @@
      */
     remove: function(idx) {
         //TODO: implement remove?
+    },
+    
+    /**
+     * APIMethod: empty
+     * remove all items from the combo
+     */
+    empty: function() {
+      this.menu.empty();
+      this.buttonSet.empty();
+      this.setLabel('');
+      this.setImage(Jx.aPixel.src);
     }
-});// $Id: toolbar.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: toolbar.js 801 2010-03-27 15:04:05Z pagameba $
 /**
  * Class: Jx.Toolbar
  *
@@ -21739,6 +23825,10 @@
             }
             this.list.add(item);
         }, this);
+        
+        //Update the size of the toolbar container.
+        this.update();
+        
         return this;
     },
     /**
@@ -21759,9 +23849,17 @@
         }
         var li = item.findElement('LI');
         this.list.remove(li);
+        this.update();
         return this;
     },
     /**
+     * APIMethod: empty
+     * remove all items from the toolbar
+     */
+    empty: function() {
+      this.list.each(function(item){this.remove(item);},this);
+    },
+    /**
      * Method: deactivate
      * Deactivate the Toolbar (when it is acting as a menu bar).
      */
@@ -21814,11 +23912,36 @@
             this.visibleItem.show();
         }
     },
+    
     showItem: function(item) {
         this.fireEvent('show', item);
+    },
+    /**
+     * Method: update
+     * Updates the size of the UL so that the size is always consistently the 
+     * exact size of the size of the sum of the buttons. This will keep all of 
+     * the buttons on one line.
+     */
+    update: function () {
+        // if (['top','bottom'].contains(this.options.position)) {
+        //     (function(){
+        //         var s = 0;
+        //         var children = this.domObj.getChildren();
+        //         children.each(function(button){
+        //             var size = button.getMarginBoxSize();
+        //             s += size.width +0.5;
+        //         },this);
+        //         if (s !== 0) {
+        //             this.domObj.setStyle('width', Math.round(s));
+        //         } else {
+        //             this.domObj.setStyle('width','auto');
+        //         }
+        //     }).delay(1,this);
+        // }
+        this.fireEvent('update');
     }
 });
-// $Id: container.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: container.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Toolbar.Container
  *
@@ -21842,14 +23965,18 @@
  *
  * This file is licensed under an MIT style license
  */
+
 Jx.Toolbar.Container = new Class({
+
     Family: 'Jx.Toolbar.Container',
     Extends: Jx.Widget,
+    Binds: ['update'],
+    pluginNamespace: 'ToolbarContainer',
     /**
      * Property: domObj
      * {HTMLElement} the HTML element that the container lives in
      */
-    domObj : null,
+    domObj: null,
     options: {
         /* Option: parent
          * a DOM element to add this to
@@ -21871,32 +23998,42 @@
          * Default is true.
          */
         scroll: true,
-        template: "<div class='jxBarContainer'></div>",
-        scrollerTemplate: "<div class='jxBarScroller'></div>"
+        template: "<div class='jxBarContainer'><div class='jxBarControls'></div></div>",
+        scrollerTemplate: "<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"
     },
     classes: new Hash({
         domObj: 'jxBarContainer',
-        scroller: 'jxBarScroller'
+        scroller: 'jxBarScroller',
+        //used to hide the overflow of the wrapper
+        wrapper: 'jxBarWrapper',
+        controls: 'jxBarControls'
+        //used to allow multiple toolbars to float next to each other
     }),
+
+    updating: false,
+
     /**
      * APIMethod: render
      * Create a new instance of Jx.Toolbar.Container
      */
-    render : function() {
+    render: function() {
         this.parent();
         /* if a container was passed in, use it instead of the one from the
          * template
          */
         if (document.id(this.options.parent)) {
             this.domObj = document.id(this.options.parent);
-            this.elements = new Hash({'jxBarContainer':this.domObj});
+            this.elements = new Hash({
+                'jxBarContainer': this.domObj
+            });
             this.domObj.addClass('jxBarContainer');
-            this.domObj.adopt(this.scroller);
+            this.domObj.grab(this.controls);
+            this.domObj.addEvent('sizeChange', this.update);
         }
 
         if (this.options.scroll) {
             this.processElements(this.options.scrollerTemplate, this.classes);
-            this.domObj.adopt(this.scroller);
+            this.domObj.grab(this.scroller, 'top');
         }
 
         /* this allows toolbars to add themselves to this bar container
@@ -21905,149 +24042,135 @@
          */
         this.domObj.store('jxBarContainer', this);
 
-        if (['top','right','bottom','left'].contains(this.options.position)) {
+        if (['top', 'right', 'bottom', 'left'].contains(this.options.position)) {
             this.domObj.addClass('jxBar' +
-                           this.options.position.capitalize());
+            this.options.position.capitalize());
         } else {
             this.domObj.addClass('jxBarTop');
             this.options.position = 'top';
         }
 
-        if (this.options.scroll && ['top','bottom'].contains(this.options.position)) {
+        if (this.options.scroll && ['top', 'bottom'].contains(this.options.position)) {
             // make sure we update our size when we get added to the DOM
-            this.addEvent('addTo', this.update.bind(this));
+            this.addEvent('addTo', function(){
+              this.domObj.getParent().addEvent('sizeChange', this.update);
+              this.update();
+            });
 
-            //making Fx.Tween optional
-            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined'){
-                this.scrollFx = scrollFx = new Fx.Tween(this.scroller, {
-                    link: 'chain'
-                });
-            }
-
             this.scrollLeft = new Jx.Button({
                 image: Jx.aPixel.src
-            }).addTo(this.domObj);
-            this.scrollLeft.domObj.addClass('jxBarScrollLeft');
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollLeft).addClass('jxBarScrollLeft');
             this.scrollLeft.addEvents({
-               click: (function(){
-                   var from = this.scroller.getStyle('left').toInt();
-                   if (isNaN(from)) { from = 0; }
-                   var to = Math.min(from+100, 0);
-                   if (to >= 0) {
-                       this.scrollLeft.domObj.setStyle('visibility', 'hidden');
-                   }
-                   this.scrollRight.domObj.setStyle('visibility', '');
-                   if ($defined(this.scrollFx)){
-                       this.scrollFx.start('left', from, to);
-                   } else {
-                       this.scroller.setStyle('left',to);
-                   }
-               }).bind(this)
+                click: this.scroll.bind(this, 'left')
             });
 
             this.scrollRight = new Jx.Button({
                 image: Jx.aPixel.src
-            }).addTo(this.domObj);
-            this.scrollRight.domObj.addClass('jxBarScrollRight');
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollRight).addClass('jxBarScrollRight');
             this.scrollRight.addEvents({
-               click: (function(){
-                   var from = this.scroller.getStyle('left').toInt();
-                   if (isNaN(from)) { from = 0; }
-                   var to = Math.max(from - 100, this.scrollWidth);
-                   if (to == this.scrollWidth) {
-                       this.scrollRight.domObj.setStyle('visibility', 'hidden');
-                   }
-                   this.scrollLeft.domObj.setStyle('visibility', '');
-                   if ($defined(this.scrollFx)){
-                       this.scrollFx.start('left', from, to);
-                   } else {
-                       this.scroller.setStyle('left',to);
-                   }
-               }).bind(this)
+                click: this.scroll.bind(this, 'right')
             });
 
+        } else if (this.options.scroll && ['left', 'right'].contains(this.options.position)) {
+            //do we do scrolling up and down?
+            //for now disable scroll in this case
+            this.options.scroll = false;
         } else {
             this.options.scroll = false;
         }
 
+        this.addEvent('add', this.update);
         if (this.options.toolbars) {
             this.add(this.options.toolbars);
         }
     },
 
     update: function() {
-        if (this.options.autoSize) {
-            /* delay the size update a very small amount so it happens
-             * after the current thread of execution finishes.  If the
-             * current thread is part of a window load event handler,
-             * rendering hasn't quite finished yet and the sizes are
-             * all wrong
-             */
-            (function(){
-                var x = 0;
-                this.scroller.getChildren().each(function(child){
-                    x+= child.getSize().x;
-                });
-                this.domObj.setStyles({width:x});
-                this.measure();
-            }).delay(1,this);
-        } else {
-            this.measure();
-        }
-    },
+        if (this.options.scroll) {
+            if (['top', 'bottom'].contains(this.options.position)) {
+                var tbcSize = this.domObj.getContentBoxSize().width;
 
-    measure: function() {
-        if (!this.options.scroll) { return; }
+                var s = 0;
+                //next check to see if we need the scrollers or not.
+                var children = this.wrapper.getChildren();
+                if (children.length > 0) {
+                    children.each(function(tb) {
+                        s += tb.getMarginBoxSize().width;
+                    },
+                    this);
 
-        if ((!this.scrollLeftSize || !this.scrollLeftSize.x) && this.domObj.parentNode) {
-            this.scrollLeftSize = this.scrollLeft.domObj.getSize();
-            this.scrollRightSize = this.scrollRight.domObj.getSize();
-        }
-        /* decide if we need to show the scroller buttons and
-         * do some calculations that will make it faster
-         */
-        this.scrollWidth = this.domObj.getSize().x;
-        this.scroller.getChildren().each(function(child){
-            this.scrollWidth -= child.getSize().x;
-        }, this);
-        if (this.scrollWidth < 0) {
-            /* we need to show scrollers on at least one side */
-            var l = this.scroller.getStyle('left').toInt();
-            if (l < 0) {
-                this.scrollLeft.domObj.setStyle('visibility','');
-            } else {
-                this.scrollLeft.domObj.setStyle('visibility','hidden');
-            }
-            if (l <= this.scrollWidth) {
-                this.scrollRight.domObj.setStyle('visibility', 'hidden');
-                if (l < this.scrollWidth) {
-                    if ($defined(this.scrollFx)){
-                        this.scrollFx.start('left', l, this.scrollWidth);
+                    var scrollerSize = tbcSize;
+
+                    if (s === 0) {
+                        this.scrollLeft.setEnabled(false);
+                        this.scrollRight.setEnabled(false);
                     } else {
-                        this.scroller.setStyle('left',this.scrollWidth);
+
+
+                        var leftMargin = this.wrapper.getStyle('margin-left').toInt();
+                        scrollerSize -= this.controls.getMarginBoxSize().width;
+
+
+                        if (leftMargin < 0) {
+                            //has been scrolled left so activate the right scroller
+                            this.scrollLeft.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollLeft.setEnabled(false);
+                        }
+
+                        if (s + leftMargin > scrollerSize) {
+                            //we need the right one
+                            this.scrollRight.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollRight.setEnabled(false);
+                        }
                     }
-                }
-            } else {
-                this.scrollRight.domObj.setStyle('visibility', '');
-            }
 
-        } else {
-            /* don't need any scrollers but we might need to scroll
-             * the toolbar into view
-             */
-            this.scrollLeft.domObj.setStyle('visibility','hidden');
-            this.scrollRight.domObj.setStyle('visibility','hidden');
-            var from = this.scroller.getStyle('left').toInt();
-            if (!isNaN(from) && from !== 0) {
-                if ($defined(this.scrollFx)) {
-                    this.scrollFx.start('left', 0);
                 } else {
-                    this.scroller.setStyle('left',0);
+                    this.scrollRight.setEnabled(false);
+                    this.scrollLeft.setEnabled(false);
                 }
+                this.scroller.setStyle('width', scrollerSize);
+
+                this.findFirstVisible();
+                this.updating = false;
             }
         }
     },
+    /**
+     * Method: findFirstVisible
+     * Finds the first visible button on the toolbar and saves a reference in 
+     * the scroller object
+     */
+    findFirstVisible: function() {
+        if ($defined(this.scroller.retrieve('buttonPointer'))) {
+            return;
+        };
 
+        var children = this.wrapper.getChildren();
+
+        if (children.length > 0) {
+            children.each(function(toolbar) {
+                var buttons = toolbar.getChildren();
+                if (buttons.length > 1) {
+                    buttons.each(function(button) {
+                        var pos = button.getCoordinates(this.scroller);
+                        if (pos.left >= 0 && !$defined(this.scroller.retrieve('buttonPointer'))) {
+                            //this is the first visible button
+                            this.scroller.store('buttonPointer', button);
+                        }
+                    },
+                    this);
+                }
+            },
+            this);
+        }
+    },
+
     /**
      * Method: add
      * Add a toolbar to the container.
@@ -22056,31 +24179,108 @@
      * toolbar - {Object} the toolbar to add.  More than one toolbar
      *    can be added by passing multiple arguments.
      */
-    add: function( ) {
+    add: function() {
         $A(arguments).flatten().each(function(thing) {
             if (this.options.scroll) {
                 /* we potentially need to show or hide scroller buttons
                  * when the toolbar contents change
                  */
-                thing.addEvent('add', this.update.bind(this));
-                thing.addEvent('remove', this.update.bind(this));
+                thing.addEvent('update', this.update.bind(this));
                 thing.addEvent('show', this.scrollIntoView.bind(this));
             }
-            if (this.scroller) {
-                this.scroller.adopt(thing.domObj);
+            if (this.wrapper) {
+                this.wrapper.adopt(thing.domObj);
             } else {
                 this.domObj.adopt(thing.domObj);
             }
-            this.domObj.addClass('jxBar'+this.options.position.capitalize());
-        }, this);
-        if (this.options.scroll) {
-            this.update();
-        }
+            this.domObj.addClass('jxBar' + this.options.position.capitalize());
+        },
+        this);
         if (arguments.length > 0) {
             this.fireEvent('add', this);
         }
         return this;
     },
+
+    scroll: function(direction) {
+        if (this.updating) {
+            return
+        };
+        this.updating = true;
+
+        var currentButton = this.scroller.retrieve('buttonPointer');
+        if (direction === 'left') {
+            //need to tween the amount of the previous button
+            var previousButton = this.scroller.retrieve('previousPointer');
+            if (!previousButton) {
+                previousButton = this.getPreviousButton(currentButton);
+            }
+            if (previousButton) {
+                var w = previousButton.getMarginBoxSize().width;
+                var ml = this.wrapper.getStyle('margin-left').toInt();
+                ml += w;
+                if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                    //scroll it
+                    this.wrapper.get('tween', {
+                        property: 'margin-left',
+                        onComplete: this.afterTweenLeft.bind(this, previousButton)
+                    }).start(ml);
+                } else {
+                    //set it
+                    this.wrapper.setStyle('margin-left', ml);
+                    this.afterTweenLeft(previousButton);
+                }
+            } else {
+                this.update();
+            }
+        } else {
+            //must be right
+            var w = currentButton.getMarginBoxSize().width;
+
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= w;
+
+            //now, if Fx is defined tween the margin to the left to
+            //hide the current button
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+
+        }
+    },
+
+    afterTweenRight: function(currentButton) {
+        var np = this.getNextButton(currentButton);
+        if (!np) {
+            np = currentButton;
+        }
+        this.scroller.store('buttonPointer', np);
+        if (np !== currentButton) {
+            this.scroller.store('previousPointer', currentButton);
+        }
+        this.update();
+    },
+
+    afterTweenLeft: function(previousButton) {
+        this.scroller.store('buttonPointer', previousButton);
+        var pp = this.getPreviousButton(previousButton);
+        if ($defined(pp)) {
+            this.scroller.store('previousPointer', pp);
+        } else {
+            this.scroller.eliminate('previousPointer');
+        }
+        this.update();
+    },
+
+
     /**
      * Method: remove
      * remove an item from a toolbar.  If the item is not in this toolbar
@@ -22094,7 +24294,12 @@
      * removed.
      */
     remove: function(item) {
-
+        if (item instanceof Jx.Widget) {
+            item.dispose();
+        } else {
+            document.id(item).dispose();
+        }
+        this.update();
     },
     /**
      * Method: scrollIntoView
@@ -22105,62 +24310,98 @@
      * item - the item to scroll.
      */
     scrollIntoView: function(item) {
-        var width = this.domObj.getSize().x;
-        var coords = item.domObj.getCoordinates(this.scroller);
+        var currentButton = this.scroller.retrieve('buttonPointer');
+        // if (!$defined(currentButton)) return;
+        if (item instanceof Jx.Widget) {
+            item = item.domObj;
+            while (!item.hasClass('jxToolItem')) {
+                item = item.getParent();
+            }
+        }
+        var pos = item.getCoordinates(this.scroller);
+        var scrollerSize = this.scroller.getStyle('width').toInt();
 
-        //left may be set to auto or even a zero length string.
-        //In the previous version, in air, this would evaluate to
-        //NaN which would cause the right hand scroller to show when
-        //the component was first created.
+        if (pos.right > 0 && pos.right <= scrollerSize && pos.left > 0 && pos.left <= scrollerSize) {
+           //we are completely on screen 
+            return;
+        };
 
-        //So, get the left value first
-        var l = this.scroller.getStyle('left');
-        //then check to see if it's auto or a zero length string
-        if (l === 'auto' || l.length <= 0) {
-            //If so, set to 0.
-            l = 0;
+        if (pos.right > scrollerSize) {
+            //it's right of the scroller
+            var diff = pos.right - scrollerSize;
+
+            //loop through toolbar items until we have enough width to
+            //make the item visible
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            var w = currentButton.getMarginBoxSize().width;
+            var np;
+            while (w < diff && $defined(currentButton)) {
+                np = this.getNextButton(currentButton);
+                if (np) {
+                    w += np.getMarginBoxSize().width;
+                } else {
+                    break;
+                }
+                currentButton = np;
+            }
+
+            ml -= w;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
         } else {
-            //otherwise, convert to int
-            l = l.toInt();
+            //it's left of the scroller
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= pos.left;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenLeft.bind(this, item)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenLeft(item);
+            }
         }
-        var slSize = this.scrollLeftSize ? this.scrollLeftSize.x : 0;
-        var srSize = this.scrollRightSize ? this.scrollRightSize.x : 0;
 
-        var left = l;
-        if (l < -coords.left + slSize) {
-            /* the left edge of the item is not visible */
-            left = -coords.left + slSize;
-            if (left >= 0) {
-                left = 0;
+    },
+
+    getPreviousButton: function(currentButton) {
+        pp = currentButton.getPrevious();
+        if (!$defined(pp)) {
+            //check for a new toolbar
+            pp = currentButton.getParent().getPrevious();
+            if (pp) {
+                pp = pp.getLast();
             }
-        } else if (width - coords.right - srSize< l) {
-            /* the right edge of the item is not visible */
-            left =  width - coords.right - srSize;
-            if (left < this.scrollWidth) {
-                left = this.scrollWidth;
-            }
         }
+        return pp;
+    },
 
-        if (left < 0) {
-            this.scrollLeft.domObj.setStyle('visibility','');
-        } else {
-            this.scrollLeft.domObj.setStyle('visibility','hidden');
-        }
-        if (left <= this.scrollWidth) {
-            this.scrollRight.domObj.setStyle('visibility', 'hidden');
-        } else {
-            this.scrollRight.domObj.setStyle('visibility', '');
-        }
-        if (left != l) {
-            if ($defined(this.scrollFx)) {
-                this.scrollFx.start('left', left);
-            } else {
-                this.scroller.setStyle('left',left);
+    getNextButton: function(currentButton) {
+        np = currentButton.getNext();
+        if (!np) {
+            np = currentButton.getParent().getNext();
+            if (np) {
+                np = np.getFirst();
             }
         }
+        return np;
     }
-});
-// $Id: toolbar.item.js 626 2009-11-20 13:22:22Z pagameba $
+
+});// $Id: toolbar.item.js 626 2009-11-20 13:22:22Z pagameba $
 /**
  * Class: Jx.Toolbar.Item
  *
@@ -22203,7 +24444,7 @@
             this.domObj.adopt(el);
         }
     }
-});// $Id: panel.js 633 2009-11-20 17:54:21Z zak4ms $
+});// $Id: panel.js 770 2010-03-18 21:12:28Z jonlb at comcast.net $
 /**
  * Class: Jx.Panel
  *
@@ -22223,6 +24464,17 @@
  * close - fired when the panel is closed
  * collapse - fired when the panel is collapsed
  * expand - fired when the panel is opened
+ * 
+ * MooTools.lang Keys:
+ * - panel.collapseTooltip
+ * - panel.collapseLabel
+ * - panel.expandlabel
+ * - panel.maximizeTooltip
+ * - panel.maximizeLabel
+ * - panel.restoreTooltip
+ * - panel.restoreLabel
+ * - panel.closeTooltip
+ * - panel.closeLabel
  *
  * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
@@ -22267,26 +24519,6 @@
          * to control the state of the panel.
          */
         collapse: true,
-        /* Option: collapseTooltip
-         * the tooltip to display over the collapse button
-         */
-        collapseTooltip: 'Collapse/Expand Panel',
-        /* Option: collapseLabel
-         * the label to use for the collapse menu item
-         */
-        collapseLabel: 'Collapse',
-        /* Option: expandLabel
-         * the label to use for the expand menu item
-         */
-        expandLabel: 'Expand',
-        /* Option: maximizeTooltip
-         * the tooltip to display over the maximize button
-         */
-        maximizeTooltip: 'Maximize Panel',
-        /* Option: maximizeLabel
-         * the label to use for the maximize menu item
-         */
-        maximizeLabel: 'Maximize',
         /* Option: close
          * boolean, determine if the panel can be closed (hidden) by the user.
          * The application needs to provide a way to re-open the panel after
@@ -22295,14 +24527,6 @@
          * the panel.
          */
         close: false,
-        /* Option: closeTooltip
-         * the tooltip to display over the close button
-         */
-        closeTooltip: 'Close Panel',
-        /* Option: closeLabel
-         * the label to use for the close menu item
-         */
-        closeLabel: 'Close',
         /* Option: closed
          * boolean, initial state of the panel (true to start the panel
          *  closed), default is false
@@ -22317,7 +24541,9 @@
          * of each toolbar is used to position the toolbar within the panel.
          */
         toolbars: [],
-        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>'
+        type: 'panel',
+        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',
+        controlButtonTemplate: '<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'
     },
     classes: new Hash({
         domObj: 'jxPanel',
@@ -22352,76 +24578,106 @@
         this.toolbar = new Jx.Toolbar({parent:tbDiv, scroll: false});
 
         var that = this;
-
         if (this.options.menu) {
             this.menu = new Jx.Menu({
                 image: Jx.aPixel.src
+            }, {
+              buttonTemplate: this.options.controlButtonTemplate
             });
             this.menu.domObj.addClass(this.options.menuClass);
             this.menu.domObj.addClass('jxButtonContentLeft');
             this.toolbar.add(this.menu);
         }
 
-        var b, item;
+        //var b, item;
         if (this.options.collapse) {
-            b = new Jx.Button({
+            this.colB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
                 image: Jx.aPixel.src,
-                tooltip: this.options.collapseTooltip,
+                tooltip: MooTools.lang.get('Jx','panel').collapseTooltip,
                 onClick: function() {
                     that.toggleCollapse();
                 }
             });
-            b.domObj.addClass(this.options.collapseClass);
-            this.toolbar.add(b);
+            this.colB.domObj.addClass(this.options.collapseClass);
+            this.addEvents({
+                collapse: function() {
+                    this.colB.setTooltip(MooTools.lang.get('Jx','panel').expandTooltip);
+                }.bind(this),
+                expand: function() {
+                    this.colB.setTooltip(MooTools.lang.get('Jx','panel').collapseTooltip);
+                }.bind(this)
+            });
+            this.toolbar.add(this.colB);
             if (this.menu) {
-                item = new Jx.Menu.Item({
+                this.colM = new Jx.Menu.Item({
                     label: this.options.collapseLabel,
                     onClick: function() { that.toggleCollapse(); }
                 });
+                var item = this.colM
                 this.addEvents({
                     collapse: function() {
-                        item.setLabel(this.options.expandLabel);
-                    },
+                        this.colM.setLabel(MooTools.lang.get('Jx','panel').expandLabel);
+                    }.bind(this),
                     expand: function() {
-                        item.setLabel(this.options.collapseLabel);
-                    }
+                        this.colM.setLabel(MooTools.lang.get('Jx','panel').collapseLabel);
+                    }.bind(this)
                 });
                 this.menu.add(item);
             }
         }
 
         if (this.options.maximize) {
-            b = new Jx.Button({
+            this.maxB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
                 image: Jx.aPixel.src,
-                tooltip: this.options.maximizeTooltip,
+                tooltip: MooTools.lang.get('Jx','panel').maximizeTooltip,
                 onClick: function() {
                     that.maximize();
                 }
             });
-            b.domObj.addClass(this.options.maximizeClass);
-            this.toolbar.add(b);
+            this.maxB.domObj.addClass(this.options.maximizeClass);
+            this.addEvents({
+                maximize: function() {
+                    this.maxB.setTooltip(MooTools.lang.get('Jx','panel').restoreTooltip);
+                }.bind(this),
+                restore: function() {
+                    this.maxB.setTooltip(MooTools.lang.get('Jx','panel').maximizeTooltip);
+                }.bind(this)
+            });
+            this.toolbar.add(this.maxB);
             if (this.menu) {
-                item = new Jx.Menu.Item({
+                this.maxM = new Jx.Menu.Item({
                     label: this.options.maximizeLabel,
                     onClick: function() { that.maximize(); }
                 });
-                this.menu.add(item);
+                
+                this.addEvents({
+                    maximize: function() {
+                        this.maxM.setLabel(MooTools.lang.get('Jx','panel').restoreLabel);
+                    }.bind(this),
+                    restore: function() {
+                        this.maxM.setLabel(MooTools.lang.get('Jx','panel').maximizeLabel);
+                    }.bind(this)
+                });
+                this.menu.add(this.maxM);
             }
         }
 
         if (this.options.close) {
-            b = new Jx.Button({
+            this.closeB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
                 image: Jx.aPixel.src,
-                tooltip: this.options.closeTooltip,
+                tooltip: MooTools.lang.get('Jx','panel').closeTooltip,
                 onClick: function() {
                     that.close();
                 }
             });
-            b.domObj.addClass(this.options.closeClass);
-            this.toolbar.add(b);
+            this.closeB.domObj.addClass(this.options.closeClass);
+            this.toolbar.add(this.closeB);
             if (this.menu) {
-                item = new Jx.Menu.Item({
-                    label: this.options.closeLabel,
+                this.closeM = new Jx.Menu.Item({
+                    label: MooTools.lang.get('Jx','panel').closeLabel,
                     onClick: function() {
                         that.close();
                     }
@@ -22515,6 +24771,7 @@
             }, this);
             if (Jx.type(this.options.toolbars) == 'array') {
                 this.options.toolbars.each(function(tb){
+                    tb.update();
                     position = tb.options.position;
                     tbc = this.toolbarContainers[position];
                     // IE 6 doesn't seem to want to measure the width of
@@ -22660,20 +24917,6 @@
             window.setTimeout(this.onContentReady.bind(this),1);
         }
     },
-    /**
-     * Method: setBusy
-     * Set the panel as busy or not busy, which displays a loading image
-     * in the title bar.
-     *
-     * Parameters:
-     * isBusy - {Boolean} the busy state
-     */
-    setBusy : function(isBusy) {
-        this.busyCount += isBusy?1:-1;
-        if (this.loadingObj){
-            this.loadingObj.img.style.visibility = (this.busyCount>0)?'visible':'hidden';
-        }
-    },
 
     /**
      * Method: toggleCollapse
@@ -22722,9 +24965,32 @@
     close: function() {
         this.domObj.dispose();
         this.fireEvent('close', this);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();	//TODO: change this class so that we can access these properties without too much voodoo...
+    	if($defined(this.closeB)) {
+    		this.closeB.setTooltip(MooTools.lang.get('Jx','panel').closeTooltip);
+    	}
+    	if ($defined(this.closeM)) {
+    		this.closeM.setLabel(MooTools.lang.get('Jx','panel').closeLabel);
+    	}
+    	if ($defined(this.maxB)) {
+    		this.maxB.setTooltip(MooTools.lang.get('Jx','panel').maximizeTooltip);
+    	}
+    	if ($defined(this.colB)) {
+    		this.colB.setTooltip(MooTools.lang.get('Jx','panel').collapseTooltip);
+    	}
+    	if ($defined(this.colM)) {
+	    	if (this.options.closed == true) {
+	    		this.colM.setLabel(MooTools.lang.get('Jx','panel').expandLabel);
+	    	} else {
+	    		this.colM.setLabel(MooTools.lang.get('Jx','panel').collapseLabel);
+	    	}
+    	}
     }
 
-});// $Id: dialog.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: dialog.js 816 2010-03-30 11:57:09Z pagameba $
 /**
  * Class: Jx.Dialog
  *
@@ -22760,6 +25026,9 @@
  *
  * Extends:
  * Jx.Dialog extends <Jx.Panel>, please go there for more details.
+ * 
+ * MooTools.lang Keys:
+ * - dialog.resizeToolTip
  *
  * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
@@ -22769,22 +25038,30 @@
 Jx.Dialog = new Class({
     Family: 'Jx.Dialog',
     Extends: Jx.Panel,
-    //Implements: [Jx.AutoPosition, Jx.Chrome],
 
-    /**
-     * Property: {HTMLElement} blanket
-     * modal dialogs prevent interaction with the rest of the application
-     * while they are open, this element is displayed just under the
-     * dialog to prevent the user from clicking anything.
-     */
-    blanket: null,
-
     options: {
         /* Option: modal
          * (optional) {Boolean} controls whether the dialog will be modal
          * or not.  The default is to create modal dialogs.
          */
         modal: true,
+        /** 
+         * Option: maskOptions
+         */
+        maskOptions: {
+          'class':'jxModalMask',
+          maskMargins: true,
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          }
+        },
+        eventMaskOptions: {
+          'class':'jxEventMask',
+          maskMargins: false,
+          useIframeShim: false,
+          destroyOnHide: true
+        },
         /* just overrides default position of panel, don't document this */
         position: 'absolute',
         /* Option: width
@@ -22810,10 +25087,9 @@
          */
         vertical: 'center center',
         /* Option: label
-         * (optional) {String} the title of the dialog box.  "New Dialog"
-         * is the default value.
+         * (optional) {String} the title of the dialog box.
          */
-        label: 'New Dialog',
+        label: '',
         /* Option: id
          * (optional) {String} an HTML ID to assign to the dialog, primarily
          * used for applying CSS styles to specific dialogs
@@ -22830,10 +25106,7 @@
          * resizeable by the user or not.  Default is false.
          */
         resize: false,
-        /* Option: resizeTooltip
-         * the tooltip to display for the resize handle, empty by default.
-         */
-        resizeTooltip: '',
+
         /* Option: move
          * (optional) {Boolean} determines whether the dialog is
          * moveable by the user or not.  Default is true.
@@ -22849,7 +25122,7 @@
         menuClass: 'jxDialogMenu',
         maximizeClass: 'jxDialogMaximize',
         closeClass: 'jxDialogClose',
-
+        type: 'dialog',
         template: '<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'
     },
     classes: new Hash({
@@ -22880,26 +25153,6 @@
         this.openOnLoaded = this.open.bind(this);
         this.options.parent = document.id(this.options.parent);
 
-        if (this.options.modal) {
-            this.blanket = new Element('div',{
-                'class':'jxDialogModal',
-                styles:{
-                    display:'none',
-                    zIndex: -1
-                }
-            });
-            this.blanket.resize = (function() {
-                var ss = document.id(document.body).getScrollSize();
-                this.setStyles({
-                    width: ss.x,
-                    height: ss.y
-                });
-            }).bind(this.blanket);
-            this.options.parent.adopt(this.blanket);
-            window.addEvent('resize', this.blanket.resize);
-
-        }
-
         this.domObj.setStyle('display','none');
         this.options.parent.adopt(this.domObj);
 
@@ -22909,13 +25162,19 @@
             new Drag(this.domObj, {
                 handle: this.title,
                 onBeforeStart: (function(){
-                    Jx.Dialog.orderDialogs(this);
+                    this.stack();
                 }).bind(this),
                 onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
                     this.contentContainer.setStyle('visibility','hidden');
                     this.chrome.addClass('jxChromeDrag');
                 }).bind(this),
                 onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
                     this.chrome.removeClass('jxChromeDrag');
                     this.contentContainer.setStyle('visibility','');
                     var left = Math.max(this.chromeOffsets.left, parseInt(this.domObj.style.left,10));
@@ -22936,7 +25195,7 @@
         if (this.options.resize && typeof Drag != 'undefined') {
             this.resizeHandle = new Element('div', {
                 'class':'jxDialogResize',
-                title: this.options.resizeTooltip,
+                title: MooTools.lang.get('Jx','panel').resizeTooltip,
                 styles: {
                     'display':this.options.closed?'none':'block'
                 }
@@ -22951,6 +25210,9 @@
             this.domObj.makeResizable({
                 handle:this.resizeHandle,
                 onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
                     this.contentContainer.setStyle('visibility','hidden');
                     this.chrome.addClass('jxChromeDrag');
                 }).bind(this),
@@ -22958,6 +25220,9 @@
                     this.resizeChrome(this.domObj);
                 }).bind(this),
                 onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
                     this.chrome.removeClass('jxChromeDrag');
                     var size = this.domObj.getMarginBoxSize();
                     this.options.width = size.width;
@@ -22973,7 +25238,7 @@
         }
         /* this adjusts the zIndex of the dialogs when activated */
         this.domObj.addEvent('mousedown', (function(){
-            Jx.Dialog.orderDialogs(this);
+            this.stack();
         }).bind(this));
     },
 
@@ -23062,6 +25327,60 @@
         }
         this.showChrome(this.domObj);
     },
+    
+    /**
+     * Method: maximize
+     * Called when the maximize button of a dialog is clicked. It will maximize
+     * the dialog to match the size of its parent.
+     */
+    maximize: function () {
+        
+        if (!this.maximized) {
+            //get size of parent
+            var p = this.options.parent;
+            var size;
+            
+            if (p === document.body) {
+                size = Jx.getPageDimensions();
+            } else {
+                size = p.getBorderBoxSize();
+            }
+            this.previousSettings = {
+                width: this.options.width,
+                height: this.options.height,
+                horizontal: this.options.horizontal,
+                vertical: this.options.vertical,
+                left: this.options.left,
+                right: this.options.right,
+                top: this.options.top,
+                bottom: this.options.bottom
+            };
+            this.options.width = size.width;
+            this.options.height = size.height;
+            this.options.vertical = '0 top';
+            this.options.horizontal = '0 left';
+            this.options.right = 0;
+            this.options.left = 0;
+            this.options.top = 0;
+            this.options.bottom = 0;
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = true;
+            this.domObj.addClass('jxDialogMaximized');
+            this.fireEvent('maximize');
+        } else {
+            this.options = $merge(this.options, this.previousSettings);
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = false;
+            if (this.domObj.hasClass('jxDialogMaximized')) {
+                this.domObj.removeClass('jxDialogMaximized');
+            }
+            this.fireEvent('restore');
+        }
+    },
 
     /**
      * Method: show
@@ -23074,20 +25393,20 @@
             'display': 'block',
             'visibility': 'hidden'
         });
-
-        if (this.blanket) {
-            this.blanket.resize();
-        }
-
-        Jx.Dialog.orderDialogs(this);
-
+        this.toolbar.update();
+        
         /* do the modal thing */
-        if (this.blanket) {
-            this.blanket.setStyles({
-                visibility: 'visible',
-                display: 'block'
-            });
+        if (this.options.modal && this.options.parent.mask) {
+          var opts = $merge(this.options.maskOptions || {}, {
+            style: {
+              'z-index': Jx.getNumber(this.domObj.getStyle('z-index')) - 1
+            }
+          });
+          this.options.parent.mask(opts);
+          Jx.Stack.stack(this.options.parent.get('mask').element);
         }
+        /* stack the dialog */
+        this.stack();
 
         if (this.options.closed) {
             var m = this.domObj.measure(function(){
@@ -23098,6 +25417,7 @@
         } else {
             this.domObj.resize(this.options);
         }
+        
         if (this.firstShow) {
             this.contentContainer.resize({forceResize: true});
             this.layoutContent();
@@ -23115,7 +25435,7 @@
         this.showChrome(this.domObj);
         /* put it in the right place using auto-positioning */
         this.position(this.domObj, this.options.parent, this.options);
-        this.domObj.setStyle('visibility', '');
+        this.domObj.setStyle('visibility', 'visible');
     },
     /**
      * Method: hide
@@ -23123,12 +25443,11 @@
      * method to hide the dialog.
      */
     hide : function() {
-        Jx.Dialog.Stack.erase(this);
-        Jx.Dialog.ZIndex--;
         this.domObj.setStyle('display','none');
-        if (this.blanket) {
-            this.blanket.setStyle('visibility', 'hidden');
-            Jx.Dialog.ZIndex--;
+        this.unstack();
+        if (this.options.modal && this.options.parent.unmask) {
+          Jx.Stack.unstack(this.options.parent.get('mask').element);
+          this.options.parent.unmask();
         }
 
     },
@@ -23146,8 +25465,7 @@
             this.options.content = null;  //force Url loading
             this.loadContent(this.content);
             this.addEvent('contentLoaded', this.openOnLoaded);
-        }
-        else {
+        } else {
             this.open();
         }
     },
@@ -23183,28 +25501,33 @@
         this.fireEvent('close');
     },
 
-    cleanup: function() {
-        this.blanket.destroy();
+    cleanup: function() { },
+    
+    /**
+     * APIMethod: isOpen
+     * returns true if the dialog is currently open, false otherwise
+     */
+    isOpen: function () {
+        //check to see if we're visible
+        return !((this.domObj.getStyle('display') === 'none') || (this.domObj.getStyle('visibility') === 'hidden'));
+    },
+    
+    createText: function (lang) {
+    	this.parent();
+    	if ($defined(this.maxM)) {
+			if (this.maximize) {
+				this.maxM.setLabel(MooTools.lang.get('Jx','panel').restoreLabel);
+	    	} else {
+	    		this.maxM.setLabel(MooTools.lang.get('Jx','panel').maximizeLabel);
+	    	}
+    	}
+    	if ($defined(this.resizeHandle)) {
+    		this.resizeHandle.set('title', MooTools.lang.get('Jx','dialog').resizeTooltip);
+    	}
     }
 });
 
-Jx.Dialog.Stack = [];
-Jx.Dialog.BaseZIndex = null;
-Jx.Dialog.orderDialogs = function(d) {
-    Jx.Dialog.Stack.erase(d).push(d);
-    if (Jx.Dialog.BaseZIndex === null) {
-        Jx.Dialog.BaseZIndex = Math.max(Jx.Dialog.Stack[0].domObj.getStyle('zIndex').toInt(), 1);
-    }
-    Jx.Dialog.Stack.each(function(d, i) {
-        var z = Jx.Dialog.BaseZIndex+i;
-        if (d.blanket) {
-            d.blanket.setStyle('zIndex',z);
-        }
-        d.domObj.setStyle('zIndex',z);
-    });
-
-};
-// $Id: splitter.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: splitter.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Splitter
  *
@@ -23221,6 +25544,9 @@
  * Example:
  * (code)
  * (end)
+ * 
+ * MooTools.lang Keys:
+ * - splitter.barToolTip
  *
  * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
@@ -23300,11 +25626,6 @@
          * elements open or closed.
          */
         snaps: [],
-        /* Option: barTooltip
-         * the tooltip to display when the mouse hovers over a split bar,
-         * used for i18n.
-         */
-        barTooltip: 'drag this bar to resize',
         /* Option: onStart
          * an optional function to call when a bar starts dragging
          */
@@ -23453,7 +25774,7 @@
     prepareBar: function() {
         var o = new Element('div', {
             'class': 'jxSplitBar'+this.options.layout.capitalize(),
-            'title': this.options.barTitle
+            'title': MooTools.lang.get('Jx','panel').barTooltip
         });
         return o;
     },
@@ -23946,20 +26267,29 @@
                  currentPosition += rightBar.retrieve('size').height;
              }
          }
+    },
+    
+    createText: function (lang) {
+    	this.parent();
+    	this.bars.each(function(bar){
+    		document.id(bar).set('title', MooTools.lang.get('Jx','splitter').title);
+    	},this);
+    	
     }
-});// $Id: panelset.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: panelset.js 770 2010-03-18 21:12:28Z jonlb at comcast.net $
 /**
  * Class: Jx.PanelSet
  *
  * Extends: <Jx.Widget>
  *
- * A panel set manages a set of panels within a DOM element.  The PanelSet fills
- * its container by resizing the panels in the set to fill the width and then
- * distributing the height of the container across all the panels.  Panels
- * can be resized by dragging their respective title bars to make them taller
- * or shorter.  The maximize button on the panel title will cause all other
- * panels to be closed and the target panel to be expanded to fill the remaining
- * space.  In this respect, PanelSet works like a traditional Accordion control.
+ * A panel set manages a set of panels within a DOM element.  The PanelSet
+ * fills its container by resizing the panels in the set to fill the width and
+ * then distributing the height of the container across all the panels. 
+ * Panels can be resized by dragging their respective title bars to make them
+ * taller or shorter.  The maximize button on the panel title will cause all
+ * other panels to be closed and the target panel to be expanded to fill the
+ * remaining space.  In this respect, PanelSet works like a traditional
+ * Accordion control.
  *
  * When creating panels for use within a panel set, it is important to use the
  * proper options.  You must override the collapse option and set it to false
@@ -23968,11 +26298,14 @@
  *
  * Example:
  * (code)
- * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'content1'});
- * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'content2'});
- * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'content3'});
+ * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'c1'});
+ * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'c2'});
+ * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'c3'});
  * var panelSet = new Jx.PanelSet('panels', [p1,p2,p3]);
  * (end)
+ * 
+ * MooTools.lang Keys:
+ * - panelset.barTooltip
  *
  * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
@@ -23991,11 +26324,7 @@
         /* Option: panels
          * an array of <Jx.Panel> objects that will be managed by the set.
          */
-        panels: [],
-        /* Option: barTooltip
-         * the tooltip to place on the title bars of each panel
-         */
-        barTooltip: 'drag this bar to resize'
+        panels: []
     },
 
     /**
@@ -24045,7 +26374,7 @@
             prepareBar: (function(i) {
                 var bar = new Element('div', {
                     'class': 'jxPanelBar',
-                    'title': this.options.barTooltip
+                    'title': MooTools.lang.get('Jx','panelset').barTooltip
                 });
 
                 var panel = this.panels[i];
@@ -24174,15 +26503,24 @@
             }
         }
         panel.domObj.resize({top: top, height:panelSize, bottom: null});
+        this.fireEvent('panelMaximize',panel);
+    },
+    
+    createText: function (lang) {
+      this.parent();
+      //barTooltip is handled by the splitter's createText() function
     }
-});// $Id:$
+});// $Id: message.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Dialog.Message
  *
  * Extends: <Jx.Dialog>
  *
- * Jx.Dialog.Confirm is an extension of Jx.Dialog that allows the developer
+ * Jx.Dialog.Message is an extension of Jx.Dialog that allows the developer
  * to display a message to the user. It only presents an OK button.
+ * 
+ * MooTools.lang Keys:
+ * - message.okButton
  *
  * License:
  * Copyright (c) 2009, Jonathan Bomgardner
@@ -24190,9 +26528,9 @@
  * This file is licensed under an MIT style license
  */
 Jx.Dialog.Message = new Class({
-
+    Family: 'Jx.Dialog.Message',
     Extends: Jx.Dialog,
-
+    Binds: ['onOk'],
     options: {
         /**
          * Option: message
@@ -24200,27 +26538,43 @@
          */
         message: '',
         /**
-         * Jx.Dialog option defaults
+         * Option: width
+         * default width of message dialogs is 300px
          */
         width: 300,
+        /**
+         * Option: height
+         * default height of message dialogs is 150px
+         */
         height: 150,
+        /**
+         * Option: close
+         * by default, message dialogs are closable
+         */
         close: true,
+        /**
+         * Option: resize
+         * by default, message dialogs are resizable
+         */
         resize: true,
+        /**
+         * Option: collapse
+         * by default, message dialogs are not collapsible
+         */
         collapse: false
     },
     /**
-     * APIMethod: render
+     * Method: render
      * constructs the dialog.
      */
     render: function () {
         //create content to be added
-        this.buttons = new Jx.Toolbar({position: 'bottom'});
-        this.buttons.add(
-            new Jx.Button({
-                label: 'Ok',
-                onClick: this.onClick.bind(this, 'Ok')
-            })
-        );
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+            label: MooTools.lang.get('Jx','message').okButton,
+            onClick: this.onOk
+        });
+        this.buttons.add(this.ok);
         this.options.toolbars = [this.buttons];
         if (Jx.type(this.options.message) === 'string') {
             this.question = new Element('div', {
@@ -24229,22 +26583,46 @@
             });
         } else {
             this.question = this.options.question;
-            $(this.question).addClass('jxMessage');
+            document.id(this.question).addClass('jxMessage');
         }
         this.options.content = this.question;
         this.parent();
     },
     /**
-     * Method: onClick
+     * Method: onOk
      * Called when the OK button is clicked. Closes the dialog.
      */
-    onClick: function (value) {
+    onOk: function () {
         this.close();
+    },
+    
+    /**
+     * APIMethod: setMessage
+     * set the message of the dialog, useful for responding to language
+     * changes on the fly.
+     *
+     * Parameters
+     * message - {String} the new message
+     */
+    setMessage: function(message) {
+      this.options.message = message;
+      if ($defined(this.question)) {
+        this.question.set('html',message);
+      }
+    },
+    
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    createText: function (lang) {
+      this.parent();
+      if ($defined(this.ok)) {
+        this.ok.setLabel(MooTools.lang.get('Jx','message').okButton);
+      }
     }
-
-
 });
-// $Id:$
+// $Id: confirm.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Dialog.Confirm
  *
@@ -24252,7 +26630,11 @@
  *
  * Jx.Dialog.Confirm is an extension of Jx.Dialog that allows the developer
  * to prompt their user with e yes/no question.
- *
+ * 
+ * MooTools.lang Keys:
+ * - confirm.affirmitiveLabel
+ * - confirm.negativeLabel
+ * 
  * License:
  * Copyright (c) 2009, Jonathan Bomgardner
  *
@@ -24269,17 +26651,6 @@
          */
         question: '',
         /**
-         * Option: affirmitiveLabel
-         * The text to use for the affirmitive button. Defaults to 'Yes'.
-         */
-        affirmitiveLabel: 'Yes',
-        /**
-         * Option: negativeLabel
-         * The text to use for the negative button. Defaults to 'No'.
-         */
-        negativeLabel: 'No',
-
-        /**
          * Jx.Dialog option defaults
          */
         width: 300,
@@ -24294,17 +26665,18 @@
      */
     render: function () {
         //create content to be added
-        this.buttons = new Jx.Toolbar({position: 'bottom'});
-        this.buttons.add(
-            new Jx.Button({
-                label: this.options.affirmitiveLabel,
-                onClick: this.onClick.bind(this, this.options.affirmitiveLabel)
-            }),
-            new Jx.Button({
-                label: this.options.negativeLabel,
-                onClick: this.onClick.bind(this, this.options.negativeLabel)
-            })
-        );
+        //turn scrolling off as confirm only has 2 buttons.
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll: false});
+        
+        this.ok = new Jx.Button({
+            label: MooTools.lang.get('Jx','confirm').affirmitiveLabel,
+            onClick: this.onClick.bind(this, MooTools.lang.get('Jx','confirm').affirmitiveLabel)
+        }),
+        this.cancel = new Jx.Button({
+            label: MooTools.lang.get('Jx','confirm').negativeLabel,
+            onClick: this.onClick.bind(this, MooTools.lang.get('Jx','confirm').negativeLabel)
+        })
+        this.buttons.add(this.ok, this.cancel);
         this.options.toolbars = [this.buttons];
         if (Jx.type(this.options.question) === 'string') {
             this.question = new Element('div', {
@@ -24313,7 +26685,7 @@
             });
         } else {
             this.question = this.options.question;
-            $(this.question).addClass('jxConfirmQuestion');
+            document.id(this.question).addClass('jxConfirmQuestion');
         }
         this.options.content = this.question;
         this.parent();
@@ -24327,451 +26699,30 @@
         this.isOpening = false;
         this.hide();
         this.fireEvent('close', [this, value]);
-    }
-
-
-});// $Id: $
-/**
- * Class: Jx.Panel.DataView
- *
- * Extends: <Jx.Panel>
- *
- * This panel extension takes a standard Jx.Store (or subclass) and displays
- * each record as an item using a provided template. It sorts the store as requested
- * before doing so. The class only creates the HTML and has no default CSS display. All
- * styling must be done by the developer using the control.
- *
- *
- * Events:
- * renderDone - fires when the panel completes creating all of the items.
- *
- * License:
- * Copyright (c) 2009, Jonathan Bomgardner.
- *
- * This file is licensed under an MIT style license
- */
-Jx.Panel.DataView = new Class({
-
-    Extends: Jx.Panel,
-
-    options: {
-        /**
-         * Option: data
-         * The store containing the data
-         */
-        data: null,
-        /**
-         * Option: sortColumns
-         * An array of columns to sort the store by.
-         */
-        sortColumns: null,
-        /**
-         * Option: itemTemplate
-         * The template to use in rendering records
-         */
-        itemTemplate: null,
-        /**
-         * Option: emptyTemplate
-         * the template that is displayed when there are no records in the
-         * store.
-         */
-        emptyTemplate: null,
-        /**
-         * Option: containerClass
-         * The class added to the container. It can be used to target the items
-         * in the panel.
-         */
-        containerClass: null,
-        /**
-         * Option: itemClass
-         * The class to add to each item. Used for styling purposes
-         */
-        itemClass: null,
-        /**
-         * Option: itemOptions
-         * Options to pass to the list object
-         */
-        listOptions: {
-            select: true,
-            hover: true
-        }
     },
-
-    /**
-     * Property: bound
-     * hold bound functions
-     */
-    bound: {},
-
-    init: function () {
-        this.domA = new Element('div');
-        this.list = this.createList(this.domA, this.options.listOptions);
-        this.parent();
-    },
-    /**
-     * APIMethod: render
-     * Renders the dataview. If the store already has data loaded it will be rendered
-     * at the end of the method.
-     */
-    render: function () {
-        if (!$defined(this.options.data)) {
-            //we can't do anything without data
-            return;
-        }
-
-        this.options.content = this.domA;
-
-        //pass to parent
-        this.parent();
-
-        this.domA.addClass(this.options.containerClass);
-
-        //parse templates so we know what values are needed in each
-        this.itemCols = this.parseTemplate(this.options.itemTemplate);
-
-        this.bound.update = this.update.bind(this);
-        //listen for data updates
-        this.options.data.addEvent('loadFinished', this.bound.update);
-        this.options.data.addEvent('sortFinished', this.bound.update);
-        this.options.data.addEvent('loadError', this.bound.update);
-
-        if (this.options.data.loaded) {
-            this.update();
-        }
-
-    },
-
-    /**
-     * Method: draw
-     * begins the process of creating the items
-     */
-    draw: function () {
-        var n = this.options.data.count();
-        if ($defined(n) && n > 0) {
-            for (var i = 0; i < n; i++) {
-                this.options.data.moveTo(i);
-
-                var item = this.createItem();
-                this.list.add(item);
-            }
-        } else {
-            var empty = new Element('div', {html: this.options.emptyTemplate});
-            this.list.add(item);
-        }
-        this.fireEvent('renderDone', this);
-    },
-    /**
-     * Method: createItem
-     * Actually does the work of getting the data from the store
-     * and creating a single item based on the provided template
-     */
-    createItem: function () {
-      //create the item
-        var itemObj = {};
-        this.itemCols.each(function (col) {
-            itemObj[col] = this.options.data.get(col);
-        }, this);
-        var itemTemp = this.options.itemTemplate.substitute(itemObj);
-        var item = new Element('div', {
-            'class': this.options.itemClass,
-            html: itemTemp
-        });
-        return item;
-    },
-    /**
-     * APIMethod: update
-     * This method begins the process of creating the items. It is called when
-     * the store is loaded or can be called to manually recreate the view.
-     */
-    update: function () {
-        if (!this.updating) {
-            this.updating = true;
-            this.list.empty();
-            this.options.data.sort(this.options.sortColumns);
-            this.draw();
-            this.updating = false;
-        }
-    },
-    /**
-     * Method: parseTemplate
-     * parses the provided template to determine which store columns are
-     * required to complete it.
-     *
-     * Parameters:
-     * template - the template to parse
-     */
-    parseTemplate: function (template) {
-        //we parse the template based on the columns in the data store looking
-        //for the pattern {column-name}. If it's in there we add it to the
-        //array of ones to look for
-        var columns = this.options.data.getColumns();
-        var arr = [];
-        columns.each(function (col) {
-            var s = '{' + col.name + '}';
-            if (template.contains(s)) {
-                arr.push(col.name);
-            }
-        }, this);
-        return arr;
-    },
-    /**
-     * Method: enterItem
-     * Fires mouseenter event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    enterItem: function(item, list){
-        this.fireEvent('mouseenter', item, list);
-    },
-    /**
-     * Method: leaveItem
-     * Fires mouseleave event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    leaveItem: function(item, list){
-        this.fireEvent('mouseleave', item, list);
-    },
-    /**
-     * Method: selectItem
-     * Fires select event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    selectItem: function(item, list){
-        this.fireEvent('select', item, list);
-    },
-    /**
-     * Method: unselectItem
-     * Fires unselect event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    unselectItem: function(item, list){
-        this.fireEvent('unselect', item, list);
-    },
-    /**
-     * Method: addItem
-     * Fires add event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    addItem: function(item, list) {
-        this.fireEvent('add', item, list);
-    },
-    /**
-     * Method: removeItem
-     * Fires remove event
-     *
-     * Parameters:
-     * item - the item that is the target of the event
-     * list - the list this item is in.
-     */
-    removeItem: function(item, list) {
-        this.fireEvent('remove', item, list);
-    },
-    /**
-     * Method: createList
-     * Creates the list object
-     *
-     * Parameters:
-     * container - the container to use in the list
-     * options - the options for the list
-     */
-    createList: function(container, options){
-        return new Jx.List(container, $extend({
-            onMouseenter: this.enterItem.bind(this),
-            onMouseleave: this.leaveItem.bind(this),
-            onSelect:  this.selectItem.bind(this),
-            onAdd: this.addItem.bind(this),
-            onRemove: this.removeItem.bind(this),
-            onUnselect: this.unselectItem.bind(this)
-        }, options));
+    
+    createText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel(MooTools.lang.get('Jx','confirm').affirmitiveLabel);
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel(MooTools.lang.get('Jx','confirm').negativeLabel);
+    	}
+    	
     }
-});
-// $Id: $
-/**
- * Class: Jx.Panel.DataView.Group
- *
- * Extends: <Jx.Panel.DataView>
- *
- * This extension of Jx.Panel.DataView that provides for grouping the items
- * by a particular column.
- *
- * License:
- * Copyright (c) 2009, Jonathan Bomgardner.
- *
- * This file is licensed under an MIT style license
- */
-Jx.Panel.DataView.Group = new Class({
 
-    Extends: Jx.Panel.DataView,
 
-    options: {
-        /**
-         * Option: groupTemplate
-         * The template used to render the group heading
-         */
-        groupTemplate: null,
-        /**
-         * Option: groupContainerClass
-         * The class added to the group container. All of the items and header
-         * for a single grouping is contained by a div that has this class added.
-         */
-        groupContainerClass: null,
-        /**
-         * Option: groupHeaderClass
-         * The class added to the heading. Used for styling.
-         */
-        groupHeaderClass: null,
-        /**
-         * Option: listOption
-         * Options to pass to the main list
-         */
-        listOptions: {
-            select: false,
-            hover: false
-        },
-        /**
-         * Option: itemOption
-         * Options to pass to the item lists
-         */
-        itemOptions: {
-            select: true,
-            hover: true,
-            hoverClass: 'jxItemHover',
-            selectClass: 'jxItemSelect'
-        }
-    },
-
-    init: function() {
-        this.groupCols = this.parseTemplate(this.options.groupTemplate);
-        this.itemManager = new Jx.Selection({
-            eventToFire: {
-                select: 'itemselect',
-                unselect: 'itemunselect'
-            },
-            selectClass: 'jxItemSelected'
-        });
-        this.groupManager = new Jx.Selection({
-            eventToFire: {
-                select: 'groupselect',
-                unselect: 'groupunselect'
-            },
-            selectClass: 'jxGroupSelected'
-        });
-        this.parent();
-
-    },
-    /**
-     * APIMethod: render
-     * sets up the list container and calls the parent class' render function.
-     */
-    render: function () {
-        this.list = this.createList(this.domA, this.listOptions, this.groupManager);
-        this.parent();
-
-    },
-    /**
-     * Method: draw
-     * actually does the work of creating the view
-     */
-    draw: function () {
-        var d = this.options.data;
-        var n = d.count();
-
-        if ($defined(n) && n > 0) {
-            var currentGroup = '';
-            var itemList = null;
-
-            for (var i = 0; i < n; i++) {
-                d.moveTo(i);
-                var group = d.get(this.options.sortColumns[0]);
-
-                if (group !== currentGroup) {
-                    //we have a new grouping
-
-                    //group container
-                    var container =  new Element('div', {
-                        'class': this.options.groupContainerClass
-                    });
-                    var l = this.createList(container,{
-                        select: false,
-                        hover: false
-                    });
-                    this.list.add(l.container);
-
-                    //group header
-                    currentGroup = group;
-                    var obj = {};
-                    this.groupCols.each(function (col) {
-                        obj[col] = d.get(col);
-                    }, this);
-                    var temp = this.options.groupTemplate.substitute(obj);
-                    var g = new Element('div', {
-                        'class': this.options.groupHeaderClass,
-                        'html': temp,
-                        id: 'group-' + group.replace(" ","-","g")
-                    });
-                    l.add(g);
-
-                    //items container
-                    var currentItemContainer = new Element('div', {
-                        'class': this.options.containerClass
-                    });
-                    itemList = this.createList(currentItemContainer, this.options.itemOptions, this.itemManager);
-                    l.add(itemList.container);
-                }
-
-                var item = this.createItem();
-                itemList.add(item);
-            }
-        } else {
-            var empty = new Element('div', {html: this.options.emptyTemplate});
-            this.list.add(empty);
-        }
-        this.fireEvent('renderDone', this);
-    },
-
-    /**
-     * Method: createList
-     * Creates the list object
-     *
-     * Parameters:
-     * container - the container to use in the list
-     * options - the options for the list
-     * manager - <Jx.Selection> which selection obj to connect to this list
-     */
-    createList: function(container, options, manager){
-        return new Jx.List(container, $extend({
-            onMouseenter: this.enterItem.bind(this),
-            onMouseleave: this.leaveItem.bind(this),
-            onAdd: this.addItem.bind(this),
-            onRemove: this.removeItem.bind(this)
-        }, options), manager);
-    }
-
-});
-// $Id: $
+});// $Id: tooltip.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Tooltip
  *
  * Extends: <Jx.Widget>
  *
  * An implementation of tooltips. These are very simple tooltips that are
- * designed to be instantiated in javascript and directly attached to the object
- * that they are the tip for. We can only have one Tip per element so we use
- * element storage to store the tip object and check for it's presence
+ * designed to be instantiated in javascript and directly attached to the
+ * object that they are the tip for. We can only have one Tip per element so
+ * we use element storage to store the tip object and check for it's presence
  * before creating a new tip. If one is there we remove it and create this new
  * one.
  *
@@ -24787,12 +26738,12 @@
 Jx.Tooltip = new Class({
     Family: 'Jx.Widget',
     Extends : Jx.Widget,
-
+    Binds: ['enter', 'leave', 'move'],
     options : {
         /**
          * Option: offsets
-         * An object with x and y components for where to put the tip related to
-         * the mouse cursor.
+         * An object with x and y components for where to put the tip related
+         * to the mouse cursor.
          */
         offsets : {
             x : 15,
@@ -24806,8 +26757,8 @@
         showDelay : 100,
         /**
          * Option: cssClass
-         * a class to be added to the tip's container. This can be used to style
-         * the tip.
+         * a class to be added to the tip's container. This can be used to
+         * style the tip.
          */
         cssClass : null
     },
@@ -24822,10 +26773,9 @@
     parameters: ['target','tip','options'],
 
     /**
-     * APIMethod: render
+     * Method: render
      * Creates the tooltip
      *
-
      */
     render : function () {
         this.parent();
@@ -24860,10 +26810,9 @@
         this.options.target.store('Tip', this);
 
         //add events
-        this.options.target.addEvent('mouseenter', this.enter.bindWithEvent(this));
-        this.options.target.addEvent('mouseleave', this.leave.bindWithEvent(this));
-        this.options.target.addEvent('mousemove', this.move.bindWithEvent(this));
-
+        this.options.target.addEvent('mouseenter', this.enter);
+        this.options.target.addEvent('mouseleave', this.leave);
+        this.options.target.addEvent('mousemove', this.move);
     },
 
     /**
@@ -24872,9 +26821,8 @@
      *
      * Parameters:
      * event - the event object
-     * element - the element the cursor passed over
      */
-    enter : function (event, element) {
+    enter : function (event) {
         this.timer = $clear(this.timer);
         this.timer = (function () {
             this.domObj.setStyle('visibility', 'visible');
@@ -24887,9 +26835,8 @@
      *
      * Parameters:
      * event - the event object
-     * element - the element the cursor passed over
      */
-    leave : function (event, element) {
+    leave : function (event) {
         this.timer = $clear(this.timer);
         this.timer = (function () {
             this.domObj.setStyle('visibility', 'hidden');
@@ -24936,7 +26883,7 @@
         this.domObj.setStyle('left', tipPlacement.x);
     },
     /**
-     * Method: detach
+     * APIMethod: detach
      * Called to manually remove a tooltip.
      */
     detach : function () {
@@ -24944,7 +26891,7 @@
         this.destroy();
     }
 });
-// $Id: $
+// $Id: field.js 823 2010-03-31 12:18:00Z pagameba $
 /**
  * Class: Jx.Field
  *
@@ -24958,6 +26905,9 @@
  * Example:
  * (code)
  * (end)
+ * 
+ * MooTools.lang Keys:
+ * - field.requiredText
  *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
@@ -24968,7 +26918,7 @@
     Family: 'Jx.Field',
     Extends : Jx.Widget,
     pluginNamespace: 'Field',
-
+    
     options : {
         /**
          * Option: id
@@ -25042,12 +26992,6 @@
          */
         required : false,
         /**
-         * Option: requiredText
-         * Text to be displayed if a field is required. It is added as an
-         * <em> element inside the <label>.
-         */
-        requiredText : '*',
-        /**
          * Option: readonly
          * {True|False} defaults to false. Whether this field is readonly.
          */
@@ -25159,11 +27103,11 @@
             this.label.set('for', this.id);
 
             if (this.options.required) {
-                var em = new Element('em', {
-                    'html' : this.options.requiredText,
+                this.requiredText = new Element('em', {
+                    'html' : MooTools.lang.get('Jx','field').requiredText,
                     'class' : 'required'
                 });
-                em.inject(this.label);
+                this.requiredText.inject(this.label);
             }
         }
 
@@ -25190,12 +27134,14 @@
                 this.field.set("disabled", "disabled");
                 this.field.addClass('jxFieldDisabled');
             }
+            
+            //add events
+            this.field.addEvents({
+              'focus': this.onFocus.bind(this),
+              'blur': this.onBlur.bind(this),
+              'change': this.onChange.bind(this)
+            });
 
-            // add validator classes
-            if ($defined(this.options.validatorClasses)) {
-                this.field.addClass(this.options.validatorClasses);
-            }
-
             this.field.store('field', this);
         }
 
@@ -25217,13 +27163,16 @@
 
     },
     /**
-     * APIMethod: setValue Sets the value property of the field
+     * APIMethod: setValue 
+     * Sets the value property of the field
      *
      * Parameters:
      * v - The value to set the field to.
      */
     setValue : function (v) {
-        this.field.set('value', v);
+        if (!this.options.readonly) {
+            this.field.set('value', v);
+        }
     },
 
     /**
@@ -25271,10 +27220,45 @@
             this.parent(what);
         }
         return this;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.requiredText)) {
+        this.requiredText.set('html',MooTools.lang.get('Jx','field').requiredText);
+      }
+    },
+    
+    onFocus: function() {
+      this.fireEvent('focus', this);
+    },
+    
+    onBlur: function () {
+      this.fireEvent('blur',this);
+    },
+    
+    onChange: function () {
+      this.fireEvent('change', this);
+    },
+    
+    setBusy: function(state, withoutMask) {
+      if (!withoutMask) {
+        this.parent(state);
+      }
+      this.field.set('readonly', state || this.options.readonly);
     }
 
 });
-// $Id: $
+// $Id: text.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Field.Text
  *
@@ -25331,8 +27315,530 @@
 
     }
 
-});// $Id: $
+});// $Id: prompt.js 785 2010-03-25 19:19:21Z jonlb at comcast.net $
 /**
+ * Class: Jx.Dialog.Prompt
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Prompt is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user and ask for a text response. 
+ * 
+ * MooTools.lang Keys:
+ * - prompt.okButton
+ * - prompt.cancelButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Prompt = new Class({
+
+    Extends: Jx.Dialog,
+
+    options: {
+        /**
+         * Option: prompt
+         * The message to display to the user
+         */
+        prompt: '',
+        /**
+         * Option: startingValue
+         * The startingvalue to place in the text box
+         */
+        startingValue: '',
+        /**
+         * Jx.Dialog option defaults
+         */
+        width: 400,
+        height: 200,
+        close: true,
+        resize: true,
+        collapse: false
+    },
+    /**
+     * APIMethod: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+                label: MooTools.lang.get('Jx','prompt').okButton,
+                onClick: this.onClick.bind(this, 'Ok')
+            });
+        this.cancel = new Jx.Button({
+                label: MooTools.lang.get('Jx','prompt').cancelButton,
+                onClick: this.onClick.bind(this,'Cancel')
+            });
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+        
+        this.field = new Jx.Field.Text({
+            label: this.options.prompt,
+            value: this.options.startingValue,
+            containerClass: 'jxPrompt'
+        });
+        this.options.content = document.id(this.field);
+        this.parent();
+    },
+    /**
+     * Method: onClick
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onClick: function (value) {
+        this.isOpening = false;
+        this.hide();
+        this.fireEvent('close', [this, value, this.field.getValue()]);
+    },
+    
+    createText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel(MooTools.lang.get('Jx','prompt').okButton);
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel(MooTools.lang.get('Jx','prompt').cancelButton);
+    	}
+    }
+
+
+});
+// $Id: dataview.js 686 2010-02-01 05:45:28Z jonlb at comcast.net $
+/**
+ * Class: Jx.Panel.DataView
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This panel extension takes a standard Jx.Store (or subclass) and displays
+ * each record as an item using a provided template. It sorts the store as requested
+ * before doing so. The class only creates the HTML and has no default CSS display. All
+ * styling must be done by the developer using the control.
+ *
+ *
+ * Events:
+ * renderDone - fires when the panel completes creating all of the items.
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.DataView = new Class({
+
+    Extends: Jx.Panel,
+
+    options: {
+        /**
+         * Option: data
+         * The store containing the data
+         */
+        data: null,
+        /**
+         * Option: sortColumns
+         * An array of columns to sort the store by.
+         */
+        sortColumns: null,
+        /**
+         * Option: itemTemplate
+         * The template to use in rendering records
+         */
+        itemTemplate: null,
+        /**
+         * Option: emptyTemplate
+         * the template that is displayed when there are no records in the
+         * store.
+         */
+        emptyTemplate: null,
+        /**
+         * Option: containerClass
+         * The class added to the container. It can be used to target the items
+         * in the panel.
+         */
+        containerClass: null,
+        /**
+         * Option: itemClass
+         * The class to add to each item. Used for styling purposes
+         */
+        itemClass: null,
+        /**
+         * Option: itemOptions
+         * Options to pass to the list object
+         */
+        listOptions: {
+            select: true,
+            hover: true
+        }
+    },
+
+    /**
+     * Property: bound
+     * hold bound functions
+     */
+    bound: {},
+
+    init: function () {
+        this.domA = new Element('div');
+        this.list = this.createList(this.domA, this.options.listOptions);
+        this.parent();
+    },
+    /**
+     * APIMethod: render
+     * Renders the dataview. If the store already has data loaded it will be rendered
+     * at the end of the method.
+     */
+    render: function () {
+        if (!$defined(this.options.data)) {
+            //we can't do anything without data
+            return;
+        }
+
+        this.options.content = this.domA;
+
+        //pass to parent
+        this.parent();
+
+        this.domA.addClass(this.options.containerClass);
+
+        //parse templates so we know what values are needed in each
+        this.itemCols = this.parseTemplate(this.options.itemTemplate);
+
+        this.bound.update = this.update.bind(this);
+        //listen for data updates
+        this.options.data.addEvent('loadFinished', this.bound.update);
+        this.options.data.addEvent('sortFinished', this.bound.update);
+        this.options.data.addEvent('loadError', this.bound.update);
+
+        if (this.options.data.loaded) {
+            this.update();
+        }
+
+    },
+
+    /**
+     * Method: draw
+     * begins the process of creating the items
+     */
+    draw: function () {
+        var n = this.options.data.count();
+        if ($defined(n) && n > 0) {
+            for (var i = 0; i < n; i++) {
+                this.options.data.moveTo(i);
+
+                var item = this.createItem();
+                this.list.add(item);
+            }
+        } else {
+            var empty = new Element('div', {html: this.options.emptyTemplate});
+            this.list.add(item);
+        }
+        this.fireEvent('renderDone', this);
+    },
+    /**
+     * Method: createItem
+     * Actually does the work of getting the data from the store
+     * and creating a single item based on the provided template
+     */
+    createItem: function () {
+        //create the item
+        var itemObj = {};
+        this.itemCols.each(function (col) {
+            itemObj[col] = this.options.data.get(col);
+        }, this);
+        var itemTemp = this.options.itemTemplate.substitute(itemObj);
+        var item = new Element('div', {
+            'class': this.options.itemClass,
+            html: itemTemp
+        });
+        return item;
+    },
+    /**
+     * APIMethod: update
+     * This method begins the process of creating the items. It is called when
+     * the store is loaded or can be called to manually recreate the view.
+     */
+    update: function () {
+        if (!this.updating) {
+            this.updating = true;
+            this.list.empty();
+            this.options.data.sort(this.options.sortColumns);
+            this.draw();
+            this.updating = false;
+        }
+    },
+    /**
+     * Method: parseTemplate
+     * parses the provided template to determine which store columns are
+     * required to complete it.
+     *
+     * Parameters:
+     * template - the template to parse
+     */
+    parseTemplate: function (template) {
+        //we parse the template based on the columns in the data store looking
+        //for the pattern {column-name}. If it's in there we add it to the
+        //array of ones to look for
+        var columns = this.options.data.getColumns();
+        var arr = [];
+        columns.each(function (col) {
+            var s = '{' + col.name + '}';
+            if (template.contains(s)) {
+                arr.push(col.name);
+            }
+        }, this);
+        return arr;
+    },
+    /**
+     * Method: enterItem
+     * Fires mouseenter event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    enterItem: function(item, list){
+        this.fireEvent('mouseenter', item, list);
+    },
+    /**
+     * Method: leaveItem
+     * Fires mouseleave event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    leaveItem: function(item, list){
+        this.fireEvent('mouseleave', item, list);
+    },
+    /**
+     * Method: selectItem
+     * Fires select event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    selectItem: function(item, list){
+        this.fireEvent('select', item, list);
+    },
+    /**
+     * Method: unselectItem
+     * Fires unselect event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    unselectItem: function(item, list){
+        this.fireEvent('unselect', item, list);
+    },
+    /**
+     * Method: addItem
+     * Fires add event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    addItem: function(item, list) {
+        this.fireEvent('add', item, list);
+    },
+    /**
+     * Method: removeItem
+     * Fires remove event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    removeItem: function(item, list) {
+        this.fireEvent('remove', item, list);
+    },
+    /**
+     * Method: createList
+     * Creates the list object
+     *
+     * Parameters:
+     * container - the container to use in the list
+     * options - the options for the list
+     */
+    createList: function(container, options){
+        return new Jx.List(container, $extend({
+            onMouseenter: this.enterItem.bind(this),
+            onMouseleave: this.leaveItem.bind(this),
+            onSelect:  this.selectItem.bind(this),
+            onAdd: this.addItem.bind(this),
+            onRemove: this.removeItem.bind(this),
+            onUnselect: this.unselectItem.bind(this)
+        }, options));
+    }
+});
+// $Id: group.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Panel.DataView.Group
+ *
+ * Extends: <Jx.Panel.DataView>
+ *
+ * This extension of Jx.Panel.DataView that provides for grouping the items
+ * by a particular column.
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.DataView.Group = new Class({
+
+    Extends: Jx.Panel.DataView,
+
+    options: {
+        /**
+         * Option: groupTemplate
+         * The template used to render the group heading
+         */
+        groupTemplate: null,
+        /**
+         * Option: groupContainerClass
+         * The class added to the group container. All of the items and header
+         * for a single grouping is contained by a div that has this class added.
+         */
+        groupContainerClass: null,
+        /**
+         * Option: groupHeaderClass
+         * The class added to the heading. Used for styling.
+         */
+        groupHeaderClass: null,
+        /**
+         * Option: listOption
+         * Options to pass to the main list
+         */
+        listOptions: {
+            select: false,
+            hover: false
+        },
+        /**
+         * Option: itemOption
+         * Options to pass to the item lists
+         */
+        itemOptions: {
+            select: true,
+            hover: true,
+            hoverClass: 'jxItemHover',
+            selectClass: 'jxItemSelect'
+        }
+    },
+
+    init: function() {
+        this.groupCols = this.parseTemplate(this.options.groupTemplate);
+        this.itemManager = new Jx.Selection({
+            eventToFire: {
+                select: 'itemselect',
+                unselect: 'itemunselect'
+            },
+            selectClass: 'jxItemSelected'
+        });
+        this.groupManager = new Jx.Selection({
+            eventToFire: {
+                select: 'groupselect',
+                unselect: 'groupunselect'
+            },
+            selectClass: 'jxGroupSelected'
+        });
+        this.parent();
+
+    },
+    /**
+     * APIMethod: render
+     * sets up the list container and calls the parent class' render function.
+     */
+    render: function () {
+        this.list = this.createList(this.domA, this.listOptions, this.groupManager);
+        this.parent();
+
+    },
+    /**
+     * Method: draw
+     * actually does the work of creating the view
+     */
+    draw: function () {
+        var d = this.options.data;
+        var n = d.count();
+
+        if ($defined(n) && n > 0) {
+            var currentGroup = '';
+            var itemList = null;
+
+            for (var i = 0; i < n; i++) {
+                d.moveTo(i);
+                var group = d.get(this.options.sortColumns[0]);
+
+                if (group !== currentGroup) {
+                    //we have a new grouping
+
+                    //group container
+                    var container =  new Element('div', {
+                        'class': this.options.groupContainerClass
+                    });
+                    var l = this.createList(container,{
+                        select: false,
+                        hover: false
+                    });
+                    this.list.add(l.container);
+
+                    //group header
+                    currentGroup = group;
+                    var obj = {};
+                    this.groupCols.each(function (col) {
+                        obj[col] = d.get(col);
+                    }, this);
+                    var temp = this.options.groupTemplate.substitute(obj);
+                    var g = new Element('div', {
+                        'class': this.options.groupHeaderClass,
+                        'html': temp,
+                        id: 'group-' + group.replace(" ","-","g")
+                    });
+                    l.add(g);
+
+                    //items container
+                    var currentItemContainer = new Element('div', {
+                        'class': this.options.containerClass
+                    });
+                    itemList = this.createList(currentItemContainer, this.options.itemOptions, this.itemManager);
+                    l.add(itemList.container);
+                }
+
+                var item = this.createItem();
+                itemList.add(item);
+            }
+        } else {
+            var empty = new Element('div', {html: this.options.emptyTemplate});
+            this.list.add(empty);
+        }
+        this.fireEvent('renderDone', this);
+    },
+
+    /**
+     * Method: createList
+     * Creates the list object
+     *
+     * Parameters:
+     * container - the container to use in the list
+     * options - the options for the list
+     * manager - <Jx.Selection> which selection obj to connect to this list
+     */
+    createList: function(container, options, manager){
+        return new Jx.List(container, $extend({
+            onMouseenter: this.enterItem.bind(this),
+            onMouseleave: this.leaveItem.bind(this),
+            onAdd: this.addItem.bind(this),
+            onRemove: this.removeItem.bind(this)
+        }, options), manager);
+    }
+
+});
+// $Id: hidden.js 649 2009-11-30 22:19:48Z pagameba $
+/**
  * Class: Jx.Field.Hidden
  *
  * Extends: <Jx.Field>
@@ -25370,7 +27876,7 @@
 
 
 
-// $Id: $
+// $Id: form.js 823 2010-03-31 12:18:00Z pagameba $
 /**
  * Class: Jx.Form
  *
@@ -25422,7 +27928,12 @@
          * Option: name
          * the name property for the form
          */
-        name: ''
+        name: '',
+        /**
+         * Option: acceptCharset
+         * the character encoding to be used. Defaults to utf-8.
+         */
+        acceptCharset: 'utf-8'
     },
     
     /**
@@ -25438,13 +27949,17 @@
      * Property: fields
      * An array of all of the single fields (not contained in a fieldset) for this form
      */
-    fields : new Hash(),
+    fields : null,
     /**
      * Property: pluginNamespace
      * required variable for plugins
      */
     pluginNamespace: 'Form',
-
+    
+    init: function() {
+      this.parent();
+      this.fields = new Hash();
+    },
     /**
      * APIMethod: render
      * Constructs the form but does not add it to anything to be shown. The caller
@@ -25457,6 +27972,7 @@
             'action' : this.options.action,
             'class' : 'jxForm',
             'name' : this.options.name,
+            'accept-charset': this.options.acceptCharset,
             events: {
                 keypress: function(e) {
                     if (e.key == 'enter' && 
@@ -25465,6 +27981,7 @@
                         this.defaultAction.click) {
                         document.id(this.defaultAction).focus();
                         this.defaultAction.click();
+                        e.stop();
                     }
                 }.bind(this)
             }
@@ -25500,7 +28017,7 @@
      * Determines if the form passes validation
      *
      * Parameters:
-     * evt - the Mootools event object
+     * evt - the MooTools event object
      */
     isValid : function (evt) {
         return true;
@@ -25519,7 +28036,7 @@
      */
     getValues : function (asQueryString) {
         var queryString = this.domObj.toQueryString();
-        if ($defined(asQueryString)) {
+        if ($defined(asQueryString) && asQueryString) {
             return queryString;
         } else {
             return queryString.parseQueryString();
@@ -25557,7 +28074,10 @@
             if (field instanceof Jx.Field && !$defined(field.form)) {
                 field.form = this;
                 this.addField(field);
+            } else if (field instanceof Jx.Fieldset && !$defined(field.form)) {
+                field.form = this;
             }
+            
             this.domObj.grab(field);
         }
         return this;
@@ -25582,6 +28102,23 @@
             }
         },this);
         return fields;
+    },
+    
+    getField: function (id) {
+        if (this.fields.has(id)) {
+            return this.fields.get(id);
+        } 
+        return null;
+    },
+    
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.parent(state);
+      this.fields.each(function(field) {
+        field.setBusy(state, true);
+      });
     }
 });
 /**
@@ -25601,6 +28138,9 @@
  * call the Jx.Field.File.upload() method for each file input directly and
  * then submit the rest of the form via ajax.
  *
+ * MooTools.lang Keys:
+ * - file.browseLabel
+ * 
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
  *
@@ -25698,7 +28238,7 @@
             template : '<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'
         });
         this.browseButton = new Jx.Button({
-            label : 'Browse...'
+            label: MooTools.lang.get('Jx','file').browseLabel
         });
 
 
@@ -25764,14 +28304,14 @@
         });
 
         //iframeBody.grab(this.form);
-        $(this.form).set('target', this.iframe.get('name')).setStyles({
+        document.id(this.form).set('target', this.iframe.get('name')).setStyles({
             visibility: 'hidden',
             display: 'none'
         }).inject(document.body);
 
 
         //move the form input into it (cloneNode)
-        $(this.form).grab(this.field.cloneNode(true));
+        document.id(this.form).grab(this.field.cloneNode(true));
         //if polling the server we need an APC_UPLOAD_PROGRESS id.
         //get it from the server.
         if (this.options.progress) {
@@ -25809,7 +28349,7 @@
 
 
         //submit the form
-        $(this.form).submit();
+        document.id(this.form).submit();
         //begin polling if needed
         if (this.options.progress && $defined(this.progressID)) {
             this.pollUpload();
@@ -25908,6 +28448,22 @@
     getExt: function () {
         var fn = this.getFileName();
         return fn.slice(fn.length - 3);
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.browseButton)) {
+    		this.browseButton.setLabel( MooTools.lang.get('Jx','file').browseLabel);
+    	}
     }
 });
 /**
@@ -25936,6 +28492,10 @@
  * onUpdate - Fired when the bar is updated
  * onComplete - fires when the progress bar completes it's fill
  * 
+ * MooTools.lang keys:
+ * - progressbar.messageText
+ * - progressbar.progressText
+ * 
  */
 Jx.Progressbar = new Class({
     Family: 'Jx.Progressbar',
@@ -25945,17 +28505,6 @@
         onUpdate: $empty,
         onComplete: $empty,
         /**
-         * Option: messageText
-         * The text of a message displayed above the bar. Set to NULL to prevent any text from appearing
-         */
-        messageText: 'Loading...',
-        /**
-         * Option: progressText
-         * The text displayed inside the bar. This defaults to "{progress} of {total}" 
-         * where {progress} and {total} are substituted for passed in values.
-         */
-        progressText: '{progress} of {total}',
-        /**
          * Option: bar
          * an object that gives options for the bar itself. Specifically, 
          * the width and height of the bar. You can set either to 'auto' to
@@ -26007,9 +28556,11 @@
         this.parent();
         
         if ($defined(this.options.parent)) {
-            this.domObj.inject($(this.options.parent));
+            this.domObj.inject(document.id(this.options.parent));
         }
         
+        this.domObj.addClass('jxProgressStarting');
+        
         //determine width of progressbar
         if (this.options.bar.width === 'auto') {
             //get width of container
@@ -26017,14 +28568,16 @@
         }
         
         //determine height
+        /**
         if (this.options.bar.height === 'auto') {
             this.options.bar.height = this.domObj.getStyle('height').toInt() - 4;
         }
+        **/
         
         //Message
         if (this.message) {
-            if ($defined(this.options.messageText)) {
-                this.message.set('html', this.options.messsageText);
+            if ($defined(MooTools.lang.get('Jx','progressbar').messageText)) {
+                this.message.set('html', MooTools.lang.get('Jx','progressbar').messageText);
             } else {
                 this.message.destroy();
             }
@@ -26034,39 +28587,40 @@
         if (this.container) {
             this.container.setStyles({
                 'position': 'relative',
-                'width': this.options.bar.width,
-                'height' : this.options.bar.height + 4
+                'width': this.options.bar.width
+                //'height' : this.options.bar.height + 4
             });
         }
         
         //Outline
         if (this.outline) {
             this.outline.setStyles({
-                'width': this.options.bar.width,
-                'height' : this.options.bar.height
+                'width': this.options.bar.width
+                //'height' : this.options.bar.height
             });
         }
         
         //Fill
         if (this.fill) {
             this.fill.setStyles({
-                'width': 0,
-                'height' : this.options.bar.height
+                'width': 0
+                //'height' : this.options.bar.height
             });
         }
         
         //TODO: check for {progress} and {total} in progressText
         var obj = {};
-        if (this.options.progressText.contains('{progress}')) {
+        var progressText = MooTools.lang.get('Jx','progressbar').progressText;
+        if (progressText.contains('{progress}')) {
             obj.progress = 0;
         }
-        if (this.options.progressText.contains('{total}')) {
+        if (progressText.contains('{total}')) {
             obj.total = 0;
         }
         
         //Progress text
         if (this.text) {
-            this.text.set('html', this.options.progressText.substitute(obj));
+            this.text.set('html', progressText.substitute(obj));
         }
         
     },
@@ -26080,19 +28634,25 @@
      *              equal to the total)
      */
     update: function (total, progress) {
+    	
+    	//check for starting class
+    	if (this.domObj.hasClass('jxProgressStarting')) {
+    		this.domObj.removeClass('jxProgressStarting').addClass('jxProgressWorking');
+    	}
+    	
         var newWidth = (progress * this.options.bar.width) / total;
         
         //update bar width
-        //TODO: animate this
         this.text.get('tween', {property:'width', onComplete: function() {
             var obj = {};
-            if (this.options.progressText.contains('{progress}')) {
+            var progressText = MooTools.lang.get('Jx','progressbar').progressText
+            if (progressText.contains('{progress}')) {
                 obj.progress = progress;
             }
-            if (this.options.progressText.contains('{total}')) {
+            if (progressText.contains('{total}')) {
                 obj.total = total;
             }
-            var t = this.options.progressText.substitute(obj);
+            var t = progressText.substitute(obj);
             this.text.set('text', t);
         }.bind(this)}).start(newWidth);
         
@@ -26100,15 +28660,33 @@
             
             if (total === progress) {
                 this.complete = true;
+                this.domObj.removeClass('jxProgressWorking').addClass('jxProgressFinished');
                 this.fireEvent('complete');
             } else {
                 this.fireEvent('update');
             }
         }).bind(this)}).start(newWidth);
         
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.message) {
+    		this.message.set('html',MooTools.lang.get('Jx','progressbar').messageText);
+    	}
+        //progress text will update on next update.
     }
     
-});
+});// $Id: upload.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Panel.FileUpload
  *
@@ -26116,6 +28694,9 @@
  *
  * This class extends Jx.Panel to provide a consistent interface for uploading
  * files in an application.
+ * 
+ * MooTools.lang Keys:
+ * - upload.buttonText
  *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
@@ -26139,7 +28720,7 @@
             progressUrl: ''
         },
         /**
-         * Option: onFileUploadComplete
+         * Option: onFileComplete
          * An event handler that is called when a file has been uploaded
          */
         onFileComplete: $empty,
@@ -26155,11 +28736,6 @@
          */
         prompt: null,
         /**
-         * Option: buttonText
-         * The text to place on the upload button
-         */
-        buttonText: 'Upload Files',
-        /**
          * Option: removeOnComplete
          * Determines whether a file is removed from the queue after uploading
          */
@@ -26177,7 +28753,7 @@
      */
     fileQueue: [],
     /**
-     * APIMethod: render
+     * Method: render
      * Sets up the upload panel.
      */
     render: function () {
@@ -26216,7 +28792,7 @@
         });
         this.queueDiv.inject(this.domObjA);
         this.uploadBtn = new Jx.Button({
-            label : this.options.buttonText,
+            label : MooTools.lang.get('Jx','upload').buttonText,
             onClick: this.upload.bind(this)
         });
         var tlb = new Jx.Toolbar({position: 'bottom'}).add(this.uploadBtn);
@@ -26228,8 +28804,8 @@
     },
     /**
      * Method: moveToQueue
-     * Called by Jx.Field.File's fileSelected event. Moves the selected file into the
-     * upload queue.
+     * Called by Jx.Field.File's fileSelected event. Moves the selected file
+     * into the upload queue.
      */
     moveToQueue: function (file) {
         var cf = this.currentFile;
@@ -26239,7 +28815,7 @@
 
         this.currentFile = new Jx.Field.File(this.fileOpt);
         this.currentFile.addEvent('fileSelected', this.moveToQueue.bind(this));
-        $(this.currentFile).replaces($(cf));
+        document.id(this.currentFile).replaces(document.id(cf));
 
         //add to queue div
 
@@ -26275,6 +28851,8 @@
             file.addEvent('uploadProgress', this.fileUploadProgress.bind(this));
             //progressbar
             //setup options
+            // TODO: should (at least some of) these options be available to
+            // the developer?
             var options = {
                 containerClass: 'progress-container',
                 messageText: null,
@@ -26392,14 +28970,25 @@
         var name = file.getFileName();
         //TODO: Should prompt the user to be sure - use Jx.Dialog.Confirm?
         this.list.remove(file.queuedDiv);
-        //$(name).destroy();
+        //document.id(name).destroy();
         this.fileQueue = this.fileQueue.erase(file);
         if (this.fileQueue.length === 0) {
             this.uploadBtn.setEnabled(false);
         }
+    },
+    
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    createText: function (lang) {
+      this.parent();
+      if ($defined(this.uploadBtn)) {
+        this.uploadBtn.setLabel(MooTools.lang.get('Jx','upload').buttonText);
+      }
     }
 });
-// $Id: $
+// $Id: listitem.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.ListItem
  *
@@ -26439,7 +29028,7 @@
     enable: function(state) {
 
     }
-});// $Id: $
+});// $Id: listview.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.ListView
  *
@@ -26512,7 +29101,7 @@
         this.list.replace(item, withItem);
         return this;
     }
-});// $Id: $
+});// $Id: column.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Column
  *
@@ -26531,24 +29120,31 @@
  */
 Jx.Column = new Class({
 
-    Extends: Jx.Object,
+	Family: 'Jx.Column',
+    Extends: Jx.Widget,
 
     options: {
         /**
-         * Option: header
-         * The text to be used as a header for this column
+         * Option: renderMode
+         * The mode to use in rendering this column to determine its width. 
+         * Valid options include
+         * 
+         * fit - auto calculates the width for the best fit to the text. This 
+         * 			is the default.
+         * fixed - uses the value passed in the width option, doesn't 
+         * 			auto calculate.
+         * expand - uses the value in the width option as a minimum width and 
+         * 			allows this column to expand and take up any leftover space. 
+         * 			NOTE: there can be only 1 expand column in a grid. The 
+         * 			Jx.Columns object will only take the first column with this 
+         * 			option as the expanding column. All remaining columns marked 
+         * 			"expand" will be treated as "fixed".
          */
-        header: null,
+        renderMode: 'fit',
         /**
-         * Option: modelField
-         * The field of the model that this column is keyed to
-         */
-        modelField: null,
-        /**
          * Option: width
-         * Determines the width of the column. Set to 'null' or 'auto'
-         * to allow the column to autocalculate it's width based on its
-         * contents
+         * Determines the width of the column when using 'fixed' rendering mode
+         * or acts as a minimum width when using 'expand' mode.
          */
         width: null,
         /**
@@ -26572,51 +29168,31 @@
          */
         isHidden: false,
         /**
-         * Option: formatter
-         * an instance of <Jx.Formatter> or one of its subclasses which
-         * will be used to format the data in this column. It can also be
-         * an object containing the name (This should be the part after
-         * Jx.Formatter in the class name. For instance, to get a currency
-         * formatter, specify 'Currency' as the name.) and options for the
-         * needed formatter (see individual formatters for options).
-         * (code)
-         * {
-         *    name: 'formatter name',
-         *    options: {}
-         * }
-         * (end)
-         */
-        formatter: null,
-        /**
          * Option: name
          * The name given to this column
          */
         name: '',
         /**
-         * Option: dataType
-         * The type of the data in this column, used for sorting. Can be
-         * alphanumeric, numeric, currency, boolean, or date
+         * Option: template
          */
-        dataType: 'alphanumeric',
+        template: null,
         /**
-         * Option: templates
-         * objects used to determine the type of tag and css class to
-         * assign to a header cell and a regular cell. The css class can
-         * also be a function that returns a string to assign as the css
-         * class. The function will be passed the text to be formatted.
+         * Option: renderer
+         * an instance of a Jx.Grid.Renderer to use in rendering the content
+         * of this column or a config object for creating one like so:
+         *
+         * (code)
+         * {
+         *     name: 'Text',
+         *     options: { ... renderer options ... }
+         * }
          */
-        templates: {
-            header: {
-                tag: 'span',
-                cssClass: null
-            },
-            cell: {
-                tag: 'span',
-                cssClass: null
-            }
-        }
-
+        renderer: null
     },
+    
+    classes: $H({
+    	domObj: 'jxGridCellContent'
+    }),
     /**
      * Property: model
      * holds a reference to the model (an instance of <Jx.Store> or subclass)
@@ -26630,127 +29206,157 @@
      * initializes the column object
      */
     init : function () {
+
+        this.name = this.options.name;
+
+        //adjust header for column
+        if (!$defined(this.options.template)) {
+            this.options.template = '<span class="jxGridCellContent">' + this.name.capitalize() + '</span>';
+        }
+
         this.parent();
         if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
             this.grid = this.options.grid;
         }
-        this.name = this.options.name;
-        //we need to check the formatter
-        if ($defined(this.options.formatter)
-                && !(this.options.formatter instanceof Jx.Formatter)) {
-            var t = Jx.type(this.options.formatter);
-            if (t === 'object') {
-                this.options.formatter = new Jx.Formatter[this.options.formatter.name](
-                        this.options.formatter.options);
+
+        //check renderer
+        if (!$defined(this.options.renderer)) {
+            //set a default renderer
+            this.options.renderer = new Jx.Grid.Renderer.Text({
+                textTemplate: '{' + this.name + '}'
+            });
+        } else {
+            if (!(this.options.renderer instanceof Jx.Grid.Renderer)) {
+                var t = Jx.type(this.options.renderer);
+                if (t === 'object') {
+                    this.options.renderer = new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](
+                            this.options.renderer.options);
+                }
             }
         }
+
+        this.options.renderer.setColumn(this);
+
+        this.sortImg = new Element('img', {
+            src: Jx.aPixel.src
+        });
+
     },
+    	
     /**
      * APIMethod: getHeaderHTML
-     * Returns the header text wrapped in the tag specified in
-     * options.templates.hedaer.tag
      */
     getHeaderHTML : function () {
-        var text = this.options.header ? this.options.header
-                : this.options.modelField;
-        var ht = this.options.templates.header;
-        var el = new Element(ht.tag, {
-            'class' : 'jxGridCellContent',
-            'html' : text
-        });
-        if ($defined(ht.cssClass)) {
-            if (Jx.type(ht.cssClass) === 'function') {
-                el.addClass(ht.cssClass.run(text));
-            } else {
-                el.addClass(ht.cssClass);
-            }
-        }
-        this.header = el;
-        return el;
+    	if (this.isSortable()) {
+    		this.sortImg.inject(this.domObj);	
+    	}
+      return this.domObj;
     },
 
     setWidth: function(newWidth) {
-        if (this.rule && parseInt(newWidth,10) >= 0) {
-            this.width = parseInt(newWidth,10);
-            this.rule.style.width = parseInt(newWidth,10) + "px";
-        }
+      var delta = this.cellWidth - this.width;
+    	this.width = parseInt(newWidth,10);
+    	this.cellWidth = this.width + delta;
+    	this.options.width = newWidth;
+      if (this.rule && parseInt(newWidth,10) >= 0) {
+          this.rule.style.width = parseInt(newWidth,10) + "px";
+      }
+      if (this.cellRule && parseInt(this.cellWidth,10) >= 0) {
+          this.cellRule.style.width = parseInt(this.cellWidth,10) + "px";
+      }
     },
+    
+    getWidth: function () {
+    	return this.width;
+    },
+    
+    getCellWidth: function() {
+      return this.cellWidth;
+    },
+    
     /**
-     * APIMethod: getWidth
+     * APIMethod: calculateWidth
      * returns the width of the column.
      *
      * Parameters:
-     * recalculate - {boolean} determines if the width should be recalculated
-     *          if the column is set to autocalculate. Has no effect if the width is
-     *          preset
      * rowHeader - flag to tell us if this calculation is for the row header
      */
-    getWidth : function (recalculate, rowHeader) {
-        rowHeader = $defined(rowHeader) ? rowHeader : false;
+    calculateWidth : function (rowHeader) {
+        //if this gets called then we assume that we want to calculate the width
+    	rowHeader = $defined(rowHeader) ? rowHeader : false;
         var maxWidth;
-        //check for null width or for "auto" setting and measure all contents in this column
-        //in the entire model as well as the header (really only way to do it).
-        if (!$defined(this.width) || recalculate) {
-            if (this.options.width !== null
-                    && this.options.width !== 'auto') {
-                maxWidth = this.width = Jx.getNumber(this.options.width);
-            } else {
-                //calculate the width
-                var model = this.grid.getModel();
-                var oldPos = model.getPosition();
-                maxWidth = 0;
-                model.first();
-                while (model.valid()) {
-                    //check size by placing text into a TD and measuring it.
-                    //TODO: this should add .jxGridRowHead/.jxGridColHead if
-                    //      this is a header to get the correct measurement.
-                    var text = model.get(this.options.modelField);
-                    var klass = 'jxGridCell';
-                    if (this.grid.row.useHeaders()
-                            && this.options.modelField === this.grid.row
-                            .getRowHeaderField()) {
-                        klass = 'jxGridRowHead';
-                    }
-                    var s = this.measure(text, klass, rowHeader);
-                    if (s.width > maxWidth) {
-                        maxWidth = s.width;
-                    }
-                    if (model.hasNext()) {
-                        model.next();
-                    } else {
-                        break;
-                    }
+        var maxCellWidth;
+        
+        var model = this.grid.getModel();
+        model.first();
+        if ((this.options.renderMode == 'fixed' || 
+             this.options.renderMode == 'expand') && 
+            model.valid) {
+          var t = new Element('span', {
+            'class': 'jxGridCellContent',
+            html: 'a',
+            styles: {
+              width: this.options.width
+            }
+          });
+          var s = this.measure(t,'jxGridCell');
+          maxWidth = s.content.width;
+          maxCellWidth = s.cell.width;
+        } else {
+            //calculate the width
+            var oldPos = model.getPosition();
+            maxWidth = maxCellWidth = 0;
+            while (model.valid()) {
+                //check size by placing text into a TD and measuring it.
+                this.options.renderer.render();
+                var text = document.id(this.options.renderer);
+                var klass = 'jxGridCell';
+                if (this.grid.row.useHeaders()
+                        && this.options.name === this.grid.row
+                        .getRowHeaderColumn()) {
+                    klass = 'jxGridRowHead';
                 }
+                var s = this.measure(text, klass, rowHeader, model.getPosition());
+                if (s.content.width > maxWidth) {
+                    maxWidth = s.content.width;
+                }
+                if (s.cell.width > maxCellWidth) {
+                  maxCellWidth = s.cell.width
+                }
+                if (model.hasNext()) {
+                    model.next();
+                } else {
+                    break;
+                }
+            }
 
-                //check the column header as well (unless this is the row header)
-                if (!(this.grid.row.useHeaders() && this.options.modelField === this.grid.row
-                        .getRowHeaderField())) {
-                    klass = 'jxGridColHead';
-                    if (this.isEditable()) {
-                        klass += ' jxColEditable';
-                    }
-                    if (this.isResizable()) {
-                        klass += ' jxColResizable';
-                    }
-                    if (this.isSortable()) {
-                        klass += ' jxColSortable';
-                    }
-                    s = this.measure(this.options.header, klass);
-                    if (s.width > maxWidth) {
-                        maxWidth = s.width;
-                    }
+            //check the column header as well (unless this is the row header)
+            if (!(this.grid.row.useHeaders() && 
+                this.options.name === this.grid.row.getRowHeaderColumn())) {
+                klass = 'jxGridColHead';
+                if (this.isEditable()) {
+                    klass += ' jxColEditable';
                 }
-                if (!rowHeader) {
-                    this.width = maxWidth;
+                if (this.isResizable()) {
+                    klass += ' jxColResizable';
                 }
-                model.moveTo(oldPos);
+                if (this.isSortable()) {
+                    klass += ' jxColSortable';
+                }
+                s = this.measure(this.domObj.clone(), klass);
+                if (s.content.width > maxWidth) {
+                    maxWidth = s.content.width;
+                }
+                if (s.cell.width > maxCellWidth) {
+                  maxCellWidth = s.cell.width
+                }
             }
         }
-        if (!rowHeader) {
-            return this.width;
-        } else {
-            return maxWidth;
-        }
+        
+        this.width = maxWidth;
+        this.cellWidth = maxCellWidth;
+        model.moveTo(oldPos);
+        return this.width;
     },
     /**
      * Method: measure
@@ -26760,32 +29366,33 @@
      * text - the text to measure
      * klass - a string indicating and extra classes to add so that
      *          css classes can be taken into account.
+     * rowHeader - 
+     * row - 
      */
-    measure : function (text, klass, rowHeader) {
-        if ($defined(this.options.formatter)
-                && text !== this.options.header) {
-            text = this.options.formatter.format(text);
-        }
+    measure : function (text, klass, rowHeader, row) {
         var d = new Element('span', {
             'class' : klass
         });
-        var el = new Element('span', {
-            'html' : text,
-            'class': 'jxGridCellContent'
-        }).inject(d);
-        d.setStyle('height', this.grid.row.getHeight());
+        text.inject(d);
+        //d.setStyle('height', this.grid.row.getHeight(row));
         d.setStyles({
             'visibility' : 'hidden',
             'width' : 'auto'
-            //'font-family' : 'Arial'  removed because CSS may impose different font(s)
         });
+        
         d.inject(document.body, 'bottom');
         var s = d.measure(function () {
-            //if nogt rowHeader, get size of innner span
+            //if not rowHeader, get size of innner span
             if (!rowHeader) {
-                return this.getFirst().getContentBoxSize();
+                return {
+                  content: this.getFirst().getContentBoxSize(),
+                  cell: this.getFirst().getMarginBoxSize()
+                }
             } else {
-                return this.getMarginBoxSize();
+                return {
+                  content: this.getMarginBoxSize(),
+                  cell: this.getMarginBoxSize()
+                }
             }
         });
         d.destroy();
@@ -26821,34 +29428,14 @@
     },
     /**
      * APIMethod: getHTML
-     * returns the content of the current model row wrapped in the tag
-     * specified by options.templates.cell.tag and with the appropriate classes
-     * added
+     * calls render method of the renderer object passed in.
      */
     getHTML : function () {
-        var text = this.grid.getModel().get(this.options.modelField);
-        var ct = this.options.templates.cell;
-        if ($defined(this.options.formatter)) {
-            text = this.options.formatter.format(text);
-        }
-        var el = new Element(ct.tag, {
-            'html' : text,
-            'class' : 'jxGridCellContent',
-            styles: {
-                // width: this.getWidth()
-            }
-        });
-        if ($defined(ct.cssClass)) {
-            if (Jx.type(ct.cssClass) === 'function') {
-                el.addClass(ct.cssClass.run(text));
-            } else {
-                el.addClass(ct.cssClass);
-            }
-        }
-        return el;
+        this.options.renderer.render();
+        return document.id(this.options.renderer);
     }
 
-});// $Id: $
+});// $Id: columns.js 818 2010-03-30 12:45:20Z pagameba $
 /**
  * Class: Jx.Columns
  *
@@ -26865,6 +29452,7 @@
  */
 Jx.Columns = new Class({
 
+  Family: 'Jx.Columns',
     Extends : Jx.Object,
 
     options : {
@@ -26907,14 +29495,13 @@
 
         this.options.columns.each(function (col) {
             //check the column to see if it's a Jx.Grid.Column or an object
-                if (col instanceof Jx.Column) {
-                    this.columns.push(col);
-                } else if (Jx.type(col) === "object") {
-                    col.grid = this.grid;
-                    this.columns.push(new Jx.Column(col));
-                }
+            if (col instanceof Jx.Column) {
+                this.columns.push(col);
+            } else if (Jx.type(col) === "object") {
+                this.columns.push(new Jx.Column(col,this.grid));
+            }
 
-            }, this);
+        }, this);
     },
     /**
      * APIMethod: getHeaderHeight
@@ -26981,11 +29568,16 @@
      * index - an integer denoting the placement of the column in the grid (zero-based)
      */
     getByGridIndex : function (index) {
-        var headers = this.grid.colTableBody.getFirst().getChildren();
+        var headers = this.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
         var cell = headers[index];
-        var hClasses = cell.get('class').split(' ').filter(function (cls) {
-            return cls.test('jxColHead-');
-        });
+          var hClasses = cell.get('class').split(' ').filter(function (cls) {
+            if(this.options.useHeaders)
+              return cls.test('jxColHead-')
+            else
+              return cls.test('jxCol-');
+          }.bind(this));
         var parts = hClasses[0].split('-');
         return this.getByName(parts[1]);
     },
@@ -26999,17 +29591,17 @@
      */
     getHeaders : function (list) {
         var r = this.grid.row.useHeaders();
-        var hf = this.grid.row.getRowHeaderField();
+        var hf = this.grid.row.getRowHeaderColumn();
         this.columns.each(function (col, idx) {
-            if (r && hf === col.options.modelField) {
+            if (r && hf === col.options.name) {
                 //do nothing
             } else if (!col.isHidden()) {
-                var th = new Element('td', {
+                var th = new Element('th', {
                     'class' : 'jxGridColHead jxGridCol'+idx
                 });
                 th.adopt(col.getHeaderHTML());
                 // th.setStyle('width', col.getWidth());
-                th.addClass('jxColHead-' + col.options.modelField);
+                th.addClass('jxColHead-' + col.name);
                 //add other styles for different attributes
                 if (col.isEditable()) {
                     th.addClass('jxColEditable');
@@ -27039,15 +29631,16 @@
      */
     getColumnCells : function (list) {
         var r = this.grid.row;
-        var f = r.getRowHeaderField();
+        var f = r.getRowHeaderColumn();
         var h = r.useHeaders();
         this.columns.each(function (col, idx) {
-            if (h && col.options.modelField !== f && !col.isHidden()) {
+            if (h && col.name !== f && !col.isHidden()) {
                 list.add(this.getColumnCell(col, idx));
             } else if (!h && !col.isHidden()) {
                 list.add(this.getColumnCell(col, idx));
             }
         }, this);
+        list.add(new Element('td'));
     },
     /**
      * APIMethod: getColumnCell
@@ -27062,7 +29655,7 @@
             'class' : 'jxGridCell'
         });
         td.adopt(col.getHTML());
-        td.addClass('jxCol-' + col.options.modelField);
+        td.addClass('jxCol-' + col.name);
         td.addClass('jxGridCol'+idx);
         //add other styles for different attributes
         if (col.isEditable()) {
@@ -27077,21 +29670,98 @@
 
         td.store('jxCellData',{
             col: col,
-            index: idx,
+            index: idx, //This is the position of the column
             row: this.grid.model.getPosition()
         });
 
         return td;
     },
+    
+    calculateWidths: function () {
+      //to calculate widths we loop through each column
+      var expand = null;
+      var totalWidth = 0;
+      var rowHeaderWidth = 0;
+      this.columns.each(function(col,idx){
+        //are we checking the rowheader?
+        var rowHeader = false;
+        if (col.name == this.grid.row.options.headerColumn) {
+          rowHeader = true;
+        }
+        //if it's fixed, set the width to the passed in width
+        if (col.options.renderMode == 'fixed') {
+          col.calculateWidth(); //col.setWidth(col.options.width);
+          
+        } else if (col.options.renderMode == 'fit') {
+          col.calculateWidth(rowHeader);
+        } else if (col.options.renderMode == 'expand' && !$defined(expand)) {
+          expand = col;
+        } else {
+          //treat it as fixed if has width, otherwise as fit
+          if ($defined(col.options.width)) {
+            col.setWidth(col.options.width);
+          } else {
+            col.calculateWidth(rowHeader);
+          }
+        }
+        if (!col.isHidden() && !(col.name == this.grid.row.options.headerColumn)) {
+            totalWidth += Jx.getNumber(col.getWidth());
+            if (rowHeader) {
+                rowHeaderWidth = col.getWidth();
+            }
+        }
+      },this);
+      
+      // width of the container
+      //var containerWidth = this.grid.gridObj.getContentBoxSize();
+      var gridSize = this.grid.gridObj.getContentBoxSize();
+      if (gridSize.width > totalWidth) {
+        //now figure the expand column
+        if ($defined(expand)) {
+          // var leftOverSpace = gridSize.width - totalWidth + rowHeaderWidth;
+          var leftOverSpace = gridSize.width - totalWidth;
+          if (leftOverSpace >= expand.options.width) {
+            expand.options.width = leftOverSpace;
+            expand.calculateWidth();
+            expand.setWidth(leftOverSpace);
+            totalWidth += leftOverSpace;
+          } else {
+            expand.setWidth(expand.options.width);
+          }
+        }
+      } //else {
+      this.grid.gridTable.setContentBoxSize({'width': totalWidth - rowHeaderWidth});
+      this.grid.colTable.setContentBoxSize({'width': totalWidth - rowHeaderWidth});
+      // }
+    },
 
     createRules: function(styleSheet, scope) {
         this.columns.each(function(col, idx) {
-            var selector = scope+' .jxGridCol'+idx+', '+scope + " .jxGridCol" + idx + " .jxGridCellContent";
-            col.rule = Jx.Styles.insertCssRule(selector, '', styleSheet);
+            var selector = scope+' .jxGridCol'+idx
+            var dec = '';
+            if (col.options.renderMode === 'fixed' || col.options.renderMode === 'expand') {
+              //set the white-space to 'normal !important'
+              dec = 'white-space: normal !important';
+            }
+            col.cellRule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+            col.cellRule.style.width = col.getCellWidth() + "px";
+
+            var selector = scope+" .jxGridCol" + idx + " .jxGridCellContent";
+            col.rule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
             col.rule.style.width = col.getWidth() + "px";
+
         }, this);
     },
 
+    updateRule: function(column) {
+        var col = this.getByName(column);
+        if (col.options.renderMode === 'fit') {
+          col.calculateWidth();
+        }
+        col.rule.style.width = col.getWidth() + "px";
+        col.cellRule.style.width = col.getCellWidth() + "px";
+    },
+    
     /**
      * APIMethod: getColumnCount
      * returns the number of columns in this model (including hidden).
@@ -27107,13 +29777,19 @@
      * name - the name of the column to get an index for
      */
     getIndexFromGrid : function (name) {
-        var headers = this.grid.colTableBody.getFirst().getChildren();
+        var headers = this.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
         var c;
         var i = -1;
+        var self = this;
         headers.each(function (h) {
             i++;
             var hClasses = h.get('class').split(' ').filter(function (cls) {
-                return cls.test('jxColHead-');
+                if(self.options.useHeaders)
+                  return cls.test('jxColHead-');
+                else
+                  return cls.test('jxCol-');
             });
             hClasses.each(function (cls) {
                 if (cls.test(name)) {
@@ -27125,7 +29801,7 @@
     }
 
 });
-// $Id: $
+// $Id: row.js 809 2010-03-28 04:15:14Z jonlb at comcast.net $
 /**
  * Class: Jx.Row
  *
@@ -27143,6 +29819,7 @@
  */
 Jx.Row = new Class({
 
+	Family: 'Jx.Row',
     Extends : Jx.Object,
 
     options : {
@@ -27169,7 +29846,7 @@
         },
         /**
          * Option: rowHeight
-         * The height of the row. Make it null or 'auto' to auto-calculate
+         * The height of the row. Make it null or 'auto' to auto-calculate.
          */
         rowHeight : 20,
         /**
@@ -27178,30 +29855,26 @@
          */
         headerWidth : 20,
         /**
-         * Option: headerField
-         * The field in the model to use as the header
+         * Option: headerColumn
+         * The name of the column in the model to use as the header
          */
-        headerField : 'id',
-        /**
-         * Option: templates
-         * objects used to determine the type of tag and css class to
-         * assign to a header cell. The css class can
-         * also be a function that returns a string to assign as the css
-         * class. The function will be passed the text to be formatted.
-         */
-        templates: {
-            header: {
-                tag: 'span',
-                cssClass: null
-            }
-        }
-
+        headerColumn : 'id'
     },
     /**
      * Property: grid
      * A reference to the grid that this row model belongs to
      */
     grid : null,
+    /**
+     * Property: heights
+     * This will hold the calculated height of each row in the grid.
+     */
+    heights: [],
+    /**
+     * Property: rules
+     * A hash that will hold all of the CSS rules for the rows.
+     */
+    rules: $H(),
 
     parameters: ['options','grid'],
 
@@ -27220,16 +29893,18 @@
      * APIMethod: getGridRowElement
      * Used to create the TR for the main grid row
      */
-    getGridRowElement : function () {
+    getGridRowElement : function (row) {
 
         var tr = new Element('tr');
-        tr.setStyle('height', this.getHeight());
+        //tr.setStyle('height', this.getHeight());
         if (this.options.alternateRowColors) {
             tr.className = (this.grid.getModel().getPosition() % 2) ? this.options.rowClasses.even
                     : this.options.rowClasses.odd;
         } else {
             tr.className = this.options.rowClasses.all;
         }
+        tr.store('jxRowData', {row: row});
+        tr.addClass('jxGridRow'+row);
         return tr;
     },
     /**
@@ -27239,24 +29914,16 @@
     getRowHeaderCell : function () {
         //get and set text for element
         var model = this.grid.getModel();
-        var th = new Element('td', {
+        var th = new Element('th', {
             'class' : 'jxGridRowHead'
         });
-
-        var text = model.get(this.options.headerField);
-        var ht = this.options.templates.header;
-        var el = new Element(ht.tag, {
-            'class' : 'jxGridCellContent',
-            'html' : text
-        }).inject(th);
-        if ($defined(ht.cssClass)) {
-            if (Jx.type(ht.cssClass) === 'function') {
-                el.addClass(ht.cssClass.run(text));
-            } else {
-                el.addClass(ht.cssClass);
-            }
-        }
-
+        var col = this.grid.columns.getByName(this.options.headerColumn);
+        var el = col.getHTML();
+        el.inject(th);
+        th.store('jxCellData',{
+        	row: model.getPosition(),
+        	rowHeader: true
+        });
         return th;
 
     },
@@ -27267,20 +29934,65 @@
     getRowHeaderWidth : function () {
         //this can be drawn from the column for the
         //header field
-        var col = this.grid.columns.getByField(this.options.headerField);
-        return col.getWidth(true, true);
+    	var col = this.grid.columns.getByName(this.options.headerColumn);
+    	if (!$defined(col.getWidth())) {
+    		col.calculateWidth(true);
+    	}
+        return col.getWidth();
     },
 
     /**
      * APIMethod: getHeight
      * determines and returns the height of a row
      */
-    getHeight : function () {
+    getHeight : function (row) {
         //this should eventually compute a height, however, we would need
         //a fixed width to do so reliably. For right now, we use a fixed height
         //for all rows.
-        return this.options.rowHeight;
+    	if ((!$defined(this.options.rowHeight) || this.options.rowHeight === 'auto') && $defined(this.heights[row])) {
+    		return this.heights[row];
+    	} else if (Jx.type(this.options.rowHeight === 'number')) {
+    		return this.options.rowHeight;
+    	}
     },
+    calculateHeights : function () {
+    	//grab all rows in the grid body
+      document.id(this.grid.gridTableBody).getChildren().each(function(row){
+        row = document.id(row);
+        var data = row.retrieve('jxRowData');
+        var s = row.getContentBoxSize();
+        this.heights[data.row] = s.height;
+      },this);
+      document.id(this.grid.rowTableHead).getChildren().each(function(row){
+        row = document.id(row);
+        var data = row.retrieve('jxRowData');
+        if (data) {
+          var s = row.getContentBoxSize();
+          this.heights[data.row] = Math.max(this.heights[data.row],s.height);
+          if (Browser.Engine.webkit) {
+              //for some reason webkit (Safari and Chrome)
+              this.heights[data.row] -= 1;
+          }
+        }
+      },this);
+    	
+    },
+    
+    createRules: function(styleSheet, scope) {
+        this.grid.gridTableBody.getChildren().each(function(row, idx) {
+            var selector = scope+' .jxGridRow'+idx;
+            var rule = Jx.Styles.insertCssRule(selector, '', styleSheet);
+            this.rules.set('jxGridRow'+idx, rule);
+            rule.style.height = this.heights[idx] + "px";
+
+            if (Browser.Engine.webkit) {
+                selector += " th";
+                var thRule = Jx.Styles.insertCssRule(selector, '', styleSheet);
+                thRule.style.height = this.heights[idx] + "px";
+            }
+
+        }, this);
+    },
     /**
      * APIMethod: useHeaders
      * determines and returns whether row headers should be used
@@ -27297,10 +30009,10 @@
      */
     getRowHeader : function (list) {
         var th = this.getRowHeaderCell();
-        if (this.grid.model.getPosition() === 0) {
-            var rowWidth = this.getRowHeaderWidth();
-            th.setStyle("width", rowWidth);
-        }
+        //if (this.grid.model.getPosition() === 0) {
+        //    var rowWidth = this.getRowHeaderWidth();
+        //    th.setStyle("width", rowWidth);
+        //}
         th.store('jxCellData', {
             rowHeader: true,
             row: this.grid.model.getPosition()
@@ -27308,16 +30020,16 @@
         list.add(th);
     },
     /**
-     * APIMethod: getRowHeaderField
-     * returns the name of the model field that is used for the header
+     * APIMethod: getRowHeaderColumn
+     * returns the name of the column that is used for the row header
      */
-    getRowHeaderField : function () {
-        return this.options.headerField;
+    getRowHeaderColumn : function () {
+        return this.options.headerColumn;
     }
 });
-// $Id: $
+// $Id: plugin.js 822 2010-03-31 11:28:31Z conrad.barthelmes $
 /**
- * Class: Jx.Grid.Plugin
+ * Class: Jx.Plugin
  *
  * Extend: <Jx.Object>
  *
@@ -27353,9 +30065,26 @@
      */
     detach: function(obj){
         obj.deregisterPlugin(this);
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
     }
-
-});// $Id: $
+});// $Id: plugin.grid.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Plugin.Grid
  * Grid plugin namespace
@@ -27366,7 +30095,7 @@
  *
  * This file is licensed under an MIT style license
  */
-Jx.Plugin.Grid = {};// $Id: grid.js 637 2009-11-21 06:50:06Z jonlb at comcast.net $
+Jx.Plugin.Grid = {};// $Id: grid.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Grid
  *
@@ -27391,8 +30120,7 @@
  * Sorter - sorts rows by specific column
  *
  * Jx.Grid renders data that comes from an external source.  This external
- * source, called the model, must be a Jx.Store or extended from it (such as
- * Jx.Store.Remote).
+ * source, called the model, must be a Jx.Store or extended from it.
  *
  * Events:
  * gridCellEnter(cell, list) - called when the mouse enters a cell
@@ -27412,6 +30140,9 @@
     Family : 'Jx.Grid',
     Extends : Jx.Widget,
 
+    Binds: ['modelChanged','render','addRow','removeRow','removeRows',
+            'onSelect', 'onUnselect','onMouseEnter','onMouseLeave'],
+    
     options : {
         /**
          * Option: parent
@@ -27446,7 +30177,7 @@
 
         /**
          * Option: model
-         * An instance of Jx.Store or one of its descendants
+         * An instance of Jx.Store
          */
         model : null,
 
@@ -27496,13 +30227,16 @@
      */
     init : function () {
         this.uniqueId = this.generateId('jxGrid_');
+        
         var opts;
         if ($defined(this.options.model)
                 && this.options.model instanceof Jx.Store) {
             this.model = this.options.model;
-            this.model.addEvent('storeColumnChanged', this.modelChanged
-                    .bind(this));
-            this.model.addEvent('storeSortFinished', this.render.bind(this));
+            this.model.addEvent('storeColumnChanged', this.modelChanged);
+            this.model.addEvent('storeSortFinished', this.render);
+            this.model.addEvent('storeRecordAdded', this.addRow);
+            this.model.addEvent('storeRecordRemoved', this.removeRow);
+            this.model.addEvent('storeMultipleRecordsRemoved', this.removeRows);
         }
 
         if ($defined(this.options.columns)) {
@@ -27557,7 +30291,7 @@
         this.colTable = new Element('table', {
             'class' : 'jxGridTable jxGridHeader'
         });
-        this.colTableBody = new Element('tbody');
+        this.colTableBody = new Element('thead');
         this.colTable.appendChild(this.colTableBody);
         this.colObj.appendChild(this.colTable);
 
@@ -27580,7 +30314,7 @@
             }
         });
         this.gridTable = new Element('table', {
-            'class' : 'jxGridTable'
+            'class' : 'jxGridTable jxGridContent'
         });
         this.gridTableBody = new Element('tbody');
         this.gridTable.appendChild(this.gridTableBody);
@@ -27595,19 +30329,11 @@
 
         this.gridObj.addEvent('scroll', this.onScroll.bind(this));
 
-        //bind events
-        this.bound = {
-            select: this.onSelect.bind(this),
-            unselect: this.onUnselect.bind(this),
-            mouseenter: this.onMouseEnter.bind(this),
-            mouseleave: this.onMouseLeave.bind(this)
-        };
-
         //setup the selection
         this.selection = new Jx.Selection();
         this.selection.addEvents({
-            select: this.bound.select,
-            unselect: this.bound.unselect
+            select: this.onSelect,
+            unselect: this.onUnselect
         });
         this.parent();
 
@@ -27643,6 +30369,7 @@
         var size = this.domObj.getContentBoxSize();
 
         //sum all of the column widths except the hidden columns and the header column
+        /**
         var w = size.width - rowWidth - 1;
         var totalCols = 0;
         this.columns.columns.each(function (col) {
@@ -27651,7 +30378,8 @@
                 totalCols += col.getWidth();
             }
         }, this);
-
+		**/
+        
         /* -1 because of the right/bottom borders */
         this.rowColObj.setStyles({
             width : rowWidth - 1,
@@ -27725,6 +30453,12 @@
         this.gridTable.replaceChild(n, this.gridTableBody);
         this.gridTableBody = n;
 
+        document.id(this.rowColObj).empty();
+        
+        if (Jx.Styles.isStyleSheetDefined(this.styleSheet)) {
+        	Jx.Styles.removeStyleSheet(this.styleSheet);
+        }
+
     },
 
     /**
@@ -27758,37 +30492,50 @@
                 this.columns.getHeaders(headerList);
 
                 /* one extra column at the end for filler */
-                th = new Element('td', {
-                    'class':'jxGridColHead'
+                th = new Element('th', {
+                    'class':'jxGridColHead',
+                    styles: {
+                      width: 1000,
+                      height: colHeight - 1
+                    }
                 }).inject(trBody);
-                new Element('span',{
-                    'class': 'jxGridCellContent',
-                    styles : {
-                        width : 1000,
-                        height : colHeight - 1
-                    }
-                }).inject(th);
-
             } else {
                 //hide the headers
                 this.colTableBody.setStyle('visibility', 'hidden');
             }
 
-            if (this.row.useHeaders()) {
-                this.rowTableHead.setStyle('visibility', 'visible');
+            //This section actually adds the rows
+            this.model.first();
+            while (this.model.valid()) {
+                tr = this.row.getGridRowElement(this.model.getPosition());
+                var rl = this.makeList(tr);
+                this.gridTableBody.appendChild(tr);
+                //this.rowList.add(rl.container);
 
-                var rowHeight = this.row.getHeight();
+                //Actually add the columns
+                this.columns.getColumnCells(rl);
 
+                if (this.model.hasNext()) {
+                    this.model.next();
+                } else {
+                    break;
+                }
 
+            }
+            
+            
+            //Moved rowheaders after other columns so we can figure the heights
+            //of each row (after render)
+            if (this.row.useHeaders()) {
+                this.rowTableHead.setStyle('visibility', 'visible');
 
                 //loop through all rows and add header
                 this.model.first();
                 while (this.model.valid()) {
-                    var tr = new Element('tr', {
-                        styles : {
-                            height : rowHeight
-                        }
+                    var tr = new Element('tr',{
+                    	'class': 'jxGridRow'+this.model.getPosition()
                     });
+                    tr.store('jxRowData', {row:this.model.getPosition()});
                     var rowHeaderList = this.makeList(tr);
                     this.row.getRowHeader(rowHeaderList);
                     this.rowTableHead.appendChild(tr);
@@ -27800,7 +30547,7 @@
                 }
                 /* one extra row at the end for filler */
                 tr = new Element('tr').inject(this.rowTableHead);
-                th = new Element('td', {
+                th = new Element('th', {
                     'class' : 'jxGridRowHead',
                     styles : {
                         width : this.row.getRowHeaderWidth(),
@@ -27811,35 +30558,15 @@
                 //hide row headers
                 this.rowTableHead.setStyle('visibility', 'hidden');
             }
-
-            colHeight = this.columns.getHeaderHeight();
-
-
-            //This section actually adds the rows
-            this.model.first();
-            while (this.model.valid()) {
-                tr = this.row.getGridRowElement();
-                tr.store('jxRowData', {row: this.model.getPosition()});
-
-
-                var rl = this.makeList(tr);
-                this.gridTableBody.appendChild(tr);
-                //this.rowList.add(rl.container);
-
-                //Actually add the columns
-                this.columns.getColumnCells(rl);
-
-                if (this.model.hasNext()) {
-                    this.model.next();
-                } else {
-                    break;
-                }
-
-            }
-
+            
+            this.domObj.resize();
+            this.resize();
+            this.columns.calculateWidths();
             Jx.Styles.enableStyleSheet(this.styleSheet);
             this.columns.createRules(this.styleSheet, "."+this.uniqueId);
-            this.domObj.resize();
+            this.row.calculateHeights();
+            this.row.createRules(this.styleSheet, "."+this.uniqueId);
+            
             this.fireEvent('doneCreateGrid', this);
         } else {
             this.model.load();
@@ -27853,21 +30580,88 @@
      */
     modelChanged : function (row, col) {
         //grab new TD
-        var column = this.columns.getIndexFromGrid(col.name);
+        var column = this.columns.getIndexFromGrid(col);
         var td = document.id(this.gridObj.childNodes[0].childNodes[0].childNodes[row].childNodes[column]);
 
         var currentRow = this.model.getPosition();
         this.model.moveTo(row);
-
-        var newTD = this.columns.getColumnCell(this.columns.getByName(col.name));
+        // need to find out whether the header is used or not, to have the right reference back
+        var colIndex = this.options.row.useHeaders ? column+1 : column;
+        var newTD = this.columns.getColumnCell(this.columns.getByName(col),colIndex);
         //get parent list
         var list = td.getParent().retrieve('jxList');
         list.replace(td, newTD);
-        //newTD.replaces(td);
-
+        this.columns.updateRule(col);
         this.model.moveTo(currentRow);
     },
+    
     /**
+     * APIMethod: addRow
+     * Adds a row to the table. Can add to either the beginning or the end 
+     * based on passed flag
+     */
+    addRow: function (store, record, position) {
+        if (this.model.loaded) {
+            if (position === 'bottom') {
+                this.model.last();
+            } else {
+                this.model.first();
+                this.renumberGrid(0, 1);
+            }
+            
+            //row header
+            if (this.row.useHeaders()) {
+                var rowHeight = this.row.getHeight();
+                var tr = new Element('tr', {
+                    styles : {
+                        height : rowHeight
+                    }
+                });
+                var rowHeaderList = this.makeList(tr);
+                this.row.getRowHeader(rowHeaderList);
+                if (position === 'top') {
+                    tr.inject(this.rowTableHead, position);
+                } else {
+                    var lastTr = this.rowTableHead.children[this.rowTableHead.children.length - 1];
+                    tr.inject(lastTr, 'before');
+                }
+            }
+            tr = this.row.getGridRowElement();
+            tr.store('jxRowData', {row: this.model.getPosition()});
+            var rl = this.makeList(tr);
+            this.columns.getColumnCells(rl);
+            tr.inject(this.gridTableBody, position);
+        }
+    },
+    
+    renumberGrid: function (offset, increment) {
+        var l = this.gridTable.rows.length;
+        for (var i = offset; i < l; i++) {
+            var r = document.id(this.gridTable.rows[i]);
+            var d = r.retrieve('jxRowData');
+            d.row += increment;
+            r.store('jxRowData', d);
+            $A(r.children).each(function(cell){
+                var d = cell.retrieve('jxCellData');
+                d.row += increment;
+                cell.store('jxCellData', d);
+            },this);
+        }
+    },
+    
+    removeRow: function (store, index) {
+        this.gridTable.deleteRow(index);
+        this.rowTable.deleteRow(index);
+        this.renumberGrid(index, -1);
+    },
+    
+    removeRows: function (store, first, last) {
+      for (var i = first; i <= last; i++) {
+          this.removeRow(first);
+      }
+    },
+    
+    /**
      * Method: makeList
      * utility method used to make row lists
      *
@@ -27881,8 +30675,8 @@
         }, this.selection);
         var target = this;
         l.addEvents({
-            mouseenter: this.bound.mouseenter,
-            mouseleave: this.bound.mouseleave
+            mouseenter: this.onMouseEnter,
+            mouseleave: this.onMouseLeave
         });
         this.lists.push(l);
         return l;
@@ -27905,8 +30699,272 @@
     }
 
 });
-// $Id: $
 /**
+ * Class: Jx.Grid.Renderer
+ * This is the base class and namespace for all grid renderers.
+ * 
+ * Extends: <Jx.Widget>
+ * We extended Jx.Widget to take advantage of templating support.
+ */
+Jx.Grid.Renderer = new Class({
+  
+  Family: 'Jx.Grid.Renderer',
+  Extends: Jx.Widget,
+  
+  parameters: ['options'],
+  
+  options: {
+    deferRender: true,
+    /**
+     * Option: template
+     * The template for rendering this cell. Will be processed as per
+     * the Jx.Widget standard.
+     */
+    template: '<span class="jxGridCellContent"></span>'
+  },
+
+  classes: $H({
+    domObj: 'jxGridCellContent'
+  }),
+
+  column: null,
+
+  init: function () {
+    this.parent();
+  },
+  
+  render: function () {
+    this.parent();
+  },
+  
+  setColumn: function (column) {
+    if (column instanceof Jx.Column) {
+      this.column = column;
+    }
+  }
+  
+});/**
+ * Class: Jx.Grid.Renderer.Text
+ * This is the default renderer for grid cells. It works the same as the 
+ * original column implementation. It needs a store, a field name, and an 
+ * optional formatter as well as other options.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Text = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Text',
+  Extends: Jx.Grid.Renderer,
+  
+  options: {
+        /**
+         * Option: formatter
+         * an instance of <Jx.Formatter> or one of its subclasses which
+         * will be used to format the data in this column. It can also be
+         * an object containing the name (This should be the part after
+         * Jx.Formatter in the class name. For instance, to get a currency
+         * formatter, specify 'Currency' as the name.) and options for the
+         * needed formatter (see individual formatters for options).
+         * (code)
+         * {
+         *    name: 'formatter name',
+         *    options: {}
+         * }
+         * (end)
+         */
+        formatter: null,
+        /**
+         * Option: textTemplate
+         * Will be used for creating the text that goes iside the template. Use
+         * placeholders for indicating the field(s). You can add as much text 
+         * as you want. for example, if you wanted to display someone's full 
+         * name that is brokem up in the model with first and last names you 
+         * can write a template like '{lastName}, {firstName}' and as long as 
+         * the text between { and } are field names in the store they will be
+         * substituted properly.
+         */
+        textTemplate: null,
+        /**
+         * Option: css
+         * A string or function to use in adding classes to the text
+         */
+        css: null
+  },
+  
+  store: null,
+  
+  columnsNeeded: null,
+  
+  
+  init: function () {
+    this.parent();
+    //check the formatter
+    if ($defined(this.options.formatter)
+                && !(this.options.formatter instanceof Jx.Formatter)) {
+            var t = Jx.type(this.options.formatter);
+            if (t === 'object') {
+                this.options.formatter = new Jx.Formatter[this.options.formatter.name](
+                        this.options.formatter.options);
+            }
+        }
+  },
+  
+  setColumn: function (column) {
+    this.parent();
+    
+    this.store = column.grid.getModel();
+    
+    if ($defined(this.options.textTemplate)) {
+      this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+    }
+  },
+  
+  render: function () {
+    this.parent();
+    
+    var text = '';
+    if ($defined(this.options.textTemplate)) {
+      text = this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded);
+    } 
+    
+        if ($defined(this.options.formatter)) {
+            text = this.options.formatter.format(text);
+        }
+        
+        this.domObj.set('html',text);
+        
+        if ($defined(this.options.css) && Jx.type(this.options.css) === 'function') {
+          this.domObj.addClass(this.options.css.run(text));
+        } else if ($defined(this.options.css) && Jx.type(this.options.css) === 'string'){
+          this.domObj.addClass(this.options.css);
+        }
+        
+  }
+    
+});/**
+ * Class: Jx.Grid.Renderer.CheckBox
+ * Renders a checkbox into the cell. Allows options for connecting the cell
+ * to a model field and propogating changes back to the store.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Checkbox = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Checkbox',
+  Extends: Jx.Grid.Renderer,
+  
+  Binds: ['onBlur','onChange'],
+  
+  options: {
+    useStore: false,
+    field: null,
+    updateStore: false,
+    checkboxOptions: {
+      template : '<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',
+      name: ''
+    }
+  },
+  
+  init: function () {
+    this.parent();
+  },
+  
+  render: function () {
+    this.parent();
+    var checkbox = new Jx.Field.Checkbox(this.options.checkboxOptions);
+    this.domObj.adopt(document.id(checkbox));
+    
+    if (this.options.useStore) {
+      //set initial state
+      checkbox.setValue(this.store.get(this.options.field));
+    }
+    
+    //hook up change and blur events to change store field
+    checkbox.addEvents({
+      'blur': this.onBlur,
+      'change': this.onChange
+    });
+  },
+  
+  setColumn: function (column) {
+    this.column = column;
+    
+    if (this.options.useStore) {
+      this.store = this.column.grid.getModel();
+    }
+  },
+  
+  onBlur: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  onChange: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  updateStore: function (field) {
+    var newValue = field.getValue();
+    
+    var data = document.id(field).getParent().retrieve('jxCellData');
+    var row = data.row;
+    
+    if (this.store.get(this.options.field, row) !== newValue) {
+      this.store.set(this.options.field, newValue, row);
+    }
+  }
+  
+  
+});/**
+ * Class: Jx.Grid.Renderer.Button
+ * Renders a <Jx.Button> into the cell. You can add s many buttons as you'd like per column by passing button configs
+ * in as an array option to options.buttonOptions
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Button = new Class({
+
+    Family: 'Jx.Grid.Renderer.Button',
+    Extends: Jx.Grid.Renderer,
+
+    Binds: [],
+
+    options: {
+        template: '<span class="buttons"></span>',
+        /**
+         * Option: buttonOptions
+         * an array of option configurations for <Jx.Button>
+         */
+        buttonOptions: null
+    },
+
+    classes:  $H({
+        domObj: 'buttons'
+    }),
+
+    init: function () {
+        this.parent();
+    },
+
+    render: function () {
+        this.parent();
+
+        $A(this.options.buttonOptions).each(function(opts){
+            var button = new Jx.Button(opts);
+            this.domObj.grab(document.id(button));
+        },this);
+
+    }
+});// $Id: grid.selector.js 826 2010-03-31 18:46:16Z pagameba $
+/**
  * Class: Jx.Plugin.Selector
  *
  * Extends: <Jx.Plugin>
@@ -27923,7 +30981,10 @@
  */
 Jx.Plugin.Grid.Selector = new Class({
 
+	Family: 'Jx.Plugin.Grid.Selector',
     Extends : Jx.Plugin,
+    
+    Binds: ['select','checkSelection','checkAll','afterGridRender'],
 
     options : {
         /**
@@ -27940,13 +31001,30 @@
          * Option: column
          * determines if columns are selectable
          */
-        column : false
+        column : false,
+        /**
+         * Option: multiple
+         * Allow multiple selections
+         */
+        multiple: false,
+        /**
+         * Option: useCheckColumn
+         * Whether to use a check box column as the row header or as the 
+         * first column in the grid and use it for manipulating selections.
+         */
+        useCheckColumn: false,
+        /**
+         * Option: checkAsHeader
+         * Determines if the check column is the header of the rows
+         */
+        checkAsHeader: false
     },
     /**
-     * Property: bound
-     * storage for bound methods useful for working with events
+     * Property: selected
+     * Holds arrays of selected rows and/or columns and their headers
      */
-    bound: {},
+    selected: null,
+    
     /**
      * APIMethod: init
      * construct a new instance of the plugin.  The plugin must be attached
@@ -27954,7 +31032,12 @@
      */
     init: function() {
         this.parent();
-        this.bound.select = this.select.bind(this);
+        this.selected = $H({
+            columns: [],
+            rows: [],
+            rowHeads: [],
+            columnHeads: []
+        });
     },
     /**
      * APIMethod: attach
@@ -27969,22 +31052,85 @@
             return;
         }
         this.grid = grid;
-        this.grid.addEvent('gridCellSelect', this.bound.select);
+        this.grid.addEvent('gridCellSelect', this.select);
         if (this.options.cell) {
             this.oldSelectionClass = this.grid.selection.options.selectedClass;
             this.grid.selection.options.selectClass = "jxGridCellSelected";
+            if (this.options.multiple) {
+            	this.grid.selection.options.selectMode = 'multiple';
+            }
         }
+        
+        //setup check column if needed
+        if (this.options.useCheckColumn) {
+        	
+        	var template = '<span class="jxGridCellContent">';
+        	
+        	if (this.options.multiple) {
+        		template += '<span class="jxInputContainer jxInputContainerCheck"><input class="jxInputCheck" type="checkbox" name="checkAll" id="checkAll"/></span>';
+        	} else {
+        		template += '</span>';
+        	}
+        	
+        	template += "</span>";
+        	
+        	this.checkColumn = new Jx.Column({
+        		template: template,
+        		renderMode: 'fit',
+        		renderer: new Jx.Grid.Renderer.Checkbox({
+        		//	onChange: this.checkSelection
+        		}),
+        		name: 'selection'
+        	}, this.grid);
+        	this.grid.columns.columns.reverse();
+        	this.grid.columns.columns.push(this.checkColumn);
+        	this.grid.columns.columns.reverse();
+        	
+        	if (this.options.checkAsHeader) {
+        		this.oldHeaderColumn = this.grid.row.options.headerColumn;
+        		this.grid.row.options.headerColumn = 'selection';
+        		
+        		if (this.options.multiple) {
+                    this.grid.addEvent('doneCreateGrid', this.afterGridRender);
+        		}
+        	}
+            //attach event to header
+            if (this.options.multiple) {
+                var ch = document.id(this.checkColumn).getElement('input');
+                ch.addEvents({
+                    'change': this.checkAll
+                });
+            }
+
+        }
     },
+
+    afterGridRender: function () {
+        if (this.options.checkAsHeader) {
+            var chkCol = document.id(this.checkColumn).clone();
+            chkCol.getElement('input').addEvent('change',this.checkAll);
+            this.grid.rowColObj.adopt(chkCol);
+            //document.id(this.checkColumn).inject(this.grid.rowColObj);
+        }
+        this.grid.removeEvent('doneCreateGrid',this.afterGridRender);
+    },
     /**
      * APIMethod: detach
      */
     detach: function() {
         if (this.grid) {
-            this.grid.removeEvent('gridCellSelect', this.bound.select);
+            this.grid.removeEvent('gridCellSelect', this.select);
             if (this.options.cell) {
                 this.grid.selection.options.selectedClass = this.oldSelectionClass;
             }
         }
+        if (this.options.useCheckColumn) {
+        	var col = this.grid.columns.getByName('selection');
+        	this.grid.columns.columns.erase(col);
+        	if (this.options.checkAsHeader) {
+        		this.grid.row.options.headerColumn = this.oldHeaderColumn;
+        	}
+        }
         this.grid = null;
     },
     /**
@@ -28017,19 +31163,29 @@
             this.grid.selection.options.selectClass = this.oldSelectionClass;
             
         } else if (opt === 'row') {
-            this.selectedRow.removeClass('jxGridRowSelected');
-            this.selectedRow = null;
-            this.selectedRowHead.removeClass('jxGridRowHeaderSelected');
-            this.selectedRowHead = null;
+        	
+        	this.selected.get('rows').each(function(row){
+        		row.removeClass('jxGridRowSelected');
+        	},this);
+        	this.selected.set('rows',[]);
+        	
+        	this.selected.get('rowHeads').each(function(rowHead){
+        		rowHead.removeClass('jxGridRowHeaderSelected');
+        	},this);
+        	this.selected.set('rowHeads',[]);
+        	
         } else {
-            if ($defined(this.selectedCol)) {
+            this.selected.get('columns').each(function(column){
                 for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
-                    this.grid.gridTable.rows[i].cells[this.selectedCol].removeClass('jxGridColumnSelected');
+                    this.grid.gridTable.rows[i].cells[column].removeClass('jxGridColumnSelected');
                 }
-            }
-            this.selectedColHead.removeClass('jxGridColumnHeaderSelected');
-            this.selectedColHead = null;
-            this.selectedCol = null;
+            },this);
+            this.selected.set('columns',[]);
+            
+            this.selected.get('columnHeads').each(function(rowHead){
+        		rowHead.removeClass('jxGridColumnHeaderSelected');
+        	},this);
+        	this.selected.set('columnHeads',[]);
         }
     },
     /**
@@ -28038,16 +31194,20 @@
      */
     select : function (cell) {
 
-        console.log('select method');
+        // console.log('select method');
         var data = cell.retrieve('jxCellData');
-        console.log(data);
+        // console.log(data);
 
-        if (this.options.row) {
+        if (this.options.row && $defined(data.row)) {
             this.selectRow(data.row);
         }
 
-        if (this.options.column) {
-            this.selectColumn(data.index - 1);
+        if (this.options.column && $defined(data.index)) {
+            if (this.grid.row.useHeaders()) {
+                this.selectColumn(data.index - 1);
+            } else {
+                this.selectColumn(data.index);
+            }
         }
 
     },
@@ -28062,23 +31222,58 @@
         if (!this.options.row) { return; }
 
         var tr = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
+        tr = document.id(tr);
+        
+        var rows = this.selected.get('rows');
+	    if (tr.hasClass('jxGridRowSelected')) {
+	        tr.removeClass('jxGridRowSelected');
+	        this.setCheckField(row, false);
 
-        if (tr.hasClass('jxGridRowSelected')) {
-            this.selectedRow.removeClass('jxGridRowSelected');
-            this.selectedRow = null;
-        } else {
-            if (this.selectedRow) {
-                this.selectedRow.removeClass('jxGridRowSelected');
+            if (this.options.multiple && this.options.useCheckColumn) {
+                if (this.options.checkAsHeader) {
+                    document.id(this.grid.rowColObj).getElement('input').removeProperty('checked');
+                } else {
+                    document.id(this.checkColumn).getElement('input').removeProperty('checked');
+                }
             }
-            this.selectedRow = $(tr);
-            this.selectedRow.addClass('jxGridRowSelected');
+
+	        //search array and remove this item
+	        rows.erase(tr);
+	    } else {
+	        rows.push(tr);
+	        tr.addClass('jxGridRowSelected');
+	        this.setCheckField(row, true);
+	    }
+	        
+	    if (!this.options.multiple) {
+	    	rows.each(function(row){
+	    		if (row !== tr) {
+	    			row.removeClass('jxGridRowSelected');
+	    			this.setCheckField(row.retrieve('jxRowData').row,false);
+	    			rows.erase(row);
+	    		}
+	    	},this);
         }
-        this.selectRowHeader(row);
+        	
+	    this.selectRowHeader(row);
 
     },
+    
+    setCheckField: function (row, checked) {
+    	if (this.options.useCheckColumn) {
+    		var check;
+    		if (this.options.checkAsHeader) {
+    			check = document.id(this.grid.rowTableHead.rows[row].cells[0]).getFirst().getFirst();
+    		} else {
+    			var col = this.grid.columns.getIndexFromGrid(this.checkColumn.name);
+    			check = document.id(this.grid.gridTableBody.rows[row].cells[col]).getFirst().getFirst();
+    		}
+        	check.retrieve('field').setValue(checked);
+        }
+    },
     /**
      * Method: selectRowHeader
-     * Apply the jxGridRowHea}derSelected style to the row header cell of a
+     * Apply the jxGridRowHeaderSelected style to the row header cell of a
      * selected row.
      *
      * Parameters:
@@ -28088,20 +31283,31 @@
         if (!this.grid.row.useHeaders()) {
             return;
         }
-        var cell = (row >= 0 && row < this.grid.rowTableHead.rows.length) ? this.grid.rowTableHead.rows[row].cells[0] : null;
+        var cell = (row >= 0 && row < this.grid.rowTableHead.rows.length) ? 
+        		this.grid.rowTableHead.rows[row].cells[0] : null;
 
         if (!cell) {
             return;
         }
-        if (this.selectedRowHead) {
-            this.selectedRowHead.removeClass('jxGridRowHeaderSelected');
+        cell = document.id(cell);
+        var cells = this.selected.get('rowHeads');
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridRowHeaderSelected');
+            cells.erase(cell);
+        } else {
+        	cell.addClass('jxGridRowHeaderSelected');
+        	cells.push(cell);
         }
-        if (this.selectedRowHead !== cell) {
-            this.selectedRowHead = $(cell);
-            cell.addClass('jxGridRowHeaderSelected');
-        } else if (cell.hasClass('jxgridRowHeaderSelected')) {
-            this.selectedRowHead = null;
+        
+        if (!this.options.multiple) {
+        	cells.each(function(c){
+        		if (c !== cell) {
+        			c.removeClass('jxGridRowHeaderSelected');
+        			cells.erase(c);
+        		}
+        	},this);
         }
+        
     },
     /**
      * Method: selectColumn
@@ -28113,19 +31319,33 @@
      */
     selectColumn: function (col) {
         if (col >= 0 && col < this.grid.gridTable.rows[0].cells.length) {
-            if ($defined(this.selectedCol)) {
-                for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
-                    this.grid.gridTable.rows[i].cells[this.selectedCol].removeClass('jxGridColumnSelected');
-                }
-            }
-            if (col !== this.selectedCol) {
-                this.selectedCol = col;
-                for (i = 0; i < this.grid.gridTable.rows.length; i++) {
-                    this.grid.gridTable.rows[i].cells[col].addClass('jxGridColumnSelected');
-                }
+        	var cols = this.selected.get('columns');
+        	
+        	var m = '';
+            if (cols.contains(col)) {
+            	//deselect
+            	m = 'removeClass';
+            	cols.erase(col);
             } else {
-                this.selectedCol = null;
+            	//select
+            	m = 'addClass';
+            	cols.push(col);
             }
+            for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                this.grid.gridTable.rows[i].cells[col][m]('jxGridColumnSelected');
+            }
+            
+            if (!this.options.multiple) {
+            	cols.each(function(c){
+            		if (c !== col) {
+            			for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                            this.grid.gridTable.rows[i].cells[c].removeClass('jxGridColumnSelected');
+                        }
+            			cols.erase(c);
+            		}
+            	},this);
+            }
+            
             this.selectColumnHeader(col);
         }
     },
@@ -28144,25 +31364,79 @@
         }
 
 
-        var cell = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col]
-                                                                                                                          : null;
+        var cell = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? 
+        		this.grid.colTableBody.rows[0].cells[col] : null;
+        		
         if (cell === null) {
             return;
         }
 
-        if (this.selectedColHead) {
-            this.selectedColHead.removeClass('jxGridColumnHeaderSelected');
+        cell = document.id(cell);
+        cells = this.selected.get('columnHeads');
+        
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridColumnHeaderSelected');
+            cells.erase(cell);
+        } else {
+        	cell.addClass('jxGridColumnHeaderSelected');
+        	cells.push(cell);
         }
-        if (this.selectedColHead !== cell) {
-            this.selectedColHead = $(cell);
-            cell.addClass('jxGridColumnHeaderSelected');
+        
+        if (!this.options.multiple) {
+        	cells.each(function(c){
+        		if (c !== cell) {
+        			c.removeClass('jxGridColumnHeaderSelected');
+        			cells.erase(c);
+        		}
+        	},this);
+        }
+
+    },
+    /**
+     * Method: checkSelection
+     * Checks whether a row's check box is/isn't checked and modifies the 
+     * selection appropriately.
+     * 
+     * Parameters:
+     * column - <Jx.Column> that created the checkbox
+     * field - <Jx.Field.Checkbox> instance that was checked/unchecked
+     */
+    checkSelection: function (column, field) {
+    	var data = document.id(field).getParent().retrieve('jxCellData');
+    	this.selectRow(data.row);
+    },
+    /**
+     * Method: checkAll
+     * Checks all checkboxes in the column the selector inserted.
+     */
+    checkAll: function () {
+        var col;
+        var rows;
+        var checked;
+
+        checked = this.options.checkAsHeader ? this.grid.rowColObj.getElement('input').get('checked') :
+                this.checkColumn.domObj.getElement('input').get('checked');
+
+        if (this.options.checkAsHeader) {
+            col = 0;
+            rows = this.grid.rowTableHead.rows;
         } else {
-            this.selectedColHead = null;
+            col = this.grid.columns.getIndexFromGrid(this.checkColumn.name);
+            rows = this.grid.gridTableBody.rows;
         }
 
+        $A(rows).each(function(row, idx) {
+            var check = row.cells[col].getElement('input');
+            if ($defined(check)) {
+                var rowChecked = check.get('checked');
+                if (rowChecked !== checked) {
+                    this.selectRow(idx);
+                }
+            }
+        }, this);
     }
 });
-// $Id: $
+// $Id: grid.prelighter.js 662 2009-12-08 06:56:43Z jonlb at comcast.net $
 /**
  * Class: Jx.Plugin.Prelighter
  *
@@ -28302,7 +31576,11 @@
             this.prelightRow(data.row, on);
         }
         if (this.options.column) {
-            this.prelightColumn(data.index - 1, on);
+            if (this.grid.row.useHeaders()) {
+                this.prelightColumn(data.index - 1, on);
+            } else {
+                this.prelightColumn(data.index, on);
+            }
         }
         if (this.options.rowHeader) {
             this.prelightRowHeader(data.row, on);
@@ -28439,7 +31717,7 @@
         }
     }
 });
-// $Id: $
+// $Id: grid.sorter.js 809 2010-03-28 04:15:14Z jonlb at comcast.net $
 /**
  * Class: Jx.Plugin.Sorter
  *
@@ -28447,8 +31725,6 @@
  *
  * Grid plugin to sort the grid by a single column.
  *
- * Original selection code from Jx.Grid's original class
- *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
  *
@@ -28456,9 +31732,10 @@
  */
 Jx.Plugin.Grid.Sorter = new Class({
 
+    Family: 'Jx.Plugin.Grid.Sorter',
     Extends : Jx.Plugin,
+    Binds: ['sort', 'addHeaderClass'],
 
-    options : {},
     /**
      * Property: current
      * refernce to the currently sorted column
@@ -28475,21 +31752,6 @@
      */
     currentGridIndex : null,
     /**
-     * Property: bound
-     * storage for bound methods useful for working with events
-     */
-    bound: {},
-    /**
-     * APIMethod: init
-     * construct a new instance of the plugin.  The plugin must be attached
-     * to a Jx.Grid instance to be useful though.
-     */
-    init: function() {
-        this.parent();
-        this.bound.sort = this.sort.bind(this);
-        this.bound.addHeaderClass = this.addHeaderClass.bind(this);
-    },
-    /**
      * APIMethod: attach
      * Sets up the plugin and attaches the plugin to the grid events it
      * will be monitoring
@@ -28501,15 +31763,14 @@
 
         this.grid = grid;
 
-        this.grid.addEvent('gridCellSelect', this.bound.sort);
-        this.boundAddHeader = this.addHeaderClass.bind(this);
+        this.grid.addEvent('gridCellSelect', this.sort);
     },
     /**
      * APIMethod: detach
      */
     detach: function() {
         if (this.grid) {
-            this.grid.removeEvent('gridCellSelect', this.bound.sort);
+            this.grid.removeEvent('gridCellSelect', this.sort);
         }
         this.grid = null;
     },
@@ -28534,12 +31795,15 @@
                     this.currentGridIndex = data.index - 1;
                 }
 
-                //The grid should be listening for the sortFinished event and will re-render the grid
-                //we will listen for the grid's doneCreateGrid event to add the header
-                this.grid.addEvent('doneCreateGrid', this.bound.addHeaderClass);
+                // The grid should be listening for the sortFinished event and
+                // will re-render the grid we will listen for the grid's
+                // doneCreateGrid event to add the header
+                this.grid.addEvent('doneCreateGrid', this.addHeaderClass);
                 //sort the store
                 var strategy = this.grid.getModel().getStrategy('sort');
-                strategy.sort(this.current.name, null, this.direction);
+                if (strategy) {
+                  strategy.sort(this.current.name, null, this.direction);
+                }
             }
 
         }
@@ -28550,14 +31814,14 @@
      * column we sorted by so that the sort arrow shows
      */
     addHeaderClass : function () {
-        this.grid.removeEvent('doneCreateGrid', this.bound.addHeaderClass);
+        this.grid.removeEvent('doneCreateGrid', this.addHeaderClass);
 
         //get header TD
         var th = this.grid.colTable.rows[0].cells[this.currentGridIndex];
         th.addClass('jxGridColumnSorted' + this.direction.capitalize());
     }
 });
-// $Id: $
+// $Id: grid.resize.js 796 2010-03-26 19:56:43Z pagameba $
 /**
  * Class: Jx.Plugin.Resize
  *
@@ -28574,53 +31838,44 @@
 Jx.Plugin.Grid.Resize = new Class({
 
     Extends : Jx.Plugin,
-
+    Binds: ['createHandles','removeHandles'],
     options: {
         /**
-         * Option: columns
+         * Option: column
          * set to true to make column widths resizeable
          */
-        columns: false,
+        column: false,
         /**
-         * Option: rows
+         * Option: row
          * set to true to make row heights resizeable
          */
-        rows: false,
+        row: false,
         /**
          * Option: tooltip
          * the tooltip to display for the draggable portion of the
          * cell header
          */
-        tooltip: 'Drag to resize, double click to auto-size.'
+        tooltip: MooTools.lang.get('Jx','plugin.resize').tooltip
     },
     /**
      * Property: els
      * the DOM elements by which the rows/columns are resized.
      */
-    els: [],
+    els: {
+      column: [],
+      row: []
+    },
 
     /**
      * Property: drags
      * the Drag instances
      */
-    drags: [],
+    drags: {
+      column: [],
+      row: []
+    },
 
     /**
-     * Property: bound
-     * storage for bound methods useful for working with events
-     */
-    bound: {},
-    /**
-     * APIMethod: init
-     * construct a new instance of the plugin.  The plugin must be attached
-     * to a Jx.Grid instance to be useful though.
-     */
-    init: function() {
-        this.parent();
-        this.bound.createResizeHandles = this.createResizeHandles.bind(this);
-        this.bound.removeResizeHandles = this.removeResizeHandles.bind(this);
-    },
-    /**
      * APIMethod: attach
      * Sets up the plugin and connects it to the grid
      */
@@ -28629,64 +31884,1247 @@
             return;
         }
         this.grid = grid;
-        this.grid.addEvent('doneCreateGrid', this.bound.createResizeHandles);
-        this.grid.addEvent('beginCreateGrid', this.bound.removeResizeHandles);
-        this.createResizeHandles();
+        this.grid.addEvent('doneCreateGrid', this.createHandles);
+        this.grid.addEvent('beginCreateGrid', this.removeHandles);
+        this.createHandles();
     },
     /**
      * APIMethod: detach
      */
     detach: function() {
         if (this.grid) {
-            this.grid.removeEvent('doneCreateGrid', this.bound.createResizeHandles);
-            this.grid.removeEvent('beginCreateGrid', this.bound.removeResizeHandles);
+            this.grid.removeEvent('doneCreateGrid', this.createHandles);
+            this.grid.removeEvent('beginCreateGrid', this.removeHandles);
         }
         this.grid = null;
     },
 
-    removeResizeHandles: function() {
-        this.els.each(function(el) { el.dispose(); } );
-        this.els = [];
-        this.drags.each(function(drag){ drag.detach(); });
-        this.drags = [];
+    activate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = true;
+        }
+        this.createHandles();
     },
-
-    createResizeHandles: function() {
-        if (this.options.columns && this.grid.columns.useHeaders()) {
+    
+    deactivate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = false;
+        }
+        this.createHandles();
+    },
+    /**
+     * Method: removeHandles
+     * clean up any handles we created
+     */
+    removeHandles: function() {
+        ['column','row'].each(function(option) {
+          this.els[option].each(function(el) { el.dispose(); } );
+          this.els[option] = [];
+          this.drags[option].each(function(drag){ drag.detach(); });
+          this.drags[option] = [];
+        }, this);
+    },
+    /**
+     * Method: createHandles
+     * create handles that let the user drag to resize columns and rows
+     */
+    createHandles: function() {
+        this.removeHandles();
+        if (this.options.column && this.grid.columns.useHeaders()) {
+            var hf = this.grid.row.getRowHeaderColumn();
             this.grid.columns.columns.each(function(col, idx) {
-                if (col.header) {
+                if (col.options.name != hf && 
+                    col.isResizable() && 
+                    col.domObj) {
                     var el = new Element('div', {
                         'class':'jxGridColumnResize',
                         title: this.options.tooltip,
                         events: {
                             dblclick: function() {
+                                col.options.renderMode = 'fixed';
                                 col.options.width = 'auto';
                                 col.setWidth(col.getWidth(true));
                             }
                         }
-                    }).inject(col.header);
-                    this.els.push(el);
-                    this.drags.push(new Drag(el, {
+                    }).inject(col.domObj);
+                    el.store('col', col);
+                    this.els.column.push(el);
+                    this.drags.column.push(new Drag(el, {
                         limit: {y:[0,0]},
+                        snap: 2,
+                        onBeforeStart: function(el) {
+                          var l = el.getPosition(el.parentNode).x.toInt();
+                          el.setStyles({
+                            left: l,
+                            right: null
+                          });
+                          
+                        },
+                        onStart: function(el) {
+                          var l = el.getPosition(el.parentNode).x.toInt();
+                          el.setStyles({
+                            left: l,
+                            right: null
+                          });
+                        },
                         onDrag: function(el) {
+                            var col = el.retrieve('col');
+                            col.options.renderMode = 'fixed';
                             var w = el.getPosition(el.parentNode).x.toInt();
                             col.setWidth(w);
+                        },
+                        onComplete: function(el) {
+                          el.setStyle('left', null);
                         }
                     }));
                 }
             }, this);
         }
 
-        // if (this.options.rows && this.grid.row.useHeaders()) {
-        //
-        // }
+        //if (this.options.row && this.grid.row.useHeaders()) {}
+    },
+    /**
+     * Method: createText
+     * respond to a language change by updating the tooltip
+     */
+    createText: function (lang) {
+      this.parent();
+      var txt = MooTools.lang.get('Jx','plugin.resize').tooltip;
+      ['column','row'].each(function(option) {
+        this.els[option].each(function(el) { el.set('title',txt); } );
+      }, this);
     }
 });
+// $Id: grid.editor.js 822 2010-03-31 11:28:31Z conrad.barthelmes $
 /**
+ * Class: Jx.Plugin.Editor
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to enable inline editing within a cell
+ *
+ * Original selection code from Jx.Grid's original class
+ *
+ * License:
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Conrad Barthelmes.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Editor = new Class({
+
+    Extends : Jx.Plugin,
+    Binds: ['activate','deactivate','changeText'],
+
+    options : {
+      /**
+       * Option: enabled
+       * Determines if inline editing is avaiable
+       */
+      enabled : true,
+      /**
+       * Option: blurDelay
+       * Set the time in miliseconds when the inputfield/popup shall hide. When
+       * the user refocuses the input/popup within this time, the timeout will be cleared
+       *
+       * set to 'false' if no hiding on blur is wanted
+       */
+      blurDelay : 500,
+      /**
+       * Option: popup
+       *
+       * Definitions for a PopUp to use.
+       * - use        - determines whether to use a PopUp or simply the input
+       * - useLabel   - determines whether to use labels on top of the input.
+       *                Text will be the column header
+       * - useButtons - determines whether to use Submit and Cancel Buttons
+       * - buttonLabel.submit - Text for Submit Button, uses MooTools.lang.get('Jx', 'plugin.editor').submitButton for default
+       * - buttonLabel.cancel - Text for Cancel Button, uses MooTools.lang.get('Jx', 'plugin.editor').cancelButton for default
+       */
+      popup : {
+        use           : true,
+        useLabels     : false,
+        useButtons    : true,
+        button        : {
+          submit : {
+            label : '',
+            image : 'images/accept.png'
+          },
+          cancel : {
+            label : '',
+            image : 'images/cancel.png'
+          }
+        },
+        template: '<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'
+      },
+      /**
+       * Option {boolean} validate
+       * - set to true to have all editable input fields as mandatory field
+       *   if they don't have 'mandatory:true' in their colOptions
+       */
+      validate : true,
+      /**
+       * Option: {Array} fieldOptions with objects
+       * Contains objects with options for the Jx.Field instances to show up.
+       * Default options will be added automatically if custom options are entered.
+       *
+       * Preferences:
+       *   field             - Default * for all types or the name of the column in the model (Jx.Store)
+       *   type              - Input type to show (Text, Password, Textarea, Select, Checkbox)
+       *   options           - All Jx.Field options for this column. More options depend on what type you are using.
+       *                       See Jx.Form.[yourField] for details
+       *   validatorOptions: - See Jx.Plugin.Field.Validator Options for details
+       *                       will only be used if this.options.validate is set to true
+       */
+      fieldOptions : [
+        {
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        }
+      ],
+      /**
+       * Option: {Boolean} fieldFormatted
+       * Displays the cell value also inside the input field as formatted
+       */
+      fieldFormatted : true,
+      /**
+       * Option cellChangeFx
+       * set use to false if no highlighting effect is wanted.
+       *
+       * this is just an idea how successfully changing could be highlighed for the user
+       */
+      cellChangeFx : {
+        use     : true,
+        success : '#090',
+        error   : '#F00'
+      },
+      /**
+       * Option cellOutline
+       * shows an outline style to the currently active cell to make it easier to see
+       * which cell is active
+       */
+      cellOutline : {
+        use   : true,
+        style : '2px solid #88c3e7'
+      },
+      /**
+       * Option: useKeyboard
+       * Set to false if no keyboard support is needed
+       */
+      useKeyboard : true,
+      /**
+       * Option: keys
+       * Contains the event codes for several commands that can be used when
+       * a field is active. Syntax is the same like for the Mootools Keyboard Class
+       * http://mootools.net/docs/more/Interface/Keyboard
+       */
+      keys : {
+        'ctrl+shift+enter' : 'saveNGoUp',
+        'tab'              : 'saveNGoRight',
+        'ctrl+enter'       : 'saveNGoDown',
+        'shift+tab'        : 'saveNGoLeft',
+        'enter'            : 'saveNClose',
+        'ctrl+up'          : 'cancelNGoUp',
+        'ctrl+right'       : 'cancelNGoRight',
+        'ctrl+down'        : 'cancelNGoDown',
+        'ctrl+left'        : 'cancelNGoLeft',
+        'esc'              : 'cancelNClose',
+        'up'               : 'valueIncrement',
+        'down'             : 'valueDecrement'
+      },
+      /**
+       * Option: keyboardMethods
+       *
+       * can be used to overwrite existing keyboard methods that are used inside
+       * this.options.keys - also possible to add new ones.
+       * Functions are bound to the editor plugin when using 'this'
+       *
+       * example:
+       *  keys : {
+       *    'ctrl+u' : 'cancelNGoRightNDown'
+       *  },
+       *  keyboardMethods: {
+       *    'cancelNGoRightNDown' : function(ev){
+       *      ev.preventDefault();
+       *      this.getNextCellInRow(false);
+       *      this.getNextCellInCol(false);
+       *    }
+       *  }
+       */
+      keyboardMethods : {},
+      /**
+       * Option: linkClickListener
+       * disables all click events on links that are formatted with Jx.Formatter.Uri
+       * - otherwise the link will open directly instead of open the input editor)
+       * - hold [ctrl] to open the link in a new tab
+       */
+      linkClickListener : true
+    },
+    classes: ['jxGridEditorPopup', 'jxGridEditorPopupInnerWrapper'],
+    /**
+     * Property: activeCell
+     *
+     * Containing Objects:
+     *   field        : Reference to the Jx.Field instance that will be created
+     *   cell         : Reference to the cell inside the table 
+     *   span         : Reference to the Dom Element inside the selected cell of the grid
+     *   oldValue     : Old value of the cell from the grid's model
+     *   newValue     : Object with <data> and <error> for better validation possibilites
+     *   timeoutId    : TimeoutId if the focus blurs the input.
+     *   coords       : Coordinates of the selected cell
+     *   colOptions   : Reference to the column's option in which the cell is
+     *   fieldOptions : Reference to the field options of this column
+     */
+    activeCell : {
+      field       : null,
+      cell        : null,
+      span        : null,
+      oldValue    : null,
+      newValue    : { data: null, error: false },
+      timeoutId   : null,
+      coords      : {},
+      colOptions  : {},
+      fieldOptions: {}
+    },
+    /**
+     * Property : popup
+     *
+     * References to all contents within a popup (only 1 popup for 1 grid initialization)
+     *
+     * COMMENT: I don't know how deep we need to go into that.. innerWrapper and closeLink probably don't need
+     * own references.. I just made them here in case they are needed at some time..
+     *
+     * Containing Objects:
+     *   domObj         : Reference to the Dom Element of the popup (absolutely positioned)
+     *   innerWrapper   : Reference to the inner Wrapper inside the popup to provide relative positioning
+     *   closeIcon      : Reference to the Dom Element of a little [x] in the upper right to close it (not saving)
+     *   buttons        : References to all Jx.Buttons used inside the popup
+     *   buttons.submit : Reference to the Submit Button
+     *   buttons.cancel : Reference to the Cancel Button
+     */
+    popup : {
+      domObj       : null,
+      innerWarpper : null,
+      closeIcon    : null,
+      button       : {
+        submit : null,
+        cancel : null
+      }
+    },
+    /**
+     * Property: keyboard
+     * Instance of a Mootols Keyboard Class
+     */
+    keyboard : null,
+    /**
+     * Property keyboardMethods
+     * Editing and grid functions for keyboard functionality.
+     * Methods are defined and implemented inside this.attach() because of referencing troubles
+     */
+    keyboardMethods : {},
+    /**
+     * APIMethod: init
+     * construct a new instance of the plugin.  The plugin must be attached
+     * to a Jx.Grid instance to be useful though.
+     */
+    init: function() {
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * @var {Object} grid - Instance of Class Jx.Grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+        return;
+      }
+      this.grid = grid;
+
+      this.grid.addEvent('gridCellSelect', this.activate);
+      this.grid.addEvent('gridCellUnSelect', this.deactivate);
+
+      /*
+       * add default field options to the options in case some new options were entered
+       * to be still able to use them for the rest of the fields
+       */
+      if(this.getFieldOptionsByColName('*').field != '*') {
+        this.options.fieldOptions.unshift({
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        });
+      }
+
+      /**
+       * set the keyboard methods here to have a correct reference to the instance of
+       * the editor plugin
+       *
+       * @todo other names maybe? or even completely different way of handling the keyboard events?
+       * @todo more documentation than method name
+       */
+      var self = this;
+      this.keyboardMethods = {
+        saveNClose     : function(ev) {
+          if(self.activeCell.fieldOptions.type != 'Textarea' || (self.activeCell.fieldOptions.type == 'Textarea' && ev.key != 'enter')) {
+            self.deactivate()
+          }
+        },
+        saveNGoUp      : function(ev) {ev.preventDefault();self.getPrevCellInCol()},
+        saveNGoRight   : function(ev) {ev.preventDefault();self.getNextCellInRow()},
+        saveNGoDown    : function(ev) {ev.preventDefault();self.getNextCellInCol()},
+        saveNGoLeft    : function(ev) {ev.preventDefault();self.getPrevCellInRow()},
+        cancelNClose   : function(ev) {ev.preventDefault();self.deactivate(false)},
+        cancelNGoUp    : function(ev) {ev.preventDefault();self.getPrevCellInCol(false)},
+        cancelNGoRight : function(ev) {ev.preventDefault();self.getNextCellInRow(false)},
+        cancelNGoDown  : function(ev) {ev.preventDefault();self.getNextCellInCol(false)},
+        cancelNGoLeft  : function(ev) {ev.preventDefault();self.getPrevCellInRow(false)},
+        valueIncrement : function(ev) {ev.preventDefault();self.cellValueIncrement(true)},
+        valueDecrement : function(ev) {ev.preventDefault();self.cellValueIncrement(false)}
+      };
+      
+      var keyboardEvents = {};
+      for(var i in this.options.keys) {
+        if($defined(this.keyboardMethods[this.options.keys[i]])) {
+          keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+        }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+          keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+        }else{
+          $defined(console) ? console.log("keyboard method %o not defined", this.options.keys[i]) : false;
+        }
+      }
+
+      // initalize keyboard support but do NOT activate it (this is done inside this.activate()).
+      this.keyboard = new Keyboard({
+        events: keyboardEvents
+      });
+
+      this.addFormatterUriClickListener();
+    },
+    /**
+     * APIMethod: detach
+     * detaches from the grid
+     * 
+     * @return void
+     */
+    detach: function() {
+      if (this.grid) {
+        this.grid.removeEvent('gridClick', this.activate);
+      }
+      this.grid = null;
+      this.keyboard = null;
+    },
+    /**
+     * APIMethod: enable
+     * enables the grid 'externally'
+     *
+     * @return void
+     */
+    enable : function () {
+      this.options.enabled = true;
+    },
+    /**
+     * APIMethod: disable
+     * disables the grid 'externally'
+     *
+     * @var Boolean close - default true: also closes the currently open input/popup
+     * @var Boolean save - default false: also changes the currently open input/popup
+     * @return void
+     */
+    disable : function(close, save) {
+      close = $defined(close) ? close : true;
+      save = $defined(save) ? save : false;
+      if(close && this.activeCell.cell != null) {
+        this.deactivate(save);
+      }
+      this.options.enabled = false;
+    },
+    /**
+     * Method: activate
+     * activates the input field or breaks up if conditions are not fulfilled
+     *
+     * @todo Field validation
+     *
+     * Parameters:
+     * @var {Object} cell Table Element
+     * @return void
+     */
+    activate: function(cell) {
+      if(!this.options.enabled)
+        return;
+
+      var data  = cell.retrieve('jxCellData');
+      // @todo Rename Header too??
+      // return if a table header was clicked
+      if(($defined(data.colHeader) && data.colHeader) || ($defined(data.rowHeader) && data.rowHeader))
+        return;
+      var row   = data.row,
+          index = data.index;
+
+      clearTimeout(this.activeCell.timeoutId);
+
+      if(this.cellIsInGrid(row, index)) {
+
+        var colIndex   = this.grid.options.row.useHeaders ? index-1 : index;
+        var model      = this.grid.getModel(),
+            //cell       = this.grid.gridTableBody.rows[row].cells[col] ? this.grid.gridTableBody.rows[row].cells[col] : null,
+            colOptions = this.grid.columns.getByGridIndex(colIndex).options;
+        if (!cell || !colOptions.isEditable) {
+          return;
+        }
+        // if disabling a currently active one fails (mandatory for example) do not continue
+        if(this.activeCell.cell != null && this.deactivate() == false) {
+          return;
+        }
+
+        // set active record index to selected row
+        model.moveTo(row);
+        
+        // store properties of the active cell
+        this.activeCell = {
+          oldValue      : model.get(data.index),
+          newValue      : {data: null, error: false},
+          fieldOptions  : this.getFieldOptionsByColName(colOptions.name),
+          colOptions    : colOptions,
+          coords        : {row : row, index : index},
+          cell          : cell,
+          span          : cell.getElement('span.jxGridCellContent'),
+          validator     : null,
+          field         : null,
+          timeoutId     : null
+        }
+
+        // check if this column has special validation settings - otherwise use default from this.options.validate
+        if(!$defined(this.activeCell.colOptions.validate) || typeof(this.activeCell.colOptions.validate) != 'boolean') {
+          this.activeCell.colOptions.validate = this.options.validate;
+        }
+
+        var jxFieldOptions = $defined(this.activeCell.fieldOptions.options) ? this.activeCell.fieldOptions.options : {}
+
+        // check for different input field types
+        switch(this.activeCell.fieldOptions.type) {
+          case 'Text':
+          case 'Color':
+          case 'Password':
+          case 'File':
+            jxFieldOptions.value = this.activeCell.oldValue;
+            break;
+          case 'Textarea':
+            jxFieldOptions.value = this.activeCell.oldValue.replace(/<br \/>/gi, '\n');
+            break;
+          case 'Select':
+            // find out which visible value fits to the value inside <option>{value}</option> and set it to selected
+            var oldValue  = this.activeCell.oldValue.toString()
+            function setCombos(opts, oldValue) {
+              for(var i = 0, j = opts.length; i < j; i++) {
+                if(opts[i].value == oldValue) {
+                  opts[i].selected = true;
+                }else{
+                  opts[i].selected = false;
+                }
+              }
+              return opts;
+            }
+
+            if(jxFieldOptions.comboOpts) {
+              jxFieldOptions.comboOpts = setCombos(jxFieldOptions.comboOpts, oldValue);
+            }else if(jxFieldOptions.optGroups) {
+              var groups = jxFieldOptions.optGroups;
+              for(var k = 0, n = groups.length; k < n; k++) {
+                groups[k].options = setCombos(groups[k].options, oldValue);
+              }
+              jxFieldOptions.optGroups = groups;
+            }
+            break;
+          case 'Radio':
+          case 'Checkbox':
+          default:
+            $defined(console) ? console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.", this.activeCell.fieldOptions.type) : false;
+            return;
+            break;
+        }
+
+        // update the 'oldValue' to the formatted style, to compare the new value with the formatted one instead with the non-formatted-one
+        if(this.options.fieldFormatted && this.activeCell.colOptions.renderer.options.formatter != null) {
+          if(!$defined(this.activeCell.colOptions.fieldFormatted) || this.activeCell.colOptions.fieldFormatted == true ) {
+            jxFieldOptions.value = this.activeCell.colOptions.renderer.options.formatter.format(jxFieldOptions.value);
+            this.activeCell.oldValue = jxFieldOptions.value;
+          }
+        }
+
+        // create jx.field
+        this.activeCell.field = new Jx.Field[this.activeCell.fieldOptions.type.capitalize()](jxFieldOptions);
+        // create validator
+        if(this.options.validate && this.activeCell.colOptions.validate) {
+          this.activeCell.validator = new Jx.Plugin.Field.Validator(this.activeCell.fieldOptions.validatorOptions);
+          this.activeCell.validator.attach(this.activeCell.field);
+        }
+        this.setStyles(cell);
+
+        if(this.options.useKeyboard) {
+          this.keyboard.activate();
+        }
+
+        // convert a string to an integer if somebody entered a numeric value in quotes, if it failes: make false
+        if(typeof(this.options.blurDelay) == 'string') {
+          this.options.blurDelay = this.options.blurDelay.toInt() ? this.options.blurDelay.toInt() : false;
+        }
+
+        // add a onblur() and onfocus() event to the input field if enabled.
+        if(this.options.blurDelay !== false && typeof(this.options.blurDelay) == 'number') {
+          var self = this;
+          this.activeCell.field.field.addEvents({
+            // activate the timeout to close the input/poup
+            'blur' : function() {
+              // @todo For some reason, webkit does not clear the timeout correctly when navigating through the grid with keyboard
+              clearTimeout(self.activeCell.timeoutId);
+              self.activeCell.timeoutId = self.deactivate.delay(self.options.blurDelay);
+            },
+            // clear the timeout when the user focusses again
+            'focus' : function() {
+              clearTimeout(self.activeCell.timeoutId);
+            }, 
+            // clear the timeout when the user puts the mouse over the input
+            'mouseover' : function() {
+              clearTimeout(self.activeCell.timeoutId);
+            }
+          });
+          if(this.popup.domObj != null) {
+            this.popup.domObj.addEvent('mouseenter', function() {
+              clearTimeout(self.activeCell.timeoutId);
+            });
+          }
+        }
+
+        this.activeCell.field.field.focus();
+      }else{
+        if($defined(console)) {console.warn('out of grid %o',cell)}
+      }
+    }, 
+    /**
+     * APIMethod: deactivate
+     * hides the currently active field and stores the new entered data if the
+     * value has changed
+     *
+     * Parameters:
+     * @var {Boolean} save (Optional, default: true) - force aborting
+     * @return true if no data error occured, false if error (popup/input stays visible)
+     */
+    deactivate: function(save) {
+
+      clearTimeout(this.activeCell.timeoutId);
+      
+      if(this.activeCell.field !== null) {
+        save = $defined(save) ? save : true;
+
+        var newValue = {data : null, error : false};
+
+        // update the value in the model
+        if(save && this.activeCell.field.getValue().toString() != this.activeCell.oldValue.toString()) {
+          this.grid.model.moveTo(this.activeCell.coords.row);
+          /*
+           * @todo webkit shrinks the rows when the value is updated... but refreshing the grid
+           *       immidiately returns in a wrong calculating of the cell position (getCoordinates)
+           */
+          switch(this.activeCell.fieldOptions.type) {
+            case 'Select':
+              var index = this.activeCell.field.field.selectedIndex;
+              newValue.data = document.id(this.activeCell.field.field.options[index]).get('value');
+              break;
+            case 'Textarea':
+              newValue.data = this.activeCell.field.getValue().replace(/\n/gi, '<br />');
+              break;
+            default:
+              newValue.data = this.activeCell.field.getValue();
+              break;
+          }
+          if(save) {
+            this.activeCell.newValue.data = newValue.data;
+            // manually blur the field to activate the validator -> continues with this.terminate()
+            //this.activeCell.timeoutId = this.activeCell.field.field.blur.delay(50, this.activeCell.field.field);
+          }
+          // validation only if it should be saved!
+          if(this.activeCell.validator != null && !this.activeCell.validator.isValid()) {
+            newValue.error = true;
+            this.activeCell.field.field.focus.delay(50, this.activeCell.field.field);
+          }
+        }else{
+          this.activeCell.span.show();
+        }
+
+
+        if(save && newValue.data != null && newValue.error == false) {
+          this.grid.model.set(this.activeCell.coords.index, newValue.data);
+          this.addFormatterUriClickListener();
+        // else show error message and cell
+        }else if(newValue.error == true) {
+          this.activeCell.span.show();
+        }
+
+        // update reference to activeCell
+        if($defined(this.activeCell.coords.row) && $defined(this.activeCell.coords.index)) {
+          var colIndex = this.grid.options.row.useHeaders ? this.activeCell.coords.index-1 : this.activeCell.coords.index;
+          this.activeCell.cell = this.grid.gridTableBody.rows[this.activeCell.coords.row].cells[colIndex];
+        }
+
+        if(this.options.useKeyboard) {
+          this.activeCell.field.removeEvent('keypress', this.setKeyboard);
+        }
+
+        /**
+         * COMMENT: this is just an idea how changing a value could be visualized
+         * we could also pass an Fx.Tween element?
+         * the row could probably be highlighted as well?
+         */
+        if(this.options.cellChangeFx.use) {
+          if(newValue.data != null && newValue.error == false) {
+            this.activeCell.cell.highlight(this.options.cellChangeFx.success);
+          }else if(newValue.error){
+            this.activeCell.cell.highlight(this.options.cellChangeFx.error);
+          }
+          //this.activeCell.cell.removeProperty('style').delay(250, this.activeCell.cell);
+        }
+
+        // check for error and keep input field alive
+        if(newValue.error) {
+          if(this.options.cellChangeFx.use) {
+            this.activeCell.field.field.highlight(this.options.cellChangeFx.error);
+          }
+          this.activeCell.field.field.setStyle('border','1px solid '+this.options.cellChangeFx.error);
+          this.activeCell.field.field.focus();
+          return false;
+        // otherwise hide it
+        }else{
+          this.keyboard.deactivate();
+          this.unsetActiveField();
+          return true;
+        }
+      }
+    },
+    /**
+     * Method: setStyles
+     * 
+     * sets some styles for the Jx.Field elements...
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     * @return void
+     */
+    setStyles : function(cell) {
+      // popup
+      if(this.options.popup.use) {
+        if(this.options.popup.useLabels) {
+          this.activeCell.field.options.label = this.activeCell.colOptions.header;
+          this.activeCell.field.render();
+        }
+        var styles = {
+          field : {
+            'width'  : this.activeCell.field.type == 'Select' ?
+                         cell.getContentBoxSize().width + 5 + "px" :
+                         cell.getContentBoxSize().width - 14 + "px",
+            'margin' : 'auto 0'
+          }
+        };
+        this.activeCell.field.field.setStyles(styles.field);
+        this.showPopUp(cell);
+      // No popup
+      }else {
+        var size   = cell.getContentBoxSize(),
+            styles = {
+              domObj : {
+                position: 'absolute'
+              },
+              field : {
+                width : size.width + "px",
+                'margin-left' : 0
+              }
+            };
+
+        this.activeCell.field.domObj.setStyles(styles.domObj);
+        this.activeCell.field.field.setStyles(styles.field);
+       
+        this.activeCell.field.domObj.inject(document.body);
+        Jx.Widget.prototype.position(this.activeCell.field.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+
+        this.activeCell.span.hide();
+      }
+
+      // COMMENT: an outline of the cell helps identifying the currently active cell
+      if(this.options.cellOutline.use) {
+        cell.setStyle('outline', this.options.cellOutline.style);
+      }
+    },
+    /**
+     * Method: showPopUp
+     *
+     * Shows the PopUp of of the editor if it already exists, otherwise calls Method
+     * this.createPopUp
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    showPopUp : function(cell) {
+      if(this.popup.domObj != null) {
+        Jx.Widget.prototype.position(this.popup.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+        this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+        this.popup.domObj.show();
+        this.setPopUpButtons();
+        this.setPopUpStylesAfterRendering();
+      }else{
+        this.createPopUp(cell);
+      }
+    },
+    /**
+     * Method: createPopUp
+     *
+     * creates the popup for the requested cell.
+     *
+     * COMMENT: this could also be an jx.dialog..? if we use jx.dialog, maybe without a title element?
+     *          Maybe a jx.dialog is too much for this little thing?
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    createPopUp : function(cell) {
+      var coords = cell.getCoordinates(),
+          self      = this, popup  = null, innerWrapper = null,
+          closeIcon = null, submit = null, cancel       = null,
+          template  = Jx.Widget.prototype.processTemplate(this.options.popup.template, this.classes);
+
+      popup = template.jxGridEditorPopup;
+      
+      innerWrapper = template.jxGridEditorPopupInnerWrapper;
+      /**
+       * COMMENT: first positioning is always in the top left of the grid..
+       * don't know why
+       * manual positioning is needed..?
+       */
+      popup.setStyles({
+        'left' : coords.left+'px',
+        'top'  : coords.top +'px'
+      });
+      /*
+      Jx.Widget.prototype.position(popup, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+      });
+      */
+
+      this.popup.domObj         = popup;
+      this.popup.innerWrapper   = innerWrapper;
+      this.popup.closeIcon      = closeIcon;
+      this.setPopUpButtons();
+
+      this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+      this.popup.domObj.inject(document.body);
+
+      this.setPopUpStylesAfterRendering();
+    },
+    /**
+     * Method: setPopUpStylesAfterRendering
+     *
+     * - measures the widths of the buttons to set a new min-width for the popup
+     *   because custom labels could break the min-width and force a line-break
+     * - resets the size of the field to make it fit inside the popup (looks nicer)
+     *
+     * @return void
+     */
+    setPopUpStylesAfterRendering: function() {
+      if(this.options.popup.useButtons && this.popup.button.submit != null && this.popup.button.cancel != null) {
+        this.popup.domObj.setStyle('min-width', this.popup.button.submit.domObj.getSize().x + this.popup.button.cancel.domObj.getSize().x + "px");
+      }else{
+        if(this.popup.button.submit != null)
+          this.popup.button.submit.domObj.hide();
+        if(this.popup.button.cancel != null)
+          this.popup.button.cancel.domObj.hide();
+      }
+      this.activeCell.field.field.setStyle('width',
+        this.activeCell.field.type == 'Select' ?
+          this.popup.domObj.getSize().x - 7 + "px" :
+          this.popup.domObj.getSize().x - 17 + "px");
+    },
+    /**
+     * Method: setPopUpButtons
+     * creates the PopUp Buttons if enabled in options or deletes them if set to false
+     *
+     * @return void
+     */
+    setPopUpButtons : function() {
+      var self = this,
+          button = {
+            submit : null,
+            cancel : null
+          };
+      // check if buttons are needed, innerWrapper exists and no buttons already exist
+      if(this.options.popup.useButtons && this.popup.innerWrapper != null && this.popup.button.submit == null) {
+        button.submit = new Jx.Button({
+          label : this.options.popup.button.submit.label.length == 0 ? MooTools.lang.get('Jx','plugin.editor').submitButton : this.options.popup.button.submit.label,
+          image : this.options.popup.button.submit.image,
+          onClick: function() {
+            self.deactivate(true);
+          }
+        }).addTo(this.popup.innerWrapper);
+        button.cancel = new Jx.Button({
+          label : this.options.popup.button.cancel.label.length == 0 ? MooTools.lang.get('Jx','plugin.editor').cancelButton : this.options.popup.button.cancel.label,
+          image : this.options.popup.button.cancel.image,
+          onClick: function() {
+            self.deactivate(false);
+          }
+        }).addTo(this.popup.innerWrapper);
+      }else if(this.options.popup.useButtons && this.popup.button.submit != null) {
+        button = {
+          submit : this.popup.button.submit,
+          cancel : this.popup.button.cancel
+        };
+      // check if buttons are not needed and buttons already exist to remove them
+      }else if(this.options.popup.useButtons == false && this.popup.button.submit != null) {
+        this.popup.button.submit.cleanup();
+        this.popup.button.cancel.cleanup();
+      }
+
+      this.popup.button = button;
+    },
+    /**
+     * Method: unsetActiveField
+     * resets the activeField and hides the popup
+     *
+     * @return void
+     */
+    unsetActiveField: function() {
+      this.activeCell.field.destroy();
+      if(this.popup.domObj != null) {
+        this.popup.domObj.removeEvent('mouseenter');
+        this.popup.domObj.hide();
+      }
+
+      this.activeCell.cell.setStyle('outline', '0px');
+
+      this.activeCell = {
+        field         : null,
+        oldValue      : null,
+        newValue      : { data: null, error: false},
+        cell          : null,
+        span          : null,
+        timeoutId     : null,
+        //popup         : null,   // do not destroy the popup, it might be used again
+        colOptions    : {},
+        coords        : {},
+        fieldOptions  : {},
+        validator     : null
+      }
+    },
+    /**
+     * Method: unsetPopUp
+     * resets the popup manually to be able to use it with different settings
+     */
+    unsetPopUp : function() {
+      if(this.popup.domObj != null) {
+        this.popup.domObj.destroy();
+        this.popup.innerWrapper   = null;
+        this.popup.closeIcon      = null;
+        this.popup.button.submit = null;
+        this.popup.button.cancel = null;
+      }
+    },
+    /**
+     * APIMethod: getNextCellInRow
+     * activates the next cell in a row if it is editable
+     * otherwise the focus jumps to the next editable cell in the next row
+     * or starts at the beginning
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var nextCell = true, nextRow = true,
+            sumCols = this.grid.columns.columns.length,
+            jxCellClass = 'td.jxGridCell';
+        var i = 0;
+        do {
+          nextCell = i > 0 ? nextCell.getNext(jxCellClass) : this.activeCell.cell.getNext(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(nextCell == null) {
+            nextRow  = this.activeCell.cell.getParent('tr').getNext();
+            // check if this was the last row in the table
+            if(nextRow == null) {
+              nextRow = this.activeCell.cell.getParent('tbody').getFirst();
+            }
+            nextCell = nextRow.getFirst(jxCellClass);
+          }
+          var data  = nextCell.retrieve('jxCellData');
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(!data.col.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInRow
+     * activates the previous cell in a row if it is editable
+     * otherwise the focus jumps to the previous editable cell in the previous row
+     * or starts at the last cell in the last row at the end
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var prevCell, prevRow, i = 0,
+            sumCols = this.grid.columns.columns.length,
+            jxCellClass = 'td.jxGridCell';
+        do {
+          prevCell = i > 0 ? prevCell.getPrevious(jxCellClass) : this.activeCell.cell.getPrevious(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(prevCell == null) {
+            prevRow  = this.activeCell.cell.getParent('tr').getPrevious();
+            // check if this was the last row in the table
+            if(prevRow == null) {
+              // @todo this does not always work when shift+tab is hold pressed (out of grid error)
+              prevRow = this.activeCell.cell.getParent('tbody').getLast();
+            }
+            prevCell = prevRow.getLast(jxCellClass);
+          }
+          var data  = prevCell.retrieve('jxCellData'),
+              row   = data.row,
+              index = data.index;
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(!data.col.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(prevCell);
+      }
+    },
+    /**
+     * APIMethod: getNextCellInCol
+     * activates the next cell in a column under the currently active one
+     * if the active cell is in the last row, the first one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInCol : function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var nextRow, nextCell;
+        nextRow = this.activeCell.cell.getParent().getNext();
+        if(nextRow == null) {
+          nextRow = this.activeCell.cell.getParent('tbody').getFirst();
+        }
+        nextCell = nextRow.getElement('td.jxGridCol'+this.activeCell.coords.index);
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInCol
+     * activates the previous cell in a column above the currently active one
+     * if the active cell is in the first row, the last one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInCol : function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var prevRow, prevCell;
+        prevRow = this.activeCell.cell.getParent().getPrevious();
+        if(prevRow == null) {
+          prevRow = this.activeCell.cell.getParent('tbody').getLast();
+        }
+        prevCell = prevRow.getElement('td.jxGridCol'+this.activeCell.coords.index);
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(prevCell);
+      }
+    },
+    /**
+     * Method: cellValueIncrement
+     * Whether increments or decrements the value of the active cell if the dataType is numeric
+     *
+     * Parameters
+     * @var {Boolean} bool
+     * @return void
+     */
+    cellValueIncrement : function(bool) {
+      var dataType = this.activeCell.colOptions.dataType,
+          valueNew = null;
+      switch(dataType) {
+        case 'numeric':
+        case 'currency':
+          valueNew = this.activeCell.field.getValue().toInt();
+          if(typeof(valueNew) == 'number') {
+            if(bool) {
+              valueNew++;
+            }else{
+              valueNew--;
+            }
+          }
+          break;
+        case 'date':
+          valueNew = Date.parse(this.activeCell.field.getValue());
+          if(valueNew instanceof Date) {
+            if(bool) {
+              valueNew.increment();
+            }else{
+              valueNew.decrement();
+            }
+            var formatter = new Jx.Formatter.Date();
+            valueNew = formatter.format(valueNew);
+          }
+          break;
+      }
+      if(valueNew != null) {
+        this.activeCell.field.setValue(valueNew);
+      }
+    },
+    /**
+     * Method: cellIsInGrid
+     * determins if the given coordinates are within the grid
+     *
+     * Parameters:
+     * @var {Integer} row
+     * @var {Integer} index
+     * @return {Boolean}
+     */
+    cellIsInGrid: function(row, index) {
+      if($defined(row) && $defined(index)) {
+        //console.log("Row %i - max Rows: %i, Col %i - max Cols %i", row, this.grid.gridTableBody.rows.length, index, this.grid.gridTableBody.rows[row].cells.length);
+        if( row >= 0 && index >= 0 &&
+            row <= this.grid.gridTableBody.rows.length &&
+            index <= this.grid.gridTableBody.rows[row].cells.length
+        ) {
+          return true;
+        }else{
+          return false;
+        }
+      }else{
+        return false;
+      }
+    },
+    /**
+     * APIMethod: getFieldOptionsByColName
+     * checks for the name of a column inside the fieldOptions and returns
+     * the object if found, otherwise the default options for the field
+     *
+     * Parameters:
+     * @var {String} colName
+     * @return {Object} default field options
+     */
+    getFieldOptionsByColName : function(colName) {
+      var fo = this.options.fieldOptions,
+          r  = this.options.fieldOptions[0];
+      for(var i = 0, j = fo.length; i < j; i++) {
+        if(fo[i].field == colName) {
+          r = fo[i];
+          break;
+        }
+      }
+      return r;
+    },
+    /**
+     * Method: addFormatterUriClickListener
+     *
+     * looks up for Jx.Formatter.Uri columns to disable the link and open the
+     * inline editor instead. set option linkClickListener to false to disable this
+     *
+     */
+    addFormatterUriClickListener : function() {
+      if(this.options.linkClickListener) {
+        // prevent a link from beeing opened if the editor should appear and the uri formatter is activated
+        var uriCols = [], tableCols, anchor;
+        // find out which columns are using a Jx.Formatter.Uri
+        this.grid.columns.columns.each(function(col,i) {
+          if(col.options.renderer.options.formatter != null && col.options.renderer.options.formatter instanceof Jx.Formatter.Uri) {
+            uriCols.push(i);
+          }
+        });
+        // add an event to all anchors inside these columns
+        this.grid.gridTable.getElements('tr').each(function(tr,i) {
+          tableCols = tr.getElements('td.jxGridCell');
+          for(var j = 0, k = uriCols.length; j < k; j++) {
+            anchor = tableCols[uriCols[j]-1].getElement('a');
+            if(anchor) {
+              anchor.removeEvent('click');
+              anchor.addEvent('click', function(ev) {
+                // open link if ctrl was clicked
+                if(!ev.control) {
+                  ev.preventDefault();
+                }
+              });
+            }
+          }
+        });
+      }
+    },
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.options.popup.use && this.options.popup.useButtons) {
+        if(this.popup.button.submit != null) {
+          this.popup.button.submit.cleanup();
+          this.popup.button.cancel.cleanup();
+          this.popup.button.submit = null;
+          this.popup.button.cancel = null;
+          this.setPopUpButtons();
+        }
+    	}
+    }
+}); 
+/**
  * Namespace: Jx.Plugin.DataView
  * The namespace for all dataview plugins
  */
-Jx.Plugin.DataView = {};
+Jx.Plugin.DataView = {};// $Id: slide.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Slide
  * Hides and shows an element without depending on a fixed width or height
@@ -28697,7 +33135,7 @@
 Jx.Slide = new Class({
     Family: 'Jx.Slide',
     Implements: Jx.Object,
-
+    Binds: ['handleClick'],
     options: {
         /**
          * Option: target
@@ -28731,25 +33169,25 @@
         onSlideIn: $empty
     },
     /**
-     * APIMethod: init
+     * Method: init
      * sets up the slide
      */
     init: function () {
 
-        this.target = $(this.options.target);
+        this.target = document.id(this.options.target);
 
         this.target.set('tween', {onComplete: this.setDisplay.bind(this)});
 
         if ($defined(this.options.trigger)) {
-            this.trigger = $(this.options.trigger);
-            this.trigger.addEvent('click', this.handleClick.bindWithEvent(this));
+            this.trigger = document.id(this.options.trigger);
+            this.trigger.addEvent('click', this.handleClick);
         }
 
         this.target.store('slider', this);
 
     },
     /**
-     * APIMethod: handleClick
+     * Method: handleClick
      * event handler for clicks on the trigger. Starts the slide process
      */
     handleClick: function () {
@@ -28790,10 +33228,10 @@
         if (dir === 'in') {
             h = this.target.retrieve(this.options.type);
             this.target.setStyles({
-                'overflow': 'hidden',
-                'display': 'block'
+                overflow: 'hidden',
+                display: 'block'
             });
-            this.target.setStyle(this.options.type, 0);
+            this.target.setStyles(this.options.type, 0);
             this.target.tween(this.options.type, h);
         } else {
             if (this.options.type === 'height') {
@@ -28911,7 +33349,7 @@
         header.addClass(this.options.headerClass + '-closed');
     }
 });
-// $Id: $
+// $Id: plugin.field.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Plugin.Field
  * Field plugin namespace
@@ -28922,7 +33360,7 @@
  *
  * This file is licensed under an MIT style license
  */
-Jx.Plugin.Field = {};// $Id: $
+Jx.Plugin.Field = {};// $Id: field.validator.js 754 2010-03-14 20:13:04Z conrad.barthelmes $
 /**
  * Class: Jx.Plugin.Field.Validator
  *
@@ -28992,6 +33430,7 @@
      * storage for bound methods useful for working with events
      */
     bound: {},
+    validators : new Hash(),
     /**
      * APIMethod: init
      * construct a new instance of the plugin.  The plugin must be attached
@@ -29022,7 +33461,7 @@
             if (t === 'string') {
                 this.field.field.addClass(v);
             } else if (t === 'object') {
-                this.validators.add(v.validator.name, new InputValidator(v.validator.name, v.validator.options));
+                this.validators.set(v.validator.name, new InputValidator(v.validator.name, v.validator.options));
                 this.field.field.addClass(v.validatorClass);
             }
         }, this);
@@ -29094,7 +33533,7 @@
 
 
 });
-// $Id: $
+// $Id: plugin.form.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Plugin.Form
  * Form plugin namespace
@@ -29105,7 +33544,7 @@
  *
  * This file is licensed under an MIT style license
  */
-Jx.Plugin.Form = {};// $Id: $
+Jx.Plugin.Form = {};// $Id: form.validator.js 686 2010-02-01 05:45:28Z jonlb at comcast.net $
 /**
  * Class: Jx.Plugin.Form.Validator
  *
@@ -29196,12 +33635,12 @@
         //setup the fields
         $H(this.options.fields).each(function (val, key) {
             var opts = $merge(this.options.fieldDefaults, val);
-            var field = document.id(key).retrieve('field');
-            var plugin = new Jx.Plugin.Field.Validator(opts);
-            this.plugins.set(key, plugin);
-            plugin.attach(field);
-            plugin.addEvent('fieldValidationFailed', this.bound.failed);
-            plugin.addEvent('fieldValidationPassed', this.bound.passed);
+            var field = this.form.getField(key);
+            var p = new Jx.Plugin.Field.Validator(opts);
+            this.plugins.set(key, p);
+            p.attach(field);
+            p.addEvent('fieldValidationFailed', this.bound.failed);
+            p.addEvent('fieldValidationPassed', this.bound.passed);
 
         }, this);
 
@@ -29255,7 +33694,7 @@
         this.fireEvent('fieldValidationFailed', [field, validator]);
     },
     /**
-     * Method: fielPassed
+     * Method: fieldPassed
      * Refires the fieldValidationPassed event from the field validators it contains
      */
     fieldPassed: function (field, validator) {
@@ -29274,7 +33713,450 @@
 
 
 });
-// $Id: context.js 626 2009-11-20 13:22:22Z pagameba $
+/**
+ * Class: Jx.Plugin.Toolbar
+ * Toolbar plugin namespace
+ *
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer = {};/**
+ * Class: Jx.Plugin.ToolbarContainer.TabMenu
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * This plugin provides a menu of tabs in a toolbar (similar to the button in firefox at the end of the row of tabs).
+ * It is designed to be used only when the toolbar contains tabs and only when the container is allowed to scroll. Also,
+ * this plugin must be added directly to the Toolbar container. You can get a reference to the container for a
+ * <Jx.TabBox> by doing
+ *
+ * (code)
+ * var tabbox = new Jx.TabBox();
+ * var toolbarContainer = document.id(tabBox.tabBar).getParent('.jxBarContainer').retrieve('jxBarContainer');
+ * (end)
+ *
+ * You can then use the attach method to connect the plugin. Otherwise, you can add it via any normal means to a
+ * directly instantiated Container.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+
+Jx.Plugin.ToolbarContainer.TabMenu = new Class({
+
+    Family: 'Jx.Plugin.ToolbarContainer.TabMenu',
+    Extends: Jx.Plugin,
+
+    Binds: ['addButton'],
+
+    options: {
+    },
+    /**
+     * Property: tabs
+     * holds all of the tabs that we're tracking
+     */
+    tabs: [],
+
+    init: function () {
+        this.parent();
+    },
+
+    attach: function (toolbarContainer) {
+        this.parent(toolbarContainer);
+
+        this.container = toolbarContainer;
+
+        //we will only be used if the container is allowed to scroll
+        if (!this.container.options.scroll) {
+            return;
+        }
+
+        this.menu = new Jx.Menu({},{
+            buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'
+        }).addTo(this.container.controls,'bottom');
+        document.id(this.menu).addClass('jxTabMenuRevealer');
+        this.container.update();
+
+        //go through all of the existing tabs and add them to the menu
+        //grab the toolbar...
+        var tb = document.id(this.container).getElement('ul').retrieve('jxToolbar');
+        tb.list.each(function(item){
+            this.addButton(item);
+        },this);
+
+        //connect to the add event of the toolbar list to monitor the addition of buttons
+        tb.list.addEvent('add',this.addButton);
+    },
+
+    detach: function () {
+        this.parent();
+    },
+
+    addButton: function (item) {
+        var tab;
+        tab = (item instanceof Jx.Button.Tab) ? item : document.id(item).getFirst().retrieve('jxTab');
+
+
+        var l = tab.getLabel();
+        if (!$defined(l)) {
+            l = '';
+        }
+        var mi = new Jx.Menu.Item({
+            label: tab.getLabel(),
+            image: tab.options.image,
+            onClick: function() {
+                tab.setActive(true);
+            }.bind(this)
+        });
+
+        document.id(tab).store('menuItem', mi);
+
+        tab.addEvent('close', function() {
+            this.menu.remove(mi);
+        }.bind(this));
+
+        this.menu.add([mi]);
+    }
+});
+/**
+ * Class: Jx.Adaptor
+ * Base class for all adaptor implementations. Provides a place to locate all
+ * common code and the Jx.Adaptor namespace.  Since it extends <Jx.Plugin> all
+ * adaptors will be able to be used as plugins for their respective classes.
+ * Also as such, they must have the attach() and detach() methods.
+ * 
+ * Adaptors are specifically used to conform a <Jx.Store> to any one of 
+ * the different widgets (i.e. Jx.Tree, Jx.ListView, etc...) that could
+ * benefit from integration with the store. This approach was taken to minimize 
+ * data access code in the widgets themselves. Widgets should have no idea where 
+ * the data/items come from so that they will be usable in the broadest number
+ * of situations.
+ */
+Jx.Adaptor = new Class({
+	
+	Family: 'Jx.Adaptor',
+	Extends: Jx.Plugin,
+	
+	name: 'Jx.Adaptor',
+
+	options: {
+	    template: '',
+	    useTemplate: true,
+	    store: null
+	},
+	
+	columnsNeeded: null,
+	
+	init: function () {
+	    this.parent();
+	    
+	    this.store = this.options.store;
+	    
+	    if (this.options.useTemplate) {
+	        this.columnsNeeded = this.store.parseTemplate(this.options.template);
+	    }
+	},
+	
+	attach: function (widget) {
+		this.parent(widget);
+		this.widget = widget;
+	},
+	
+	detach: function () {
+		this.parent();
+	}
+	
+});/**
+ * Class: Jx.Adaptor.Tree
+ * This base class is used to change a store (a flat list of records) into the
+ * data structure needed for a Jx.Tree. It will have 2 subclasses:
+ * <Jx.Adapter.Tree.Mptt> and <Jx.Adapter.Tree.Parent>
+ * 
+ *  
+ */
+Jx.Adaptor.Tree = new Class({
+    
+    Family: 'Jx.Adaptor.Tree',
+    Extends: Jx.Adaptor,
+    
+    Binds: ['fill','checkFolder'],
+    
+    options: {
+        /**
+         * Option: useAjax
+         * Determines if this adapter should use ajax to request data on the
+         * fly. 
+         */
+        monitorFolders: false,
+        startingNodeKey: -1,
+        folderOptions: {
+            image: null,
+            imageClass: null
+        },
+        itemOptions: {
+            image: null,
+            imageClass: null
+        }
+    },
+    
+    folders: new Hash(),
+    
+    currentRecord: -1,
+    
+    attach: function (tree) {
+        this.parent(tree);
+        
+        this.tree = tree;
+        
+        if (this.options.monitorFolders) {
+            this.strategy = this.store.getStrategy('progressive');
+        
+            if (!$defined(this.strategy)) {
+                this.strategy = new Jx.Store.Strategy.Progressive({
+                    dropRecords: false,
+                    getPaginationParams: function () { return {}; }
+                });
+                this.store.addStrategy(this.strategy);
+            } else {
+                this.strategy.options.dropRecords = false;
+                this.strategy.options.getPaginationParams = function () { return {}; };
+            }
+            
+        }
+        
+        this.store.addEvent('storeDataLoaded', this.fill);
+        
+        
+    },
+    
+    detach: function () {
+    	this.parent();
+    	this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+    
+    firstLoad: function () {
+    	//initial store load
+    	this.busy = 'tree';
+    	this.tree.setBusy(true);
+        this.store.load({
+            node: this.options.startingNodeKey
+        });
+    },
+    
+    /**
+     * APIMethod: fill
+     * This function will start at this.currentRecord and add the remaining
+     * items to the tree. 
+     */
+    fill: function () {
+    	if (this.busy == 'tree') {
+    		this.tree.setBusy(false);
+    		this.busy = 'none';
+    	} else if (this.busy == 'folder') {
+    		this.busyFolder.setBusy(false);
+    		this.busy = 'none';
+    	}
+        var l = this.store.count() - 1;
+        for (var i = this.currentRecord + 1; i <= l; i++) {
+            var template = this.store.fillTemplate(i,this.options.template,this.columnsNeeded);
+
+            var item;
+            if (this.hasChildren(i)) {
+                //add as folder
+                var item = new Jx.TreeFolder($merge(this.options.folderOptions, {
+                    label: template
+                }));
+                
+                if (this.options.monitorFolders) {
+                	item.addEvent('disclosed', this.checkFolder);
+                }
+                
+                this.folders.set(i,item);
+            } else {
+                //add as item
+                var item = new Jx.TreeItem($merge(this.options.itemOptions, {
+                    label: template
+                }));
+            }
+            document.id(item).store('index', i);
+            document.id(item).store('jxAdaptor', this);
+            //check for a parent
+            if (this.hasParent(i)) {
+                //add as child of parent
+                var p = this.getParentIndex(i);
+                var folder = this.folders.get(p);
+                folder.add(item);
+            } else {
+                //otherwise add to the tree itself
+                this.tree.add(item);
+            }
+        }
+        this.currentRecord = l;
+    },
+    
+    checkFolder: function (folder) {
+        var items = folder.items();
+        if (!$defined(items) || items.length === 0) {
+            //get items via the store
+        	var index = document.id(folder).retrieve('index');
+        	var node = this.store.get('primaryKey', index);
+        	this.busyFolder = folder;
+        	this.busyFolder.setBusy(true);
+        	this.busy = 'folder';
+            this.store.load({
+                node: node
+            });
+        }
+    },
+    
+    hasChildren: $empty,
+    
+    hasParent: $empty,
+    
+    getParentIndex: function(){}
+    
+    
+});/**
+ * Class: Jx.Adaptor.Tree.Mptt
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * This class requires an MPTT (Modified Preorder Tree Traversal) table. The MPTT
+ * has a 'left' and a 'right' column that indicates the order of nesting. For 
+ * more details see the sitepoint.com article at 
+ * http://articles.sitepoint.com/article/hierarchical-data-database
+ * 
+ * if useAjax option is set to true then this adapter will send an Ajax request
+ * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
+ * to request additional nodes. 
+ */
+Jx.Adaptor.Tree.Mptt = new Class({
+    
+
+    Family: 'Jx.Adaptor.Tree.Mptt',
+    Extends: Jx.Adaptor.Tree,
+    
+    name: 'tree.mptt',
+    
+    options: {
+        left: 'left',
+        right: 'right'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+        var l = this.store.get(this.options.left, index).toInt();
+        var r = this.store.get(this.options.right, index).toInt();
+        return (l + 1 !== r);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        var i = this.getParentIndex(index);
+        if ($defined(i)) {
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        var l = this.store.get(this.options.left, index).toInt();
+        var r = this.store.get(this.options.right, index).toInt();
+        for (var i = index-1; i >= 0; i--) {
+            var pl = this.store.get(this.options.left, i).toInt();
+            var pr = this.store.get(this.options.right, i).toInt();
+            if (pl < l && pr > r) {
+                return i;
+            }
+        }
+        return null;
+    }
+});/**
+ * Class: Jx.Adapter.Tree.Parent
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * Basically, the store needs to have a column that will indicate each the 
+ * parent of each row. The root(s) of the tree should be indicated by a "-1" 
+ * in this column. The name of the "parent" column is configurable in the 
+ * options.
+ * 
+ * if useAjax option is set to true then this adapter will send an Ajax request
+ * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
+ * to request additional nodes. Also, a column indicating whether this is a folder needs 
+ * to be set as there is no way to tell if a node has children without it.
+ */
+Jx.Adaptor.Tree.Parent = new Class({
+    
+    Family: 'Jx.Adaptor.Tree.Parent',
+    Extends: Jx.Adaptor.Tree,
+    
+    options: {
+        parentColumn: 'parent',
+        folderColumn: 'folder'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+    	return this.store.get(this.options.folderColumn, index);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        if (this.store.get(this.options.parentColumn, index).toInt() !== -1) {
+            return true;
+        } 
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        //get the parent based on the index
+        var pk = this.store.get(this.options.parentColumn, index);
+        return this.store.findByColumn('primaryKey', pk);
+    }
+});// $Id: context.js 663 2009-12-08 07:08:21Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu.Context
  *
@@ -29342,8 +34224,8 @@
         this.contentContainer.setStyle('visibility','');
         this.showChrome(this.contentContainer);
 
-        document.addEvent('mousedown', this.hideWatcher);
-        document.addEvent('keyup', this.keypressWatcher);
+        document.addEvent('mousedown', this.bound.mousedown);
+        document.addEvent('keyup', this.bound.keypress);
 
         e.stop();
     }
@@ -29412,7 +34294,7 @@
      * Show the menu item
      */
     show: $empty
-});// $Id: submenu.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: submenu.js 708 2010-03-01 15:45:27Z pagameba $
 /**
  * Class: Jx.Menu.SubMenu
  *
@@ -29561,6 +34443,13 @@
         return this;
     },
     /**
+     * APIMethod: empty
+     * remove all items from the sub menu
+     */
+    empty: function() {
+      this.menu.empty();
+    },
+    /**
      * Method: deactivate
      * Deactivate the sub menu
      *
@@ -29746,7 +34635,7 @@
             }
         }
     }
-});// $Id: tabset.js 626 2009-11-20 13:22:22Z pagameba $
+});// $Id: tabset.js 825 2010-03-31 18:35:28Z pagameba $
 /**
  * Class: Jx.TabSet
  *
@@ -29837,7 +34726,7 @@
      * than one tab can be added by passing extra parameters to this method.
      */
     add: function() {
-        $A(arguments).each(function(tab) {
+        $A(arguments).flatten().each(function(tab) {
             if (tab instanceof Jx.Button.Tab) {
                 tab.addEvent('down',this.setActiveTabFn);
                 tab.tabSet = this;
@@ -30073,7 +34962,7 @@
         this.domObj.appendChild(this.domSpan);
     }
 });
-// $Id: tree.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: tree.js 755 2010-03-15 03:09:37Z jonlb at comcast.net $
 /**
  * Class: Jx.Tree
  *
@@ -30094,6 +34983,7 @@
     Family: 'Jx.Tree',
     Extends: Jx.Widget,
     parameters: ['options','container', 'selection'],
+    pluginNamespace: 'Tree',
     /**
      * APIProperty: selection
      * {<Jx.Selection>} the selection object for this tree.
@@ -30379,7 +35269,7 @@
     }
 });
 
-// $Id: treeitem.js 626 2009-11-20 13:22:22Z pagameba $
+// $Id: treeitem.js 755 2010-03-15 03:09:37Z jonlb at comcast.net $
 /**
  * Class: Jx.TreeItem
  *
@@ -30449,7 +35339,10 @@
          */
         imageClass: '',
         lastLeafClass: 'jxTreeLeafLast',
-        template: '<li class="jxTreeContainer jxTreeLeaf"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><a class="jxTreeItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel"></span></a></li>'
+        template: '<li class="jxTreeContainer jxTreeLeaf"><img class="jxTreeImage" src="'+Jx.aPixel.src+'" alt="" title=""><a class="jxTreeItem" href="javascript:void(0);"><img class="jxTreeIcon" src="'+Jx.aPixel.src+'" alt="" title=""><span class="jxTreeLabel"></span></a></li>',
+        busyMask: {
+			message: null
+		}
     },
     classes: new Hash({
         domObj: 'jxTreeContainer',
@@ -30637,6 +35530,29 @@
     },
     setSelection: function(selection){
         this.selection = selection;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        this.domImg.addClass(this.options.busyClass)
+      } else {
+        if (this.options.busyClass) {
+          this.domImg.removeClass(this.options.busyClass);
+        }
+      }
     }
 });
 // $Id: treefolder.js 626 2009-11-20 13:22:22Z pagameba $
@@ -30899,10 +35815,10 @@
         this.tree.setSelection(selection);
         return this;
     }
-});
+});// $Id: slider.js 727 2010-03-04 14:04:04Z pagameba $
 /**
  * Class: Jx.Slider
- * This class wraps the mootools-more slider class to make it more Jx.Slider
+ * This class wraps the mootools-more slider class to make it more Jx friendly
  *
  * Copyright 2009 by Jonathan Bomgardner
  * License: MIT-style
@@ -30976,10 +35892,15 @@
      */
     render: function () {
         this.parent();
-
+        
+        /** 
+         * Not sure why this is here...
+         */
+        /**
         if (this.domObj) {
             return;
         }
+        **/
 
         this.sliderOpts = {
             range: [this.options.min, this.options.max],
@@ -31002,7 +35923,7 @@
     },
     /**
      * Method: complete
-     * Called when the slider stops movingand the mouse button is released.
+     * Called when the slider stops moving and the mouse button is released.
      */
     complete: function (step) {
         this.fireEvent('complete', [step, this]);
@@ -31017,14 +35938,24 @@
             this.slider = new Slider(this.domObj, this.knob, this.sliderOpts);
         }
         this.slider.set(this.options.startAt);
+    },
+    /**
+     * APIMethod: set
+     * set the value of the slider
+     */
+    set: function(value) {
+      this.slider.set(value);
     }
-});// $Id: $
+});// $Id: notice.js 776 2010-03-22 14:35:16Z pagameba $
 /**
  * Class: Jx.Notice
  *
  * Extends: <Jx.ListItem>
  *
  * Events:
+ * 
+ * MooTools.lang Keys:
+ * - notice.closeTip
  *
  * License:
  * Copyright (c) 2009, DM Solutions Group.
@@ -31037,10 +35968,32 @@
     Extends: Jx.ListItem,
 
     options: {
+        /**
+         * Option: fx
+         * the effect to use on the notice when it is shown and hidden,
+         * 'fade' by default
+         */
         fx: 'fade',
+        /**
+         * Option: chrome
+         * {Boolean} should the notice be displayed with chrome or not,
+         * default is false
+         */
         chrome: false,
+        /**
+         * Option: enabled
+         * {Boolean} default is false
+         */
         enabled: true,
-        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="close this notice"></a></div></li>',
+        /**
+         * Option: template
+         * {String} the HTML template of a notice
+         */
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        /**
+         * Option: klass
+         * {String} css class to add to the notice
+         */
         klass: ''
     },
 
@@ -31052,7 +36005,7 @@
     }),
 
     /**
-     * APIMethod: render
+     * Method: render
      */
     render: function () {
         this.parent();
@@ -31064,11 +36017,17 @@
             this.domClose.addEvent('click', this.close.bind(this));
         }
     },
-
+    /**
+     * APIMethod: close
+     * close the notice
+     */
     close: function() {
         this.fireEvent('close', this);
     },
-    
+    /**
+     * APIMethod: show
+     * show the notice
+     */
     show: function(el, onComplete) {
         if (this.options.chrome) {
             this.showChrome();
@@ -31081,7 +36040,10 @@
             if (onComplete) onComplete();
         }
     },
-    
+    /**
+     * APIMethod: hide
+     * hide the notice
+     */
     hide: function(onComplete) {
         if (this.options.chrome) {
             this.hideChrome();
@@ -31095,38 +36057,63 @@
         }
     }
 });
-
+/**
+ * Class: Jx.Notice.Information
+ * A <Jx.Notice> subclass useful for displaying informational messages
+ */
 Jx.Notice.Information = new Class({
     Extends: Jx.Notice,
     options: {
-        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="close this notice"></a></div></li>',
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
         klass: 'jxNoticeInformation'
     }
 });
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying success messages
+ */
 Jx.Notice.Success = new Class({
     Extends: Jx.Notice,
     options: {
-        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="close this notice"></a></div></li>',
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
         klass: 'jxNoticeSuccess'
     }
 });
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying warning messages
+ */
 Jx.Notice.Warning = new Class({
     Extends: Jx.Notice,
     options: {
-        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="close this notice"></a></div></li>',
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
         klass: 'jxNoticeWarning'
     }
 });
+/**
+ * Class: Jx.Notice.Error
+ * A <Jx.Notice> subclass useful for displaying error messages
+ */
 Jx.Notice.Error = new Class({
     Extends: Jx.Notice,
     options: {
-        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="close this notice"></a></div></li>',
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
         klass: 'jxNoticeError'
     }
 });
-
-
-
+// $Id: notifier.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Notifier
+ *
+ * Extends: <Jx.ListView>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
 Jx.Notifier = new Class({
     
     Family: 'Jx.Notifier',
@@ -31158,10 +36145,10 @@
         listObj: 'jxNoticeList'
     }),
     
-    init: function () {
-        this.parent();
-    },
-    
+    /**
+     * Method: render
+     * render the widget
+     */
     render: function () {
         this.parent();
         
@@ -31179,6 +36166,13 @@
         }.bind(this));
     },
     
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
     add: function (notice) {
         if (!(notice instanceof Jx.Notice)) {
             notice = new Jx.Notice({content: notice});
@@ -31187,36 +36181,89 @@
         notice.show(this.listObj);
     },
     
+    /**
+     * APIMethod: remove
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to remove
+     */
     remove: function (notice) {
         if (this.domObj.hasChild(notice)) {
             notice.removeEvents('close');
             notice.hide();
         }
     }
-});Jx.Notifier.Float = new Class({
+});// $Id: notifier.float.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Notice.Float
+ * A floating notice area for displaying notices, notices get chrome if
+ * the notifier has chrome
+ *
+ * Extends: <Jx.Notifier>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier.Float = new Class({
     
     Family: 'Jx.Notifier.Float',
     Extends: Jx.Notifier,
     
     options: {
+        /**
+         * Option: chrome
+         * {Boolean} should the notifier have chrome - default true
+         */
         chrome: true,
+        /**
+         * Option: fx
+         * {String} the effect to use when showing and hiding the notifier,
+         * default is null
+         */
         fx: null,
+        /**
+         * Option: width
+         * {Integer} the width in pixels of the notifier, default is 250
+         */
         width: 250,
+        /**
+         * Option: position
+         * {Object} position options to use with <Jx.Widget::position>
+         * for positioning the Notifier
+         */
         position: {
             horizontal: 'center center',
             vertical: 'top top'
         }
     },
 
+    /**
+     * Method: render
+     * render the widget
+     */
     render: function () {
         this.parent();
         this.domObj.setStyle('position','absolute');
         if ($defined(this.options.width)) {
             this.domObj.setStyle('width',this.options.width);
         }
-        this.position(this.domObj, this.options.parent, this.options.position);
+        this.position(this.domObj, 
+                      this.options.parent,
+                      this.options.position);
     },
     
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
     add: function(notice) {
         if (!(notice instanceof Jx.Notice)) {
             notice = new Jx.Notice({content: notice});
@@ -31224,8 +36271,189 @@
         notice.options.chrome = this.options.chrome;
         this.parent(notice);
     }
-}); // $Id: $
+});// $Id: scrollbar.js 776 2010-03-22 14:35:16Z pagameba $
 /**
+ * Class: Jx.Scrollbar
+ * Creates a custom scrollbar either vertically or horizontally (determined by
+ * options). These scrollbars are designed to be styled entirely through CSS.
+ * 
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ * 
+ * Based in part on 'Mootools CSS Styled Scrollbar' on
+ * http://solutoire.com/2008/03/10/mootools-css-styled-scrollbar/
+ */
+Jx.Scrollbar = new Class({
+    
+    Family: 'Jx.Scrollbar',
+    
+    Extends: Jx.Widget,
+    
+    Binds: ['scrollIt'],
+    
+    options: {
+        /**
+         * Option: direction
+         * Determines which bars are visible. Valid options are 'horizontal'
+         * or 'vertical'
+         */
+        direction: 'vertical',
+        /**
+         * Option: useMouseWheel
+         * Whether to allow the mouse wheel to move the content. Defaults 
+         * to true.
+         */
+        useMouseWheel: true,
+        /**
+         * Option: useScrollers
+         * Whether to show the scrollers. Defaults to true.
+         */
+        useScrollers: true,
+        /**
+         * Option: scrollerInterval
+         * The amount to scroll the content when using the scrollers. 
+         * useScrollers option must be true. Default is 50 (px).
+         */
+        scrollerInterval: 50,
+        /**
+         * Option: template
+         * the HTML template for a scrollbar
+         */
+        template: '<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'
+    },
+    
+    classes: new Hash({
+        domObj: 'jxScrollbarContainer',
+        scrollLeft: 'jxScrollLeft',
+        scrollRight: 'jxScrollRight',
+        sliderHolder: 'jxSlider'
+    }),
+    
+    el: null,
+    //element is the element we want to scroll. 
+    parameters: ['element', 'options'],
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.el = document.id(this.options.element);
+        if (this.el) {
+            this.el.addClass('jxHas'+this.options.direction.capitalize()+'Scrollbar');
+            
+            //wrap content to make scroll work correctly
+            var children = this.el.getChildren();
+            this.wrapper = new Element('div',{
+                'class': 'jxScrollbarChildWrapper'
+            });
+            
+            /**
+             * the wrapper needs the same settings as the original container
+             * specifically, the width and height
+             */ 
+            this.wrapper.setStyles({
+                width: this.el.getStyle('width'),
+                height: this.el.getStyle('height')
+            });
+            
+            children.inject(this.wrapper);
+            this.wrapper.inject(this.el);
+            
+            this.domObj.inject(this.el);
+            
+            var scrollSize = this.wrapper.getScrollSize();
+            var size = this.wrapper.getContentBoxSize();
+            this.steps = this.options.direction==='horizontal'?scrollSize.x-size.width:scrollSize.y-size.height;
+            this.slider = new Jx.Slider({
+                snap: false,
+                min: 0,
+                max: this.steps,
+                step: 1,
+                mode: this.options.direction,
+                onChange: this.scrollIt
+                
+            });
+            
+            if (!this.options.useScrollers) {
+                this.scrollLeft.dispose();
+                this.scrollRight.dispose();
+                //set size of the sliderHolder
+                if (this.options.direction === 'horizontal') {
+                    this.sliderHolder.setStyle('width','100%');
+                } else {
+                    this.sliderHolder.setStyle('height', '100%');
+                }
+                
+            } else {
+                this.scrollLeft.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                this.scrollRight.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                //set size of the sliderHolder
+                var holderSize, scrollerRightSize, scrollerLeftSize;
+                if (this.options.direction === 'horizontal') {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().width;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().width;
+                    holderSize = size.width - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('width', holderSize + 'px');
+                } else {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().height;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().height;
+                    holderSize = size.height - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('height', holderSize + 'px');
+                }
+            }
+            document.id(this.slider).inject(this.sliderHolder);
+            
+            //allows mouse wheel to function
+            if (this.options.useMouseWheel) {
+                $$(this.el, this.domObj).addEvent('mousewheel', function(e){
+                    e = new Event(e).stop();
+                    var step = this.slider.slider.step - e.wheel * 30;
+                    this.slider.slider.set(step);
+                }.bind(this));
+            }
+            
+            //stop slider if we leave the window
+            document.id(document.body).addEvent('mouseleave', function(){ 
+                this.slider.slider.drag.stop();
+            }.bind(this));
+
+            this.slider.start();
+        }
+    },
+    
+    /**
+     * Method: scrollIt
+     * scroll the content in response to the slider being moved.
+     */
+    scrollIt: function (step) {
+        var x = this.options.direction==='horizontal'?step:0;
+        var y = this.options.direction==='horizontal'?0:step;
+        this.wrapper.scrollTo(x,y);
+    }
+}); // $Id: formatter.js 649 2009-11-30 22:19:48Z pagameba $
+/**
  * Class: Jx.Formatter
  *
  * Extends: <Jx.Object>
@@ -31251,7 +36479,7 @@
      * the needed formatting functionality.
      */
     format: $empty
-});// $Id: $
+});// $Id: number.js 770 2010-03-18 21:12:28Z jonlb at comcast.net $
 /**
  * Class: Jx.Formatter.Number
  *
@@ -31267,6 +36495,10 @@
  * Example:
  * (code)
  * (end)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.number'.decimalSeparator
+ * - 'formatter.number'.thousandsSeparator
  *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
@@ -31279,16 +36511,6 @@
 
     options: {
         /**
-         * Option: decimalSeparator
-         * Character to use as the decimal separator
-         */
-        decimalSeparator: '.',
-        /**
-         * Option: thousandSeparator
-         * Character to use as the thousands separator
-         */
-        thousandsSeparator: ',',
-        /**
          * Option: precision
          * The number of decimal places to round to
          */
@@ -31345,11 +36567,11 @@
             for (var i = 0; i < l; i++) {
                 ret = ret + main.charAt(i);
                 if (i === left - 1 && i !== l - 1) {
-                    ret = ret + this.options.thousandsSeparator;
+                    ret = ret + MooTools.lang.get('Jx','formatter.number').thousandsSeparator;
                 } else if (i >= left) {
                     j++;
                     if (j === 3 && i !== l - 1) {
-                        ret = ret + this.options.thousandsSeparator;
+                        ret = ret + MooTools.lang.get('Jx','formatter.number').thousandsSeparator;
                         j = 0;
                     }
                 }
@@ -31360,7 +36582,7 @@
         }
 
         if (dec) {
-            ret = ret + this.options.decimalSeparator + parts[1];
+            ret = ret + MooTools.lang.get('Jx','formatter.number').decimalSeparator + parts[1];
         }
         if (neg && this.options.useParens) {
             ret = "(" + ret + ")";
@@ -31369,8 +36591,21 @@
         }
 
         return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
     }
-});// $Id: $
+});// $Id: currency.js 770 2010-03-18 21:12:28Z jonlb at comcast.net $
 /**
  * Class: Jx.Formatter.Currency
  *
@@ -31383,6 +36618,9 @@
  * Example:
  * (code)
  * (end)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.currency'.sign
  *
  * License:
  * Copyright (c) 2009, Jon Bomgardner.
@@ -31393,14 +36631,7 @@
 
     Extends: Jx.Formatter.Number,
 
-    options: {
-        /**
-         * Option: sign
-         * The sign to use for this currency. Defaults to
-         * the US '$'.
-         */
-        sign: "$"
-    },
+    options: {},
     /**
      * APIMethod: format
      * Takes a number and formats it as currency.
@@ -31422,14 +36653,27 @@
 
         var ret;
         if (neg && !this.options.useParens) {
-            ret = "-" + this.options.sign + value.substring(1, value.length);
+            ret = "-" + MooTools.lang.get('Jx','formatter.currency').sign + value.substring(1, value.length);
         } else {
-            ret = this.options.sign + value;
+            ret = MooTools.lang.get('Jx','formatter.currency').sign + value;
         }
 
         return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
     }
-});// $Id: $
+});// $Id: date.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Formatter.Date
  *
@@ -31472,14 +36716,17 @@
         var d = Date.parse(value);
         return d.format(this.options.format);
     }
-});// $Id: $
+});// $Id: uri.js 767 2010-03-17 19:35:02Z pagameba $
 /**
- * Class: Jx.Formatter.Boolean
+ * Class: Jx.Formatter.URI
  *
  * Extends: <Jx.Formatter>
  *
- * This class formats boolean values. You supply the
- * text values for true and false in the options.
+ * This class formats URIs using the mootools-more's
+ * URI extensions. See the -more docs for details of
+ * supported formats for parsing and formatting.
+ * 
+ * @url http://mootools.net/docs/more/Native/URI
  *
  * Example:
  * (code)
@@ -31490,24 +36737,79 @@
  *
  * This file is licensed under an MIT style license
  */
-Jx.Formatter.Boolean = new Class({
+Jx.Formatter.Uri = new Class({
 
     Extends: Jx.Formatter,
 
     options: {
         /**
-         * Option: true
-         * The text to display for true values
+         * Option: format
+         * The format to use. See the mootools-more URI options
+         * to use within a {pattern}
+         *   {string} will call the URI.toString() method
          */
-        'true': 'Yes',
-        /**
-         * Option: false
-         * The text to display for false values
-         */
-        'false': 'No'
+        format: '<a href="{string}" target="_blank">{host}</a>'
     },
     /**
      * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+      var uri        = new URI(value),
+          uriContent = {},
+          pattern    = new Array(),
+          patternTmp = this.options.format.match(/\\?\{([^{}]+)\}/g);
+
+      // remove bracktes
+      patternTmp.each(function(e) {
+        pattern.push(e.slice(1, e.length-1));
+      });
+
+      // build object that contains replacements
+      for(var i = 0, j = pattern.length; i < j; i++) {
+        switch(pattern[i]) {
+          case 'string':
+            uriContent[pattern[i]] = uri.toString();
+            break;
+          default:
+            uriContent[pattern[i]] = uri.get(pattern[i]);
+            break;
+        }
+      }
+      return this.options.format.substitute(uriContent);
+    }
+});// $Id: boolean.js 770 2010-03-18 21:12:28Z jonlb at comcast.net $
+/**
+ * Class: Jx.Formatter.Boolean
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats boolean values. You supply the
+ * text values for true and false in the options.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * MooTools.lang Keys:
+ * - 'formatter.boolean'.true
+ * - 'formatter.boolean'.false
+ * 
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Boolean = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {},
+    /**
+     * APIMethod: format
      * Takes a value, determines boolean equivalent and
      * displays the appropriate text value.
      *
@@ -31534,10 +36836,23 @@
         default:
             b = true;
         }
-        return b ? this.options['true'] : this.options['false'];
+        return b ? MooTools.lang.get('Jx','formatter.boolean')['true'] : MooTools.lang.get('Jx','formatter.boolean')['false'];
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
     }
 
-});// $Id: $
+});// $Id: phone.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Formatter.Phone
  *
@@ -31607,7 +36922,7 @@
         ret = ret + v.substring(0, 3) + sep + v.substring(3);
         return ret;
     }
-});// $Id: $
+});// $Id: fieldset.js 701 2010-02-25 15:39:38Z pagameba $
 /**
  * Class: Jx.Fieldset
  *
@@ -31724,7 +37039,7 @@
         for (var x = 0; x < arguments.length; x++) {
             field = arguments[x];
             //add form to the field and field to the form if not already there
-            if (!$defined(field.form) && $defined(this.form)) {
+            if ($defined(field.jxFamily) && !$defined(field.form) && $defined(this.form)) {
                 field.form = this.form;
                 this.form.addField(field);
             }
@@ -31747,7 +37062,7 @@
     }
     
 });
-// $Id: $
+// $Id: checkbox.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Field.Check
  *
@@ -31804,7 +37119,7 @@
                     sibling = this.field.getPrevious();
                 }
                 this.field.setStyle('visibility','hidden');
-                this.field.inject($(document.body));
+                this.field.inject(document.id(document.body));
                 this.field.checked = true;
                 this.field.defaultChecked = true;
                 this.field.dispose();
@@ -31826,13 +37141,15 @@
      * Sets the value property of the field
      *
      * Parameters:
-     * v - The value to set the field to, "checked" if it should be checked.
+     * v - Whether the box shouldbe checked or not. "checked" or "true" if it should be checked.
      */
     setValue : function (v) {
-        if (v === 'checked') {
-            this.field.set('checked', "checked");
-        } else {
-            this.field.erase('checked');
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                this.field.set('checked', "checked");
+            } else {
+                this.field.erase('checked');
+            }
         }
     },
 
@@ -31864,7 +37181,7 @@
     }
 
 });
-// $Id: $
+// $Id: radio.js 826 2010-03-31 18:46:16Z pagameba $
 /**
  * Class: Jx.Field.Radio
  *
@@ -31920,7 +37237,7 @@
                     sibling = this.field.getPrevious();
                 }
                 this.field.setStyle('visibility','hidden');
-                this.field.inject($(document.body));
+                this.field.inject(document.id(document.body));
                 this.field.checked = true;
                 this.field.defaultChecked = true;
                 this.field.dispose();
@@ -31945,10 +37262,12 @@
      * v - The value to set the field to, "checked" it should be checked.
      */
     setValue: function (v) {
-        if (v === 'checked') {
-            this.field.set('checked', "checked");
-        } else {
-            this.field.erase('checked');
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                this.field.set('checked', "checked");
+            } else {
+                this.field.erase('checked');
+            }
         }
     },
 
@@ -31983,7 +37302,7 @@
 
 
 
-// $Id: $
+// $Id: select.js 681 2010-01-15 05:45:28Z jonlb at comcast.net $
 /**
  * Class: Jx.Field.Select
  *
@@ -32125,12 +37444,14 @@
      * v - The value to set the field to.
      */
     setValue: function (v) {
-        //loop through the options and set the one that matches v
-        $$(this.field.options).each(function (opt) {
-            if (opt.get('value') === v) {
-                document.id(opt).set("selected", true);
-            }
-        }, this);
+        if (!this.options.readonly) {
+            //loop through the options and set the one that matches v
+            $$(this.field.options).each(function (opt) {
+                if (opt.get('value') === v) {
+                    document.id(opt).set("selected", true);
+                }
+            }, this);
+        }
     },
 
     /**
@@ -32140,13 +37461,27 @@
     getValue: function () {
         var index = this.field.selectedIndex;
         //check for a set "value" attribute. If not there return the text
-        var ret = this.field.options[index].get("value");
-        if (!$defined(ret)) {
-           ret = this.field.options[index].get("text");
+        if (index > -1) {
+            var ret = this.field.options[index].get("value");
+            if (!$defined(ret)) {
+                ret = this.field.options[index].get("text");
+            }
+            return ret;
         }
-        return ret;
+    },
+    
+    /**
+     * APIMethod: empty
+     * Empties all options from this select
+     */
+    empty: function () {
+        if ($defined(this.field.options)) {
+            $A(this.field.options).each(function (option) {
+                this.field.remove(option);
+            }, this);
+        }
     }
-});// $Id: $
+});// $Id: textarea.js 649 2009-11-30 22:19:48Z pagameba $
 /**
  * Class: Jx.Field.Textarea
  *
@@ -32246,6 +37581,18 @@
 
     options: {
         /**
+         * Option: buttonClass
+         * choose the actual Jx.Button subclass to create for this form
+         * field.  The default is to create a basic Jx.Button.  To create
+         * a different kind of button, pass the class to this option, for
+         * instance:
+         * (code)
+         * buttonClass: Jx.Button.Color
+         * (end)
+         */
+        buttonClass: Jx.Button,
+        
+        /**
          * Option: buttonOptions
          */
         buttonOptions: {},
@@ -32266,7 +37613,7 @@
 
     processTemplate: function(template, classes, container) {
         var h = this.parent(template, classes, container);
-        this.button = new Jx.Button(this.options.buttonOptions);
+        this.button = new this.options.buttonClass(this.options.buttonOptions);
         var c = h.get('jxInputButton');
         if (c) {
             this.button.domObj.replaces(c);
@@ -32277,8 +37624,245 @@
     click: function() {
         this.button.clicked();
     }
-});// $Id: $
+});// $Id: jxcombo.js 826 2010-03-31 18:46:16Z pagameba $
 /**
+ * Class: Jx.Field.Combo
+ *
+ * Extends: <Jx.Field>
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - 
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Combo = new Class({
+    Family: 'Jx.Field.Combo',
+    Extends: Jx.Field,
+
+    options: {
+        buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+        /* Option: template
+         */
+         template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'
+     },
+     
+     type: 'Combo',
+     
+    /**
+     * APIMethod: render
+     * create a new instance of Jx.Field.Combo
+     */
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+        
+        var button = new Jx.Button({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon'
+        }).addTo(this.revealer);
+
+        this.menu = new Jx.Menu();
+        this.menu.button = button;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.buttonSet = new Jx.ButtonSet({
+            onChange: (function(set) {
+                var button = set.activeButton;
+                var l = button.options.label;
+                if (l == '&nbsp;') {
+                    l = '';
+                }
+                this.setLabel(l);
+                var img = button.options.image;
+                if (img.indexOf('a_pixel') != -1) {
+                    img = '';
+                }
+                this.setImage(img);
+                if (this.options.imageClass && this.icon) {
+                    this.icon.removeClass(this.options.imageClass);
+                }
+                if (button.options.imageClass && this.icon) {
+                    this.options.imageClass = button.options.imageClass;
+                    this.icon.addClass(button.options.imageClass);
+                }
+                this.fireEvent('change', this);
+            }).bind(this)
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+        button.addEvent('click', function(e) {
+            if (this.list.count() === 0) {
+                return;
+            }
+            if (!button.options.enabled) {
+                return;
+            }
+            this.contentContainer.setStyle('visibility','hidden');
+            this.contentContainer.setStyle('display','block');
+            document.id(document.body).adopt(this.contentContainer);
+            /* we have to size the container for IE to render the chrome correctly
+             * but just in the menu/sub menu case - there is some horrible peekaboo
+             * bug in IE related to ULs that we just couldn't figure out
+             */
+            this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+            this.showChrome(this.contentContainer);
+
+            this.position(this.contentContainer, this.button.domObj, {
+                horizontal: ['right right'],
+                vertical: ['bottom top', 'top bottom'],
+                offsets: this.chromeOffsets
+            });
+
+            this.contentContainer.setStyle('visibility','');
+
+            document.addEvent('mousedown', this.bound.mousedown);
+            document.addEvent('keyup', this.bound.keypress);
+
+            this.fireEvent('show', this);
+        }.bindWithEvent(this.menu));
+
+        this.menu.addEvents({
+            'show': (function() {
+                //this.setActive(true);
+            }).bind(this),
+            'hide': (function() {
+                //this.setActive(false);
+            }).bind(this)
+        });
+        
+        this.addEvent('change', function(){
+          console.log('on change detected');
+        })
+    },
+    
+    setLabel: function(label) {
+      if ($defined(this.field)) {
+        this.field.value = label;
+      }
+    },
+    
+    setImage: function(url) {
+      if ($defined(this.icon)) {
+        this.icon.setStyle('background-image', 'url('+url+')');
+      }
+      if (!url) {
+        this.wrapper.addClass('jxInputIconHidden');
+      } else {
+        this.wrapper.removeClass('jxInputIconHidden');
+      }
+    },
+
+    /**
+     * Method: valueChanged
+     * invoked when the current value is changed
+     */
+    valueChanged: function() {
+        this.fireEvent('change', this);
+    },
+
+    /**
+     * Method: getValue
+     * returns the currently selected value
+     */
+    getValue: function() {
+        return this.options.label;
+    },
+
+    setValue: function(value) {
+        this.buttonSet.buttons.each(function(button){
+            if (button.options.label === value) {
+                button.setActive(true);
+            }
+        },this);
+    },
+
+    /**
+     * Method: onKeyPress
+     * Handle the user pressing a key by looking for an ENTER key to set the
+     * value.
+     *
+     * Parameters:
+     * e - {Event} the keypress event
+     */
+    onKeyPress: function(e) {
+        if (e.key == 'enter') {
+            this.valueChanged();
+        }
+    },
+
+    /**
+     * Method: add
+     * add a new item to the pick list
+     *
+     * Parameters:
+     * options - {Object} object with properties suitable to be passed to
+     * a <Jx.Menu.Item.Options> object.  More than one options object can be
+     * passed, comma separated or in an array.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(opt) {
+            var button = new Jx.Menu.Item($merge(opt,{
+                toggle: true
+            }));
+            this.menu.add(button);
+            this.buttonSet.add(button);
+            if (opt.selected) {
+              this.buttonSet.setActiveButton(button);
+            }
+        }, this);
+    },
+
+    /**
+     * Method: remove
+     * Remove the item at the given index.  Not implemented.
+     *
+     * Parameters:
+     * idx - {Mixed} the item to remove by reference or by index.
+     */
+    remove: function(idx) {
+      var item;
+      if ($type(idx) == 'number' && idx < this.buttonSet.buttons.length) {
+        item = this.buttonSet.buttons[idx];
+      } else if ($type(idx) == 'string'){
+        this.buttonSet.buttons.some(function(button){
+            if (button.options.label === idx) {
+                item = button;
+                return true;
+            }
+            return false;
+        },this);
+      }
+      if (item) {
+        this.buttonSet.remove(item);
+        this.menu.remove(item);
+      }
+    },
+    /**
+     * APIMethod: empty
+     * remove all values from the combo
+     */
+    empty: function() {
+      this.menu.empty();
+      this.buttonSet.empty();
+      this.setLabel('');
+      this.setImage(Jx.aPixel.src);
+    }
+});// $Id: password.js 649 2009-11-30 22:19:48Z pagameba $
+/**
  * Class: Jx.Field.Password
  *
  * Extends: <Jx.Field.Text>
@@ -32303,4 +37887,163 @@
     },
 
     type: 'Password'
-});
\ No newline at end of file
+});/* 
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+
+/**
+ * Class: Jx.Field.Color
+ *
+ * Extends: <Jx.Field>
+ *
+ * This class provides a Jx.Field.Text in combination with a Jx.Button.Color
+ * to have a Colorpicker with an input field.
+ *
+ * License:
+ * Copyright (c) 2010, Conrad Barthelmes.
+ *
+ * This file is licensed under an MIT style license
+ */
+  Jx.Field.Color = new Class({
+    Extends: Jx.Field,
+    Binds: ['changed','hide','keyup'],
+    type: 'Color',
+    options: {
+      buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+      /**
+       * Option: template
+       * The template used to render this field
+       */
+      template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',
+      /**
+       * Option: showOnHover
+       * {Boolean} show the color palette when hovering over the input, default 
+       * is false
+       */
+      showOnHover: false,
+      /**
+       *  Option: showDelay
+       *  set time in milliseconds when to show the color field on mouseenter
+       */
+      showDelay: 250,
+      /**
+       * Option: errorMsg
+       * error message for the validator.
+       */
+      errorMsg: 'Invalid Web-Color',
+      /**
+       * Option: color
+       * a color to initialize the field with, defaults to #000000
+       * (black) if not specified.
+       */
+      color: '#000000'
+
+    },
+    button: null,
+    validator: null,
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+
+      var self = this;
+      if (!Jx.Field.Color.ColorPalette) {
+          Jx.Field.Color.ColorPalette = new Jx.ColorPalette(this.options);
+      }
+      this.button = new Jx.Button.Flyout({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon',
+          onBeforeOpen: function() {
+            if (Jx.Field.Color.ColorPalette.currentButton) {
+                Jx.Field.Color.ColorPalette.currentButton.hide();
+            }
+            Jx.Field.Color.ColorPalette.currentButton = this;
+            Jx.Field.Color.ColorPalette.addEvent('change', self.changed);
+            Jx.Field.Color.ColorPalette.addEvent('click', self.hide);
+            this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);
+            Jx.Field.Color.ColorPalette.domObj.setStyle('display', 'block');
+          },
+          onOpen: function() {
+            /* setting these before causes an update problem when clicking on
+             * a second color button when another one is open - the color
+             * wasn't updating properly
+             */
+            Jx.Field.Color.ColorPalette.options.color = self.options.color;
+            Jx.Field.Color.ColorPalette.updateSelected();
+          }
+        }).addTo(this.revealer);
+
+      this.validator = new Jx.Plugin.Field.Validator({
+        validators: [{
+            validatorClass: 'colorHex',
+            validator: {
+              name: 'colorValidator',
+              options: {
+                validateOnChange: false,
+                errorMsg: self.options.errorMsg,
+                test: function(field,props) {
+                  try {
+                    var c = field.get('value').hexToRgb(true);
+                    if(c == null) return false;
+                    for(var i = 0; i < 3; i++) {
+                      if(c[i].toString() == 'NaN') {
+                        return false;
+                      }
+                    }
+                  }catch(e) {
+                    return false;
+                  }
+                  c = c.rgbToHex().toUpperCase();
+                  self.setColor(c);
+                  return true;
+                }
+              }
+            }
+        }],
+        validateOnBlur: true,
+        validateOnChange: true
+      });
+      this.validator.attach(this);
+      this.field.addEvent('keyup', this.onKeyUp.bind(this));
+      if (this.options.showOnHover) {
+        this.field.addEvent('mouseenter', function(ev) {
+          self.button.clicked.delay(self.options.showDelay, self.button);
+        });
+      }
+      this.setValue(this.options.color);
+      this.icon.setStyle('background-color', this.options.color);
+      //this.addEvent('change', self.changed);
+    },
+    onKeyUp : function(ev) {
+      var color = this.getValue();
+      if (color.substring(0,1) == '#') {
+          color = color.substring(1);
+      }
+      if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+          this.options.color = '#' +color.toUpperCase();
+          this.setColor(this.options.color);
+      }
+    },
+    setColor: function(c) {
+        this.options.color = c;
+        this.setValue(c);
+        this.icon.setStyle('background-color', c);
+    },
+    changed: function() {
+        var c = Jx.Field.Color.ColorPalette.options.color;
+        this.setColor(c);
+    },
+    hide: function() {
+        this.button.setActive(false);
+        Jx.Field.Color.ColorPalette.removeEvent('change', this.changed);
+        Jx.Field.Color.ColorPalette.removeEvent('click', this.hide);
+
+        this.button.hide();
+        Jx.Field.Color.ColorPalette.currentButton = null;
+    }
+  });



More information about the fusion-commits mailing list