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

svn_fusion at osgeo.org svn_fusion at osgeo.org
Mon Nov 2 11:13:00 EST 2009


Author: pagameba
Date: 2009-11-02 11:12:58 -0500 (Mon, 02 Nov 2009)
New Revision: 1959

Modified:
   sandbox/jxlib-3.0/lib/jxlib.uncompressed.js
Log:
updating jxlib to 3.0 dev version.

Modified: sandbox/jxlib-3.0/lib/jxlib.uncompressed.js
===================================================================
--- sandbox/jxlib-3.0/lib/jxlib.uncompressed.js	2009-11-02 16:08:07 UTC (rev 1958)
+++ sandbox/jxlib-3.0/lib/jxlib.uncompressed.js	2009-11-02 16:12:58 UTC (rev 1959)
@@ -1,12 +1,12 @@
 /******************************************************************************
- * MooTools 1.2.1
+ * MooTools 1.2.2
  * Copyright (c) 2006-2007 [Valerio Proietti](http://mad4milk.net/).
  * MooTools is distributed under an MIT-style license.
  ******************************************************************************
  * 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.2
+ * Jx UI Library, 2.0.1
  * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -28,26 +28,30 @@
  * DEALINGS IN THE SOFTWARE.
  *****************************************************************************/
 /*
-Script: Core.js
-	MooTools - My Object Oriented JavaScript Tools.
+---
 
-License:
-	MIT-style license.
+script: Core.js
 
-Copyright:
-	Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
 
-Code & Documentation:
-	[The MooTools production team](http://mootools.net/developers/).
+license: MIT-style license.
 
-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)
+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.2',
-	'build': 'f0491d62fbb7e906789aa3733d6a67d43e5af7c9'
+	'version': '1.2.4',
+	'build': '0d9113241a90b9cd5643b926795852a2026710d4'
 };
 
 var Native = function(options){
@@ -82,7 +86,8 @@
 
 	object.alias = function(a1, a2, a3){
 		if (typeof a1 == 'string'){
-			if ((a1 = this.prototype[a1])) return add(this, a2, a1, a3);
+			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;
@@ -128,7 +133,7 @@
 		'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(window[g], generics[g][i], true);
+		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
 	}
 })();
 
@@ -228,7 +233,7 @@
 };
 
 function $lambda(value){
-	return (typeof value == 'function') ? value : function(){
+	return ($type(value) == 'function') ? value : function(){
 		return value;
 	};
 };
@@ -314,11 +319,21 @@
 	return unlinked;
 };
 /*
-Script: Browser.js
-	The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+---
 
-License:
-	MIT-style license.
+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({
@@ -338,7 +353,7 @@
 		},
 
 		trident: function(){
-			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4);
+			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
 		},
 
 		webkit: function(){
@@ -346,7 +361,7 @@
 		},
 
 		gecko: function(){
-			return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18);
+			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
 		}
 
 	}
@@ -377,6 +392,8 @@
 		return new XMLHttpRequest();
 	}, function(){
 		return new ActiveXObject('MSXML2.XMLHTTP');
+	}, function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
 	});
 };
 
@@ -453,7 +470,7 @@
 		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
 			doc.execCommand("BackgroundImageCache", false, true);
 		});
-		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() {
+		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
 			doc.window.detachEvent('onunload', arguments.callee);
 			doc.head = doc.html = doc.window = null;
 		});
@@ -470,11 +487,21 @@
 
 new Document(document);
 /*
-Script: Array.js
-	Contains Array Prototypes like each, contains, and erase.
+---
 
-License:
-	MIT-style license.
+script: Array.js
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Array.each
+
+provides: [Array]
+
+...
 */
 
 Array.implement({
@@ -494,7 +521,7 @@
 		return results;
 	},
 
-	clean: function() {
+	clean: function(){
 		return this.filter($defined);
 	},
 
@@ -610,11 +637,21 @@
 
 });
 /*
-Script: Function.js
-	Contains Function Prototypes like create, bind, pass, and delay.
+---
 
-License:
-	MIT-style license.
+script: Function.js
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Function]
+
+...
 */
 
 Function.implement({
@@ -671,11 +708,21 @@
 
 });
 /*
-Script: Number.js
-	Contains Number Prototypes like limit, round, times, and ceil.
+---
 
-License:
-	MIT-style license.
+script: Number.js
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Number]
+
+...
 */
 
 Number.implement({
@@ -715,11 +762,20 @@
 	Number.implement(methods);
 })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
 /*
-Script: String.js
-	Contains String Prototypes like camelCase, capitalize, test, and toInt.
+---
 
-License:
-	MIT-style license.
+script: String.js
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires:
+- /Native
+
+provides: [String]
+
+...
 */
 
 String.implement({
@@ -800,11 +856,20 @@
 
 });
 /*
-Script: Hash.js
-	Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+---
 
-License:
-	MIT-style license.
+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({
@@ -823,14 +888,14 @@
 	},
 
 	extend: function(properties){
-		Hash.each(properties, function(value, key){
+		Hash.each(properties || {}, function(value, key){
 			Hash.set(this, key, value);
 		}, this);
 		return this;
 	},
 
 	combine: function(properties){
-		Hash.each(properties, function(value, key){
+		Hash.each(properties || {}, function(value, key){
 			Hash.include(this, key, value);
 		}, this);
 		return this;
@@ -934,12 +999,27 @@
 
 Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
 /*
-Script: Event.js
-	Contains the Event Native, to make the event object completely crossbrowser.
+---
 
-License:
-	MIT-style license.
+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',
@@ -1047,11 +1127,26 @@
 
 });
 /*
-Script: Class.js
-	Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+---
 
-License:
-	MIT-style license.
+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){
@@ -1199,11 +1294,20 @@
 	
 };
 /*
-Script: Class.Extras.js
-	Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+---
 
-License:
-	MIT-style license.
+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({
@@ -1262,12 +1366,13 @@
 	},
 
 	removeEvents: function(events){
+		var type;
 		if ($type(events) == 'object'){
-			for (var type in events) this.removeEvent(type, events[type]);
+			for (type in events) this.removeEvent(type, events[type]);
 			return this;
 		}
 		if (events) events = Events.removeOn(events);
-		for (var type in this.$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]);
@@ -1278,7 +1383,7 @@
 });
 
 Events.removeOn = function(string){
-	return string.replace(/^on([A-Z])/, function(full, first) {
+	return string.replace(/^on([A-Z])/, function(full, first){
 		return first.toLowerCase();
 	});
 };
@@ -1298,12 +1403,26 @@
 
 });
 /*
-Script: Element.js
-	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.
+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({
@@ -1316,7 +1435,7 @@
 		var konstructor = Element.Constructors.get(tag);
 		if (konstructor) return konstructor(props);
 		if (typeof tag == 'string') return document.newElement(tag, props);
-		return $(tag).set(props);
+		return document.id(tag).set(props);
 	},
 
 	afterImplement: function(key, value){
@@ -1348,23 +1467,26 @@
 	initialize: function(){
 		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
 		var props = params.properties || {};
-		var iframe = $(params.iframe) || false;
+		var iframe = document.id(params.iframe);
 		var onload = props.onload || $empty;
 		delete props.onload;
-		props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + $time());
+		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){
+			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);
 		};
-		(window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
+		var contentWindow = $try(function(){
+			return iframe.contentWindow;
+		});
+		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
 		return iframe;
 	}
 
@@ -1378,12 +1500,12 @@
 		if (options.ddup || options.cash){
 			var uniques = {}, returned = [];
 			for (var i = 0, l = elements.length; i < l; i++){
-				var el = $.element(elements[i], !options.cash);
+				var el = document.id(elements[i], !options.cash);
 				if (options.ddup){
 					if (uniques[el.uid]) continue;
 					uniques[el.uid] = true;
 				}
-				returned.push(el);
+				if (el) returned.push(el);
 			}
 			elements = returned;
 		}
@@ -1414,7 +1536,7 @@
 			});
 			tag = '<' + tag + '>';
 		}
-		return $.element(this.createElement(tag)).set(props);
+		return document.id(this.createElement(tag)).set(props);
 	},
 
 	newTextNode: function(text){
@@ -1427,18 +1549,53 @@
 
 	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(el, nocash){
-		if (el && el.$family && el.uid) return el;
-		var type = $type(el);
-		return ($[type]) ? $[type](el, nocash, this.document) : null;
-	},
-
 	$$: function(selector){
 		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
 		var elements = [];
@@ -1463,31 +1620,10 @@
 
 });
 
-$.string = function(id, nocash, doc){
-	id = doc.getElementById(id);
-	return (id) ? $.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 $.element(obj.toElement(doc), nocash);
-	return null;
-};
-
-$.textnode = $.whitespace = $.window = $.document = $arguments(0);
-
 Native.implement([Element, Document], {
 
 	getElement: function(selector, nocash){
-		return $(this.getElements(selector, true)[0] || null, nocash);
+		return document.id(this.getElements(selector, true)[0] || null, nocash);
 	},
 
 	getElements: function(tags, nocash){
@@ -1538,7 +1674,7 @@
 	Hash.each(collected, clean);
 	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
 	if (window.CollectGarbage) CollectGarbage();
-	collected = {}; storage = {};
+	collected = storage = null;
 };
 
 var walk = function(element, walk, start, match, all, nocash){
@@ -1546,7 +1682,7 @@
 	var elements = [];
 	while (el){
 		if (el.nodeType == 1 && (!match || Element.match(el, match))){
-			if (!all) return $(el, nocash);
+			if (!all) return document.id(el, nocash);
 			elements.push(el);
 		}
 		el = el[walk];
@@ -1558,10 +1694,11 @@
 	'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', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
+var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
 
 bools = bools.associate(bools);
 
@@ -1598,12 +1735,12 @@
 	where = where.capitalize();
 
 	Element.implement('inject' + where, function(el){
-		inserter(this, $(el, true));
+		inserter(this, document.id(el, true));
 		return this;
 	});
 
 	Element.implement('grab' + where, function(el){
-		inserter($(el, true), this);
+		inserter(document.id(el, true), this);
 		return this;
 	});
 
@@ -1689,7 +1826,7 @@
 
 	adopt: function(){
 		Array.flatten(arguments).each(function(element){
-			element = $(element, true);
+			element = document.id(element, true);
 			if (element) this.appendChild(element);
 		}, this);
 		return this;
@@ -1700,23 +1837,23 @@
 	},
 
 	grab: function(el, where){
-		inserters[where || 'bottom']($(el, true), this);
+		inserters[where || 'bottom'](document.id(el, true), this);
 		return this;
 	},
 
 	inject: function(el, where){
-		inserters[where || 'bottom'](this, $(el, true));
+		inserters[where || 'bottom'](this, document.id(el, true));
 		return this;
 	},
 
 	replaces: function(el){
-		el = $(el, true);
+		el = document.id(el, true);
 		el.parentNode.replaceChild(this, el);
 		return this;
 	},
 
 	wraps: function(el, where){
-		el = $(el, true);
+		el = document.id(el, true);
 		return this.replaces(el).grab(el, where);
 	},
 
@@ -1752,7 +1889,7 @@
 		return walk(this, 'parentNode', null, match, true, nocash);
 	},
 	
-	getSiblings: function(match, nocash) {
+	getSiblings: function(match, nocash){
 		return this.getParent().getChildren(match, nocash).erase(this);
 	},
 
@@ -1774,7 +1911,7 @@
 		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
 			if (!parent) return null;
 		}
-		return $.element(el, nocash);
+		return document.id(el, nocash);
 	},
 
 	getSelected: function(){
@@ -1792,7 +1929,7 @@
 	toQueryString: function(){
 		var queryString = [];
 		this.getElements('input, select, textarea', true).each(function(el){
-			if (!el.name || el.disabled) return;
+			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;
@@ -1827,7 +1964,7 @@
 		}
 
 		clean(clone, this);
-		return $(clone);
+		return document.id(clone);
 	},
 
 	destroy: function(){
@@ -1849,7 +1986,7 @@
 	},
 
 	hasChild: function(el){
-		el = $(el, true);
+		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);
@@ -1975,11 +2112,21 @@
 	}
 };
 /*
-Script: Element.Event.js
-	Contains Element methods for dealing with events, and custom Events.
+---
 
-License:
-	MIT-style license.
+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){
@@ -2042,14 +2189,15 @@
 	},
 
 	removeEvents: function(events){
+		var type;
 		if ($type(events) == 'object'){
-			for (var type in events) this.removeEvent(type, events[type]);
+			for (type in events) this.removeEvent(type, events[type]);
 			return this;
 		}
 		var attached = this.retrieve('events');
 		if (!attached) return this;
 		if (!events){
-			for (var type in attached) this.removeEvents(type);
+			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]);
@@ -2068,7 +2216,7 @@
 	},
 
 	cloneEvents: function(from, type){
-		from = $(from);
+		from = document.id(from);
 		var fevents = from.retrieve('events');
 		if (!fevents) return this;
 		if (!type){
@@ -2122,11 +2270,20 @@
 
 })();
 /*
-Script: Element.Style.js
-	Contains methods for interacting with the styles of Elements in a fashionable way.
+---
 
-License:
-	MIT-style license.
+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){
@@ -2226,7 +2383,7 @@
 
 	getStyles: function(){
 		var result = {};
-		Array.each(arguments, function(key){
+		Array.flatten(arguments).each(function(key){
 			result[key] = this.getStyle(key);
 		}, this);
 		return result;
@@ -2262,15 +2419,24 @@
 	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
 });
 /*
-Script: Element.Dimensions.js
-	Contains methods to work with size, scroll, or positioning of Elements and the window object.
+---
 
-License:
-	MIT-style license.
+script: Element.Dimensions.js
 
-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).
+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(){
@@ -2322,13 +2488,18 @@
 		return null;
 	},
 
-	getOffsets: function(){		
-		if (Browser.Engine.trident){
-			var bound = this.getBoundingClientRect(), html = this.getDocument().documentElement;
-			var isFixed = styleString(this, 'position') == 'fixed';
+	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 + ((isFixed) ? 0 : html.scrollLeft) - html.clientLeft,
-				y: bound.top +  ((isFixed) ? 0 : html.scrollTop)  - html.clientTop
+				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
 			};
 		}
 
@@ -2365,35 +2536,49 @@
 
 	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 = $(relative))) ? relative.getPosition() : {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};
+		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')};
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
 	},
 
-	position: function(obj){
+	setPosition: function(obj){
 		return this.setStyles(this.computePosition(obj));
 	}
 
 });
 
+
 Native.implement([Document, Window], {
 
 	getSize: function(){
-		if (Browser.Engine.presto || Browser.Engine.webkit) {
+		if (Browser.Engine.presto || Browser.Engine.webkit){
 			var win = this.getWindow();
 			return {x: win.innerWidth, y: win.innerHeight};
 		}
@@ -2454,6 +2639,7 @@
 })();
 
 //aliases
+Element.alias('setPosition', 'position'); //compatability
 
 Native.implement([Window, Document, Element], {
 
@@ -2491,11 +2677,20 @@
 
 });
 /*
-Script: Selectors.js
-	Adds advanced CSS Querying capabilities for targeting elements. Also includes pseudoselectors support.
+---
 
-License:
-	MIT-style license.
+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], {
@@ -2738,7 +2933,7 @@
 	},
 
 	byClass: function(self, klass){
-		return (self.className && self.className.contains(klass, ' '));
+		return (self.className && self.className.contains && self.className.contains(klass, ' '));
 	},
 
 	byPseudo: function(self, parser, argument, local){
@@ -2848,17 +3043,30 @@
 		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
 	},
 	
-	selected: function() {
+	selected: function(){
 		return this.selected;
+	},
+	
+	enabled: function(){
+		return (this.disabled === false);
 	}
 
 });
 /*
-Script: Domready.js
-	Contains the domready custom event.
+---
 
-License:
-	MIT-style license.
+script: DomReady.js
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires:
+- /Element.Event
+
+provides: [DomReady]
+
+...
 */
 
 Element.Events.domready = {
@@ -2877,13 +3085,15 @@
 		window.fireEvent('domready');
 		document.fireEvent('domready');
 	};
+	
+	window.addEvent('load', domready);
 
 	if (Browser.Engine.trident){
 		var temp = document.createElement('div');
 		(function(){
 			($try(function(){
-				temp.doScroll('left');
-				return $(temp).inject(document.body).set('html', 'temp').dispose();
+				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){
@@ -2891,24 +3101,38 @@
 			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
 		})();
 	} else {
-		window.addEvent('load', domready);
 		document.addEvent('DOMContentLoaded', domready);
 	}
 
 })();
 /*
-Script: JSON.js
-	JSON encoder and decoder.
+---
 
-License:
-	MIT-style license.
+script: JSON.js
 
-See Also:
-	<http://www.json.org/>
+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({
-
+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){
@@ -2920,7 +3144,7 @@
 			case 'string':
 				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
 			case 'array':
-				return '[' + String(obj.map(JSON.encode).filter($defined)) + ']';
+				return '[' + String(obj.map(JSON.encode).clean()) + ']';
 			case 'object': case 'hash':
 				var string = [];
 				Hash.each(obj, function(value, key){
@@ -2950,14 +3174,23 @@
 
 });
 /*
-Script: Cookie.js
-	Class for creating, loading, and saving browser Cookies.
+---
 
-License:
-	MIT-style license.
+script: Cookie.js
 
-Credits:
-	Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+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({
@@ -3015,14 +3248,24 @@
 	return new Cookie(key, options).dispose();
 };
 /*
-Script: Swiff.js
-	Wrapper for embedding SWF movies. Supports (and fixes) External Interface Communication.
+---
 
-License:
-	MIT-style license.
+script: Swiff.js
 
-Credits:
-	Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+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({
@@ -3055,7 +3298,7 @@
 		this.setOptions(options);
 		options = this.options;
 		var id = this.id = options.id || this.instance;
-		var container = $(options.container);
+		var container = document.id(options.container);
 
 		Swiff.CallBacks[this.instance] = {};
 
@@ -3092,13 +3335,13 @@
 	},
 
 	replaces: function(element){
-		element = $(element, true);
+		element = document.id(element, true);
 		element.parentNode.replaceChild(this.toElement(), element);
 		return this;
 	},
 
 	inject: function(element){
-		$(element, true).appendChild(this.toElement());
+		document.id(element, true).appendChild(this.toElement());
 		return this;
 	},
 
@@ -3115,11 +3358,22 @@
 	return eval(rs);
 };
 /*
-Script: Fx.js
-	Contains the basic animation logic to be extended by all other Fx Classes.
+---
 
-License:
-	MIT-style license.
+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({
@@ -3246,11 +3500,21 @@
 
 Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
 /*
-Script: Fx.CSS.js
-	Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+---
 
-License:
-	MIT-style license.
+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({
@@ -3378,11 +3642,20 @@
 
 });
 /*
-Script: Fx.Tween.js
-	Formerly Fx.Style, effect to transition any CSS property for an element.
+---
 
-License:
-	MIT-style license.
+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({
@@ -3390,7 +3663,7 @@
 	Extends: Fx.CSS,
 
 	initialize: function(element, options){
-		this.element = this.subject = $(element);
+		this.element = this.subject = document.id(element);
 		this.parent(options);
 	},
 
@@ -3473,11 +3746,20 @@
 
 });
 /*
-Script: Fx.Morph.js
-	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.
+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({
@@ -3485,7 +3767,7 @@
 	Extends: Fx.CSS,
 
 	initialize: function(element, options){
-		this.element = this.subject = $(element);
+		this.element = this.subject = document.id(element);
 		this.parent(options);
 	},
 
@@ -3542,14 +3824,23 @@
 
 });
 /*
-Script: Fx.Transitions.js
-	Contains a set of advanced transitions to be used with any of the Fx Classes.
+---
 
-License:
-	MIT-style license.
+script: Fx.Transitions.js
 
-Credits:
-	Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+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({
@@ -3638,11 +3929,24 @@
 	});
 });
 /*
-Script: Request.js
-	Powerful all purpose Request Class. Uses XMLHTTPRequest.
+---
 
-License:
-	MIT-style license.
+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({
@@ -3689,6 +3993,7 @@
 		$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);
@@ -3696,7 +4001,6 @@
 			this.response = {text: null, xml: null};
 			this.failure();
 		}
-		this.xhr.onreadystatechange = $empty;
 	},
 
 	isSuccess: function(){
@@ -3753,10 +4057,10 @@
 
 		var old = this.options;
 		options = $extend({data: old.data, url: old.url, method: old.method}, options);
-		var data = options.data, url = options.url, method = options.method;
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
 
 		switch ($type(data)){
-			case 'element': data = $(data).toQueryString(); break;
+			case 'element': data = document.id(data).toQueryString(); break;
 			case 'object': case 'hash': data = Hash.toQueryString(data);
 		}
 
@@ -3765,7 +4069,7 @@
 			data = (data) ? format + '&' + data : format;
 		}
 
-		if (this.options.emulation && ['put', 'delete'].contains(method)){
+		if (this.options.emulation && !['get', 'post'].contains(method)){
 			var _method = '_method=' + method;
 			data = (data) ? _method + '&' + data : _method;
 			method = 'post';
@@ -3776,18 +4080,19 @@
 			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
 		}
 
-		if(this.options.noCache) {
-			var noCache = "noCache=" + new Date().getTime();
+		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);
@@ -3824,18 +4129,59 @@
 ['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.toLowerCase()}));
+		return this.send($extend(params, {method: method}));
 	};
 });
 
 Request.implement(methods);
 
-})();/*
-Script: Request.HTML.js
-	Extends the basic Request Class with additional methods for interacting with HTML responses.
+})();
 
-License:
-	MIT-style license.
+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({
@@ -3865,7 +4211,7 @@
 				doc = new DOMParser().parseFromString(root, 'text/xml');
 			}
 			root = doc.getElementsByTagName('root')[0];
-			if (!root) return;
+			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);
@@ -3887,8 +4233,8 @@
 		response.elements = temp.getElements('*');
 
 		if (options.filter) response.tree = response.elements.filter(options.filter);
-		if (options.update) $(options.update).empty().set('html', response.html);
-		else if (options.append) $(options.append).adopt(temp.getChildren());
+		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);
@@ -3896,26 +4242,6 @@
 
 });
 
-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.Properties.load = {
 
 	set: function(options){
@@ -3936,12 +4262,6 @@
 
 Element.implement({
 
-	send: function(url){
-		var sender = this.get('send');
-		sender.send({data: this, url: url || sender.options.url});
-		return this;
-	},
-
 	load: function(){
 		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
 		return this;
@@ -3949,11 +4269,20 @@
 
 });
 /*
-Script: Request.JSON.js
-	Extends the basic Request Class with additional methods for sending and receiving JSON data.
+---
 
-License:
-	MIT-style license.
+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({
@@ -3975,21 +4304,465 @@
 	}
 
 });
+/*
+---
+
+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.2.1'
+	'version': '1.2.4.1'
 };/*
-Script: MooTools.Lang.js
-	Provides methods for localization.
+---
 
-	License:
-		MIT-style license.
+script: Log.js
 
-	Authors:
-		Aaron Newton
+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: {
@@ -4069,38 +4842,24 @@
 	});
 
 })();/*
-Script: Log.js
-	Provides basic logging functionality for plugins to implement.
+---
 
-	License:
-		MIT-style license.
+script: Class.Refactor.js
 
-	Authors:
-		Guillermo Rauch
-*/
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
 
-var Log = new Class({
-	
-	log: function(){
-		Log.logger.call(this, arguments);
-	}
-	
-});
+license: MIT-style license
 
-Log.logged = [];
+authors:
+- Aaron Newton
 
-Log.logger = function(){
-	if(window.console && console.log) console.log.apply(console, arguments);
-	else Log.logged.push(arguments);
-};/*
-Script: Class.Refactor.js
-	Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+requires:
+- core:1.2.4/Class
+- /MooTools.More
 
-	License:
-		MIT-style license.
+provides: [Class.refactor]
 
-	Authors:
-		Aaron Newton
+...
 */
 
 Class.refactor = function(original, refactors){
@@ -4119,14 +4878,24 @@
 	return original;
 
 };/*
-Script: Class.Binds.js
-	Automagically binds specified methods in a class to the instance of the class.
+---
 
-	License:
-		MIT-style license.
+script: Class.Binds.js
 
-	Authors:
-		Aaron Newton
+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){
@@ -4141,40 +4910,63 @@
 		}, this);
 		return initialize.apply(this, arguments);
 	};
-};/*
-Script: Class.Occlude.js
-	Prevents a class from being applied to a DOM element twice.
+};
+/*
+---
 
-	License:
-		MIT-style license.
+script: Class.Occlude.js
 
-	Authors:
-		Aaron Newton
+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 = $(element || this.element);
+		element = document.id(element || this.element);
 		var instance = element.retrieve(property || this.property);
-		if (instance && !$defined(this.occluded)){
-			this.occluded = instance;
-		} else {
-			this.occluded = false;
-			element.store(property || this.property, this);
-		}
+		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
-	Adds a method to inject pauses between chained events.
+---
 
-	License:
-		MIT-style license.
+script: Chain.Wait.js
 
-	Authors:
-		Aaron Newton
+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(){
@@ -4196,35 +4988,41 @@
 		});
 	}
 
-	try {
-		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;
-			}
-		});
-	} catch(e){}
+	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
-	Extends the Array native object to include useful methods to work with arrays.
+---
 
-	License:
-		MIT-style license.
+script: Array.Extras.js
 
-	Authors:
-		Christoph Pojer
+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({
 
@@ -4255,48 +5053,122 @@
 	}
 
 });/*
-Script: Date.js
-	Extends the Date native object to include methods useful in managing dates.
+---
 
-	License:
-		MIT-style license.
+script: Date.English.US.js
 
-	Authors:
-		Aaron Newton
-		Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
-		Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+description: Date messages for US English.
 
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.English.US]
+
+...
 */
 
-(function(){
+MooTools.lang.set('en-US', 'Date', {
 
-new Native({name: 'Date', initialize: Date, protect: true});
+	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',
 
-['now','parse','UTC'].each(function(method){
-	Native.genericize(Date, method, true);
-});
+	/* 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)];
+	},
 
-Date.Methods = {};
+	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'
 
-['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
-	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
-	'AMPM', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
-	Date.Methods[method.toLowerCase()] = method;
 });
+/*
+---
 
-$each({
+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'
-}, function(value, key){
-	Date.Methods[key] = value;
+};
+
+['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 zeroize = function(what, length){
-	return '0'.repeat(length - what.toString().length) + what;
+var pad = function(what, length){
+	return new Array(length - String(what).length + 1).join('0') + what;
 };
 
 Date.implement({
@@ -4314,10 +5186,10 @@
 		return this;
 	},
 
-	get: function(key){
-		key = key.toLowerCase();
+	get: function(prop){
+		prop = prop.toLowerCase();
 		var m = Date.Methods;
-		if (m[key]) return this['get' + m[key]]();
+		if (m[prop]) return this['get' + m[prop]]();
 		return null;
 	},
 
@@ -4326,91 +5198,61 @@
 	},
 
 	increment: function(interval, times){
-		return this.multiply(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.multiply(interval, times, false);
+		return this.increment(interval, -1 * $pick(times, 1));
 	},
 
-	multiply: function(interval, times, increment){
-		interval = interval || 'day';
-		times = $pick(times, 1);
-		increment = $pick(increment, true);
-		var multiplier = increment ? 1 : -1;
-		var month = this.format('%m').toInt() - 1;
-		var year = this.format('%Y').toInt();
-		var time = this.get('time');
-		var offset = 0;
-		switch (interval) {
-				case 'year':
-					times.times(function(val) {
-						if (Date.isLeapYear(year+val) && month > 1 && multiplier > 0) val++;
-						if (Date.isLeapYear(year+val) && month <= 1 && multiplier < 0) val--;
-						offset += Date.units.year(year+val);
-					});
-					break;
-				case 'month':
-					times.times(function(val){
-						if (multiplier < 0) val++;
-						var mo = month+(val * multiplier);
-						var year = year;
-						if (mo < 0) {
-							year--;
-							mo = 12+mo;
-						}
-						if (mo > 11 || mo < 0) {
-							year += (mo / 12).toInt() * multiplier;
-							mo = mo % 12;
-						}
-						offset += Date.units.month(mo, year);
-					});
-					break;
-				case 'day':
-					return this.set('date', this.get('date')+(multiplier*times));
-				default:
-					offset = Date.units[interval]() * times;
-					break;
-		}
-		this.set('time', time + (offset * multiplier));
-		return this;
-	},
-
 	isLeapYear: function(){
 		return Date.isLeapYear(this.get('year'));
 	},
 
 	clearTime: function(){
-		['hr', 'min', 'sec', 'ms'].each(function(t){
-			this.set(t, 0);
-		}, this);
-		return this;
+		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
 	},
 
-	diff: function(d, resolution){
-		resolution = resolution || 'day';
-		if ($type(d) == 'string') d = Date.parse(d);
-		switch (resolution){
-			case 'year':
-				return d.format('%Y').toInt() - this.format('%Y').toInt();
-				break;
-			case 'month':
-				var months = (d.format('%Y').toInt() - this.format('%Y').toInt())*12;
-				return months + d.format('%m').toInt() - this.format('%m').toInt();
-				break;
-			default:
-				var diff = d.get('time') - this.get('time');
-				if (diff < 0 && Date.units[resolution]() > (-1*(diff))) return 0;
-				else if (diff >= 0 && diff < Date.units[resolution]()) return 0;
-				return ((d.get('time') - this.get('time')) / Date.units[resolution]()).round();
-		}
-		return null;
+	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(){
-		var day = (new Date(this.get('year'), 0, 1)).get('date');
-		return Math.round((this.get('dayofyear') + (day > 3 ? day - 4 : day + 3)) / 7);
+		return (this.get('dayofyear') / 7).ceil();
 	},
+	
+	getOrdinal: function(day){
+		return Date.getMsg('ordinal', day || this.get('date'));
+	},
 
 	getTimezone: function(){
 		return this.toString()
@@ -4420,11 +5262,21 @@
 
 	getGMTOffset: function(){
 		var off = this.get('timezoneOffset');
-		return ((off > 0) ? '-' : ' + ')
-			+ zeroize(Math.floor(Math.abs(off) / 60), 2)
-			+ zeroize(off % 60, 2);
+		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;
@@ -4437,34 +5289,26 @@
 	format: function(f){
 		if (!this.isValid()) return 'invalid date';
 		f = f || '%x %X';
-		//replace short-hand with actual format
-		f = ({
-			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'
-		})[f.toLowerCase()] || f;
+		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
 		var d = this;
-		return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ\%])/g,
-			function($1, $2){
-				switch ($2){
+		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 zeroize(d.get('date'), 2);
-					case 'H': return zeroize(d.get('hr'), 2);
+					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 zeroize(d.get('dayofyear'), 3);
-					case 'm': return zeroize((d.get('mo') + 1), 2);
-					case 'M': return zeroize(d.get('min'), 2);
-					case 'p': return Date.getMsg(d.get('hr') < 12 ? 'AM' : 'PM');
-					case 'S': return zeroize(d.get('seconds'), 2);
-					case 'U': return zeroize(d.get('week'), 2);
-					case 'W': throw new Error('%W is not supported yet');
+					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'));
@@ -4472,37 +5316,60 @@
 					case 'Y': return d.get('year');
 					case 'T': return d.get('GMTOffset');
 					case 'Z': return d.get('Timezone');
-					case '%': return '%';
 				}
-				return $2;
+				return $1;
 			}
 		);
 	},
 
-	setAMPM: function(ampm){
-		ampm = ampm.toUpperCase();
-		if (this.format('%H').toInt() > 11 && ampm == 'AM')
-			return this.decrement('hour', 12);
-		else if (this.format('%H').toInt() < 12 && ampm == 'PM')
-			return this.increment('hour', 12);
-		return this;
+	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 daysInMonth = function(monthIndex, year){
-	if (Date.isLeapYear(year.toInt()) && monthIndex === 1) return 29;
-	return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex];
+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({
 
-$extend(Date, {
-
 	getMsg: function(key, args) {
 		return MooTools.lang.get('Date', key, args);
 	},
@@ -4514,87 +5381,58 @@
 		hour: $lambda(3600000),
 		day: $lambda(86400000),
 		week: $lambda(608400000),
-		month: function(monthIndex, year){
-			var d = new Date();
-			return daysInMonth($pick(monthIndex,d.format('%m').toInt()), $pick(year,d.format('%Y').toInt())) * 86400000;
+		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().format('%Y').toInt();
-			return Date.isLeapYear(year.toInt()) ? 31622400000 : 31536000000;
+			year = year || new Date().get('year');
+			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
 		}
 	},
 
-	isLeapYear: function(yr){
-		return new Date(yr , 1, 29).getDate() == 29;
+	daysInMonth: function(month, year){
+		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
 	},
 
-	fixY2K: function(d){
-		if (!isNaN(d)){
-			var newDate = new Date(d);
-			if (newDate.get('year') < 2000 && d.toString().indexOf(newDate.get('year')) < 0) newDate.increment('year', 100);
-			return newDate;
-		} else {
-			return d;
-		}
+	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;
-		Date.parsePatterns.each(function(pattern, i){
-			if (parsed) return;
-			var r = pattern.re.exec(from);
-			if (r) parsed = pattern.handler(r);
+		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){
-		var ret = -1;
-		switch ($type(day)){
-			case 'number':
-				ret = Date.getMsg('days')[day - 1] || false;
-				if (!ret) throw new Error('Invalid day index value must be between 1 and 7');
-				break;
-			case 'string':
-				var match = Date.getMsg('days').filter(function(name){
-					return this.test(name);
-				}, new RegExp('^' + day, 'i'));
-				if (!match.length) throw new Error('Invalid day string');
-				if (match.length > 1) throw new Error('Ambiguous day');
-				ret = match[0];
-		}
-		return (num) ? Date.getMsg('days').indexOf(ret) : ret;
+		return parseWord('day', day, num);
 	},
 
 	parseMonth: function(month, num){
-		var ret = -1;
-		switch ($type(month)){
-			case 'object':
-				ret = Date.getMsg('months')[month.get('mo')];
-				break;
-			case 'number':
-				ret = Date.getMsg('months')[month - 1] || false;
-				if (!ret) throw new Error('Invalid month index value must be between 1 and 12:' + index);
-				break;
-			case 'string':
-				var match = Date.getMsg('months').filter(function(name){
-					return this.test(name);
-				}, new RegExp('^' + month, 'i'));
-				if (!match.length) throw new Error('Invalid month string');
-				if (match.length > 1) throw new Error('Ambiguous month');
-				ret = match[0];
-		}
-		return (num) ? Date.getMsg('months').indexOf(ret) : ret;
+		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'));
+		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);
 	},
 
@@ -4602,279 +5440,294 @@
 		return Date.getMsg('dateOrder').indexOf(unit) + 1;
 	},
 
-	parsePatterns: [
-		{
-			//"1999-12-31"
-			re: /^(\d{4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})$/,
-			handler: function(bits){
-				return new Date(bits[1], bits[2] - 1, bits[3]);
-			}
-		},
-		{
-			//"1999-12-31 23:59:59"
-			re: /^(\d{4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})\s(\d{1,2}):(\d{1,2})(?:\:(\d{1,2}))?(\w{2})?$/,
-			handler: function(bits){
-				var d = new Date(bits[1], bits[2] - 1, bits[3]);
-				d.set('hr', bits[4]);
-				d.set('min', bits[5]);
-				d.set('sec', bits[6] || 0);
-				if (bits[7]) d.set('ampm', bits[7]);
-				return d;
-			}
-		},
-		{
-			//"12.31.08", "12-31-08", "12/31/08", "12.31.2008", "12-31-2008", "12/31/2008"
-			re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})$/,
-			handler: function(bits){
-				var d = new Date(bits[Date.orderIndex('year')],
-								 bits[Date.orderIndex('month')] - 1,
-								 bits[Date.orderIndex('date')]);
-				return Date.fixY2K(d);
-			}
-		},
-		//"12.31.08", "12-31-08", "12/31/08", "12.31.2008", "12-31-2008", "12/31/2008"
-		//above plus "10:45pm" ex: 12.31.08 10:45pm
-		{
-			re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2})[:\.](\d{1,2})(?:[\:\.](\d{1,2}))?(\w{2})?$/,
-			handler: function(bits){
-				var d = new Date(bits[Date.orderIndex('year')],
-								 bits[Date.orderIndex('month')] - 1,
-								 bits[Date.orderIndex('date')]);
-				d.set('hr', bits[4]);
-				d.set('min', bits[5]);
-				d.set('sec', bits[6] || 0);
-				if (bits[7]) d.set('ampm', bits[7]);
-				return Date.fixY2K(d);
-			}
-		}
-	]
+	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;
+	}
+
 });
 
-})();/*
-Script: Date.Extras.js
-	Extends the Date native object to include extra methods (on top of those in Date.js).
+var startCentury = 1900;
+var startYear = 70;
 
-	License:
-		MIT-style license.
+var regexOf = function(type){
+	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+		return name.substr(0, 3);
+	}).join('|') + ')[a-z]*');
+};
 
-	Authors:
-		Aaron Newton
+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})?/
+};
 
-['LastDayOfMonth', 'Ordinal'].each(function(method){
-	Date.Methods[method.toLowerCase()] = method;
-});
+keys.m = keys.I;
+keys.S = keys.M;
 
+var currentLanguage;
 
-Date.implement({
+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);
+	});
+};
 
-	timeDiffInWords: function(relative_to){
-		return Date.distanceOfTimeInWords(this, relative_to || new Date);
-	},
+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
 
-	getOrdinal: function(dayOfMonth){
-		return Date.getMsg('ordinal', dayOfMonth || this.get('date'));
-	},
+	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;
+		}
+	};
+};
 
-	getDayOfYear: function(){
-		return ((Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 1, 0, 0, 0)
-			- Date.UTC(this.getFullYear(), 0, 1, 0, 0, 0) ) / Date.units.day());
-	},
+var handle = function(key, value){
+	if (!value) return this;
 
-	getLastDayOfMonth: function(){
-		var ret = this.clone();
-		ret.setMonth(ret.getMonth() + 1, 0);
-		return ret.getDate();
+	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.alias('timeDiffInWords', 'timeAgoInWords');
+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
+);
 
-$extend(Date, {
+MooTools.lang.addEvent('langChange', function(language){
+	if (MooTools.lang.get('Date')) recompile(language);
+}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
 
-	distanceOfTimeInWords: function(fromTime, toTime){
-		return this.getTimePhrase(((toTime.getTime() - fromTime.getTime()) / 1000).toInt(), fromTime, toTime);
+})();/*
+---
+
+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);
 	},
 
-	getTimePhrase: function(delta, fromTime, toTime){
-		var getPhrase = function(){
-			var suffix;
-			if (delta >= 0){
-				suffix = 'Ago';
+	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 {
-				delta = delta * -1;
-				suffix = 'Until';
+				vals.unshift(delta + step);
 			}
-			if (delta < 60){
-				return Date.getMsg('lessThanMinute' + suffix, delta);
-			} else if (delta < 120){
-				return Date.getMsg('minute' + suffix, delta);
-			} else if (delta < (45 * 60)){
-				delta = (delta / 60).round();
-				return Date.getMsg('minutes' + suffix, delta);
-			} else if (delta < (90 * 60)){
-				return Date.getMsg('hour' + suffix, delta);
-			} else if (delta < (24 * 60 * 60)){
-				delta = (delta / 3600).round();
-				return Date.getMsg('hours' + suffix, delta);
-			} else if (delta < (48 * 60 * 60)){
-				return Date.getMsg('day' + suffix, delta);
-			} else {
-				delta = (delta / 86400).round();
-				return Date.getMsg('days' + suffix, delta);
-			}
-		};
-		return getPhrase().substitute({delta: delta});
+		}
+		
+		return vals.join(joiner || ':');
 	}
 
 });
 
+Date.alias('timeDiffInWords', 'timeAgoInWords');
 
-Date.parsePatterns.extend([
+Date.extend({
 
-	{
-		// yyyy-mm-ddTHH:MM:SS-0500 (ISO8601) i.e.2007-04-17T23:15:22Z
-		// inspired by: http://delete.me.uk/2005/03/iso8601.html
-		re: /^(\d{4})(?:-?(\d{2})(?:-?(\d{2})(?:[T ](\d{2})(?::?(\d{2})(?::?(\d{2})(?:\.(\d+))?)?)?(?:Z|(?:([-+])(\d{2})(?::?(\d{2}))?)?)?)?)?)?$/,
-		handler: function(bits){
-			var offset = 0;
-			var d = new Date(bits[1], 0, 1);
-			if (bits[3]) d.set('date', bits[3]);
-			if (bits[2]) d.set('mo', bits[2] - 1);
-			if (bits[4]) d.set('hr', bits[4]);
-			if (bits[5]) d.set('min', bits[5]);
-			if (bits[6]) d.set('sec', bits[6]);
-			if (bits[7]) d.set('ms', ('0.' + bits[7]).toInt() * 1000);
-			if (bits[9]){
-				offset = (bits[9].toInt() * 60) + bits[10].toInt();
-				offset *= ((bits[8] == '-') ? 1 : -1);
-			}
-			//offset -= d.getTimezoneOffset();
-			d.setTime((d * 1) + (offset * 60 * 1000).toInt());
-			return d;
-		}
+	distanceOfTimeInWords: function(from, to){
+		return Date.getTimePhrase(((to - from) / 1000).toInt());
 	},
 
-	{
-		//"today"
-		re: /^tod/i,
-		handler: function(){
-			return new Date();
+	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()});
+	}
 
-	{
-		//"tomorow"
-		re: /^tom/i,
-		handler: function(){
-			return new Date().increment();
-		}
-	},
+});
 
-	{
-		//"yesterday"
-		re: /^yes/i,
-		handler: function(){
-			return new Date().decrement();
-		}
-	},
 
-	{
-		//4th, 23rd
-		re: /^(\d{1,2})(st|nd|rd|th)?$/i,
-		handler: function(bits){
-			var d = new Date();
-			d.set('date', bits[1].toInt());
-			return d;
-		}
-	},
+Date.defineParsers(
 
 	{
-		//4th Jan, 23rd May
-		re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
+		// "today", "tomorrow", "yesterday"
+		re: /^(?:tod|tom|yes)/i,
 		handler: function(bits){
-			var d = new Date();
-			d.set('mo', Date.parseMonth(bits[2], true), bits[1].toInt());
-			return d;
+			var d = new Date().clearTime();
+			switch(bits[0]){
+				case 'tom': return d.increment();
+				case 'yes': return d.decrement();
+				default: 	return d;
+			}
 		}
 	},
 
 	{
-		//4th Jan 2000, 23rd May 2004
-		re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
+		// "next Wednesday", "last Thursday"
+		re: /^(next|last) ([a-z]+)$/i,
 		handler: function(bits){
-			var d = new Date();
-			d.set('mo', Date.parseMonth(bits[2], true), bits[1].toInt());
-			d.setYear(bits[3]);
-			return d;
-		}
-	},
-
-	{
-		//Jan 4th
-		re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
-		handler: function(bits){
-			var d = new Date();
-			d.set('mo', Date.parseMonth(bits[1], true), bits[2].toInt());
-			d.setYear(bits[3]);
-			return d;
-		}
-	},
-
-	{
-		//Jan 4th 2003
-		re: /^next (\w+)$/i,
-		handler: function(bits){
-			var d = new Date();
+			var d = new Date().clearTime();
 			var day = d.getDay();
-			var newDay = Date.parseDay(bits[1], true);
+			var newDay = Date.parseDay(bits[2], true);
 			var addDays = newDay - day;
-			if (newDay <= day){
-				addDays += 7;
-			}
-			d.set('date', d.getDate() + addDays);
-			return d;
+			if (newDay <= day) addDays += 7;
+			if (bits[1] == 'last') addDays -= 7;
+			return d.set('date', d.getDate() + addDays);
 		}
-	},
+	}
 
-	{
-		//4 May 08:12
-		re: /^\d+\s[a-zA-z]..\s\d.\:\d.$/,
-		handler: function(bits){
-			var d = new Date();
-			bits = bits[0].split(' ');
-			d.set('date', bits[0]);
-			var m;
-			Date.getMsg('months').each(function(mo, i){
-				if (new RegExp('^' + bits[1]).test(mo)) m = i;
-			});
-			d.set('mo', m);
-			d.set('hr', bits[2].split(':')[0]);
-			d.set('min', bits[2].split(':')[1]);
-			d.set('ms', 0);
-			return d;
-		}
-	},
+);
+/*
+---
 
-	{
-		re: /^last (\w+)$/i,
-		handler: function(bits){
-			return Date.parse('next ' + bits[0]).decrement('day', 7);
-		}
-	}
+script: Hash.Extras.js
 
-]);/*
-Script: Hash.Extras.js
-	Extends the Hash native object to include getFromPath which allows a path notation to child elements.
+description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
 
-	License:
-		MIT-style license.
+license: MIT-style license
 
-	Authors:
-		Aaron Newton
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Hash.base
+- /MooTools.More
+
+provides: [Hash.Extras]
+
+...
 */
 
 Hash.implement({
@@ -4906,21 +5759,31 @@
 	}
 
 });/*
-Script: String.Extras.js
-	Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+---
 
-	License:
-		MIT-style license.
+script: String.Extras.js
 
-	Authors:
-		Aaron Newton
-		Guillermo Rauch
+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 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'];
 
@@ -4935,6 +5798,13 @@
 	"\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(){
@@ -4951,17 +5821,20 @@
 
 	pad: function(length, str, dir){
 		if (this.length >= length) return this;
-		str = str || ' ';
-		var pad = str.repeat(length - this.length).substr(0, length - this.length);
+		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());
 	},
 
-	stripTags: function(){
-		return this.replace(/<\/?[^>]+>/gi, '');
+	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){
@@ -4973,15 +5846,27 @@
 });
 
 })();/*
-Script: String.QueryString.js
-	...
+---
 
-	License:
-		MIT-style license.
+script: String.QueryString.js
 
-	Authors:
-		Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
+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(){
@@ -5014,38 +5899,46 @@
 	}
 
 });/*
-Script: URI.js
-	Provides methods useful in managing the window location and uris.
+---
 
-	License:
-		MIT-style license.
+script: URI.js
 
-	Authors:
-		Sebastian Markbåge, Aaron Newton
+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
+		/*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 },
+	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;
-		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);
+		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){
@@ -5056,7 +5949,7 @@
 	},
 
 	merge: function(bits, base){
-		if (!bits.scheme && !base.scheme) return false;
+		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
 		if (base){
 			this.parts.every(function(part){
 				if (bits[part]) return false;
@@ -5095,6 +5988,8 @@
 			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;
 		}
@@ -5106,7 +6001,7 @@
 			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
 			case 'data' : return this.getData();
 		}
-		return this.parsed[part] || undefined;
+		return this.parsed[part] || '';
 	},
 
 	go: function(){
@@ -5125,9 +6020,9 @@
 	},
 
 	setData: function(values, merge, part){
-		if ($type(arguments[0]) == 'string'){ 
-			values = this.getData(); 
-			values[arguments[0]] = arguments[1]; 
+		if (typeof values == 'string'){
+			values = this.getData();
+			values[arguments[0]] = arguments[1];
 		} else if (merge) {
 			values = $merge(this.getData(), values);
 		}
@@ -5140,34 +6035,44 @@
 
 });
 
-['toString', 'valueOf'].each(function(method){
-	URI.prototype[method] = function(){
-		return this.get('value');
-	};
-});
+URI.prototype.toString = URI.prototype.valueOf = function(){
+	return this.get('value');
+};
 
-
 URI.regs = {
 	endSlash: /\/$/,
 	scheme: /^(\w+):/,
 	directoryDot: /\.\/|\.$/
 };
 
-URI.base = new URI($$('base[href]').getLast(), { base: document.location });
+URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
 
 String.implement({
 
-	toURI: function(options){ return new URI(this, options); }
+	toURI: function(options){
+		return new URI(this, options);
+	}
 
 });/*
-Script: URI.Relative.js
-	Extends the URI class to add methods for computing relative and absolute urls.
+---
 
-	License:
-		MIT-style license.
+script: URI.Relative.js
 
-	Authors:
-		Sebastian Markbåge
+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, {
@@ -5203,16 +6108,26 @@
 	}
 
 });/*
-Script: Element.Forms.js
-	Extends the Element native object to include methods useful in managing inputs.
+---
 
-	License:
-		MIT-style license.
+script: Element.Forms.js
 
-	Authors:
-		Aaron Newton
+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(){
@@ -5224,8 +6139,8 @@
 	},
 
 	getSelectedText: function(){
-		if (document.selection && document.selection.createRange) return document.selection.createRange().text;
-		return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		return document.selection.createRange().text;
 	},
 
 	getSelectedRange: function() {
@@ -5239,9 +6154,10 @@
 			pos.end = pos.start + range.text.length;
 		} else {
 			var value = this.get('value');
-			var offset = value.length - value.match(/[\n\r]*$/)[0].length;
+			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;
@@ -5268,7 +6184,10 @@
 	},
 
 	selectRange: function(start, end){
-		if (this.createTextRange){
+		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;
@@ -5277,9 +6196,6 @@
 			range.moveEnd('character', start + diff);
 			range.moveStart('character', start);
 			range.select();
-		} else {
-			this.focus();
-			this.setSelectionRange(start, end);
 		}
 		return this;
 	},
@@ -5316,21 +6232,170 @@
 	}
 
 });/*
-Script: Element.Measure.js
-	Extends the Element native object to include methods useful in measuring dimensions.
+---
 
-	Element.measure / .expose methods by Daniel Steigerwald
-	License: MIT-style license.
-	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz
+script: Elements.From.js
 
-	License:
-		MIT-style license.
+description: Returns a collection of elements from a string of html.
 
-	Authors:
-		Aaron Newton
+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){
@@ -5339,8 +6404,8 @@
 		};
 		if (vis(this)) return fn.apply(this);
 		var parent = this.getParent(),
-			toMeasure = [], 
-			restorers = [];
+			restorers = [],
+			toMeasure = []; 
 		while (!vis(parent) && parent != document.body) {
 			toMeasure.push(parent.expose());
 			parent = parent.getParent();
@@ -5356,12 +6421,15 @@
 
 	expose: function(){
 		if (this.getStyle('display') != 'none') return $empty;
-		var before = this.getStyles('display', 'position', 'visibility');
-		return this.setStyles({
+		var before = this.style.cssText;
+		this.setStyles({
 			display: 'block',
 			position: 'absolute',
 			visibility: 'hidden'
-		}).setStyles.pass(before, this);
+		});
+		return function(){
+			this.style.cssText = before;
+		}.bind(this);
 	},
 
 	getDimensions: function(options){
@@ -5370,14 +6438,17 @@
 		var getSize = function(el, options){
 			return (options.computeSize)?el.getComputedSize(options):el.getSize();
 		};
-		if (this.getStyle('display') == 'none'){
+		var parent = this.getParent('body');
+		if (parent && this.getStyle('display') == 'none'){
 			dim = this.measure(function(){
 				return getSize(this, options);
 			});
-		} else {
+		} 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});
 	},
@@ -5416,8 +6487,7 @@
 		var subtracted = [];
 		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
 			var capitalized = key.capitalize();
-			size['total' + capitalized] = 0;
-			size['computed' + capitalized] = 0;
+			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.
@@ -5450,14 +6520,26 @@
 	}
 
 });/*
-Script: Element.Pin.js
-	Extends the Element native object to include the pin method useful for fixed positioning for elements.
+---
 
-	License:
-		MIT-style license.
+script: Element.Pin.js
 
-	Authors:
-		Aaron Newton
+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(){
@@ -5477,13 +6559,14 @@
 		pin: function(enable){
 			if (this.getStyle('display') == 'none') return null;
 			
-			var p;
+			var p,
+					scroll = window.getScroll();
 			if (enable !== false){
 				p = this.getPosition();
 				if (!this.retrieve('pinned')){
 					var pos = {
-						top: p.y - window.getScroll().y,
-						left: p.x - window.getScroll().x
+						top: p.y - scroll.y,
+						left: p.x - scroll.x
 					};
 					if (supportsPositionFixed){
 						this.setStyle('position', 'fixed').setStyles(pos);
@@ -5493,12 +6576,13 @@
 							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() + window.getScroll().y,
-									left: pos.left.toInt() + window.getScroll().x
+									top: pos.top.toInt() + scroll.y,
+									left: pos.left.toInt() + scroll.x
 								});
 						}).bind(this));
 						window.addEvent('scroll', this.retrieve('scrollFixer'));
@@ -5508,16 +6592,16 @@
 			} else {
 				var op;
 				if (!Browser.Engine.trident){
-					if (this.getParent().getComputedStyle('position') != 'static') op = this.getParent();
-					else op = this.getParent().getOffsetParent();
+					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 + window.getScroll().y,
-						left: p.x + window.getScroll().x
+						top: p.y + scroll.y,
+						left: p.x + scroll.x
 					};
 				} else {
 					this.store('pinnedByJS', false);
@@ -5527,13 +6611,13 @@
 						left: p.x
 					};
 				}
-				this.setStyles($merge(reposition, {position: 'absolute'}));
+				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
 			}
-			return this.addClass('isPinned');
+			return this;
 		},
 
 		unpin: function(){
-			return this.pin(false).removeClass('isPinned');
+			return this.pin(false);
 		},
 
 		togglepin: function(){
@@ -5543,14 +6627,24 @@
 	});
 
 })();/*
-Script: Element.Position.js
-	Extends the Element native object to include methods useful positioning elements relative to others.
+---
 
-	License:
-		MIT-style license.
+script: Element.Position.js
 
-	Authors:
-		Aaron Newton
+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(){
@@ -5564,6 +6658,8 @@
 		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
@@ -5574,22 +6670,23 @@
 			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};
-		var parentPositioned = false;
+		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 $(this.getOffsetParent());
+			return document.id(this.getOffsetParent());
 		});
 		if (offsetParent && offsetParent != this.getDocument().body){
 			parentOffset = offsetParent.measure(function(){
 				return this.getPosition();
 			});
-			parentPositioned = true;
+			parentPositioned = offsetParent != document.id(options.relativeTo);
 			options.offset.x = options.offset.x - parentOffset.x;
 			options.offset.y = options.offset.y - parentOffset.y;
 		}
@@ -5615,26 +6712,19 @@
 		}
 
 		this.setStyle('position', 'absolute');
-		var rel = $(options.relativeTo) || document.body;
-		var calc = rel == document.body ? window.getScroll() : rel.getPosition();
-		var top = calc.y;
-		var left = calc.x;
+		var rel = document.id(options.relativeTo) || document.body,
+				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
+				top = calc.y, left = calc.x;
 
-		if (Browser.Engine.trident){
-			var scrolls = rel.getScrolls();
-			top += scrolls.y;
-			left += scrolls.x;
-		}
+		var scrolls = rel.getScrolls();
+		top += scrolls.y;
+		left += scrolls.x;
 
 		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
-		if (options.ignoreMargins){
-			options.offset.x = options.offset.x - dim['margin-left'];
-			options.offset.y = options.offset.y - dim['margin-top'];
-		}
-		var pos = {};
-		var prefY = options.offset.y;
-		var prefX = options.offset.x;
-		var winSize = window.getSize();
+		var pos = {},
+				prefY = options.offset.y,
+				prefX = options.offset.x,
+				winSize = window.getSize();
 		switch(options.position.x){
 			case 'left':
 				pos.x = left + prefX;
@@ -5657,7 +6747,6 @@
 				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
 				break;
 		}
-
 		if (options.edge){
 			var edgeOffset = {};
 
@@ -5669,7 +6758,7 @@
 					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
 					break;
 				default: //center
-					edgeOffset.x = -(dim.x/2);
+					edgeOffset.x = -(dim.totalWidth/2);
 					break;
 			}
 			switch(options.edge.y){
@@ -5680,22 +6769,47 @@
 					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
 					break;
 				default: //center
-					edgeOffset.y = -(dim.y/2);
+					edgeOffset.y = -(dim.totalHeight/2);
 					break;
 			}
-			pos.x = pos.x + edgeOffset.x;
-			pos.y = pos.y + edgeOffset.y;
+			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 = pos.top.toInt() + winScroll.y;
-			pos.left = pos.left.toInt() + winScroll.x;
+			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;
@@ -5704,15 +6818,24 @@
 });
 
 })();/*
-Script: Element.Shortcuts.js
-	Extends the Element native object to include some shortcut methods.
+---
 
-	License:
-		MIT-style license.
+script: Element.Shortcuts.js
 
-	Authors:
-		Aaron Newton
+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({
@@ -5721,6 +6844,12 @@
 		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']();
 	},
@@ -5728,10 +6857,10 @@
 	hide: function(){
 		var d;
 		try {
-			//IE fails here if the element is not in the dom
-			if ('none' != this.getStyle('display')) d = this.getStyle('display');
+			// 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');
 	},
 
@@ -5745,15 +6874,1105 @@
 
 });
 /*
-Script: FormValidator.js
-	A css-class based form validation system.
+---
 
-	License:
-		MIT-style license.
+script: IframeShim.js
 
-	Authors:
-		Aaron Newton
+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],
@@ -5769,18 +7988,18 @@
 	},
 
 	test: function(field, props){
-		if ($(field)) return this.options.test($(field), props||this.getProps(field));
+		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($(field), props||this.getProps(field));
+		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
 		return err;
 	},
 
 	getProps: function(field){
-		if (!$(field)) return {};
+		if (!document.id(field)) return {};
 		return field.get('validatorProps');
 	}
 
@@ -5825,7 +8044,7 @@
 
 };
 
-var FormValidator = new Class({
+Form.Validator = new Class({
 
 	Implements:[Options, Events],
 
@@ -5838,6 +8057,7 @@
 		onElementFail: $empty(field, validatorsFailed) */
 		fieldSelectors: 'input, select, textarea',
 		ignoreHidden: true,
+		ignoreDisabled: true,
 		useTitles: false,
 		evaluateOnSubmit: true,
 		evaluateFieldsOnBlur: true,
@@ -5845,21 +8065,21 @@
 		serial: true,
 		stopOnFailure: true,
 		warningPrefix: function(){
-			return FormValidator.getMsg('warningPrefix') || 'Warning: ';
+			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
 		},
 		errorPrefix: function(){
-			return FormValidator.getMsg('errorPrefix') || 'Error: ';
+			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
 		}
 	},
 
 	initialize: function(form, options){
 		this.setOptions(options);
-		this.element = $(form);
+		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.watchFields(this.getFields());
+		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
 	},
 
 	toElement: function(){
@@ -5872,12 +8092,18 @@
 
 	watchFields: function(fields){
 		fields.each(function(el){
-				el.addEvent('blur', this.validateField.pass([el, false], this));
+			if (this.options.evaluateFieldsOnBlur)
+				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
 			if (this.options.evaluateFieldsOnChange)
-				el.addEvent('change', this.validateField.pass([el, true], this));
+				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();
@@ -5899,7 +8125,7 @@
 
 	validateField: function(field, force){
 		if (this.paused) return true;
-		field = $(field);
+		field = document.id(field);
 		var passed = !field.hasClass('validation-failed');
 		var failed, warned;
 		if (this.options.serial && !force){
@@ -5942,28 +8168,20 @@
 	},
 
 	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);
-		field = $(field);
 		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 && this.isVisible(field)) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
 		if (warn) return true;
 		return isValid;
 	},
 
-	isVisible : function(field){
-		if (!this.options.ignoreHidden) return true;
-		while(field != document.body){
-			if ($(field).getStyle('display') == 'none') return false;
-			field = field.getParent();
-		}
-		return true;
-	},
-
 	resetField: function(field){
-		field = $(field);
+		field = document.id(field);
 		if (field){
 			field.className.split(' ').each(function(className){
 				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
@@ -5986,7 +8204,7 @@
 	},
 
 	ignoreField: function(field, warn){
-		field = $(field);
+		field = document.id(field);
 		if (field){
 			this.enforceField(field);
 			if (warn) field.addClass('warnOnly');
@@ -5996,24 +8214,24 @@
 	},
 
 	enforceField: function(field){
-		field = $(field);
+		field = document.id(field);
 		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
 		return this;
 	}
 
 });
 
-FormValidator.getMsg = function(key){
-	return MooTools.lang.get('FormValidator', key);
+Form.Validator.getMsg = function(key){
+	return MooTools.lang.get('Form.Validator', key);
 };
 
-FormValidator.adders = {
+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 FormValidator and the FormValidator namespace)
+		//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){
@@ -6035,11 +8253,11 @@
 
 };
 
-$extend(FormValidator, FormValidator.adders);
+$extend(Form.Validator, Form.Validator.adders);
 
-FormValidator.implement(FormValidator.adders);
+Form.Validator.implement(Form.Validator.adders);
 
-FormValidator.add('IsEmpty', {
+Form.Validator.add('IsEmpty', {
 
 	errorMsg: false,
 	test: function(element){
@@ -6051,21 +8269,21 @@
 
 });
 
-FormValidator.addAllThese([
+Form.Validator.addAllThese([
 
 	['required', {
 		errorMsg: function(){
-			return FormValidator.getMsg('required');
+			return Form.Validator.getMsg('required');
 		},
 		test: function(element){
-			return !FormValidator.getValidator('IsEmpty').test(element);
+			return !Form.Validator.getValidator('IsEmpty').test(element);
 		}
 	}],
 
 	['minLength', {
 		errorMsg: function(element, props){
 			if ($type(props.minLength))
-				return FormValidator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
+				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
 			else return '';
 		},
 		test: function(element, props){
@@ -6078,7 +8296,7 @@
 		errorMsg: function(element, props){
 			//props is {maxLength:10}
 			if ($type(props.maxLength))
-				return FormValidator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
+				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
 			else return '';
 		},
 		test: function(element, props){
@@ -6088,38 +8306,38 @@
 	}],
 
 	['validate-integer', {
-		errorMsg: FormValidator.getMsg.pass('integer'),
+		errorMsg: Form.Validator.getMsg.pass('integer'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) || (/^-?[1-9]\d*$/).test(element.get('value'));
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
 		}
 	}],
 
 	['validate-numeric', {
-		errorMsg: FormValidator.getMsg.pass('numeric'),
+		errorMsg: Form.Validator.getMsg.pass('numeric'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) ||
+			return Form.Validator.getValidator('IsEmpty').test(element) ||
 				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
 		}
 	}],
 
 	['validate-digits', {
-		errorMsg: FormValidator.getMsg.pass('digits'),
+		errorMsg: Form.Validator.getMsg.pass('digits'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
 		}
 	}],
 
 	['validate-alpha', {
-		errorMsg: FormValidator.getMsg.pass('alpha'),
+		errorMsg: Form.Validator.getMsg.pass('alpha'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
 		}
 	}],
 
 	['validate-alphanum', {
-		errorMsg: FormValidator.getMsg.pass('alphanum'),
+		errorMsg: Form.Validator.getMsg.pass('alphanum'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
 		}
 	}],
 
@@ -6127,13 +8345,13 @@
 		errorMsg: function(element, props){
 			if (Date.parse){
 				var format = props.dateFormat || '%x';
-				return FormValidator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
 			} else {
-				return FormValidator.getMsg('dateInFormatMDY');
+				return Form.Validator.getMsg('dateInFormatMDY');
 			}
 		},
 		test: function(element, props){
-			if (FormValidator.getValidator('IsEmpty').test(element)) return true;
+			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
 			var d;
 			if (Date.parse){
 				var format = props.dateFormat || '%x';
@@ -6153,34 +8371,34 @@
 	}],
 
 	['validate-email', {
-		errorMsg: FormValidator.getMsg.pass('email'),
+		errorMsg: Form.Validator.getMsg.pass('email'),
 		test: function(element){
-			return FormValidator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
+			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: FormValidator.getMsg.pass('url'),
+		errorMsg: Form.Validator.getMsg.pass('url'),
 		test: function(element){
-			return FormValidator.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'));
+			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: FormValidator.getMsg.pass('currencyDollar'),
+		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
 		test: function(element){
 			// [$]1[##][,###]+[.##]
 			// [$]1###+[.##]
 			// [$]0.##
 			// [$].##
-			return FormValidator.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'));
+			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: FormValidator.getMsg.pass('oneRequired'),
+		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
 		test: function(element, props){
-			var p = $(props['validate-one-required']) || element.parentNode;
+			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');
@@ -6201,7 +8419,7 @@
 	get: function(options){
 		if (options || !this.retrieve('validator')){
 			if (options || !this.retrieve('validator:options')) this.set('validator', options);
-			this.store('validator', new FormValidator(this, this.retrieve('validator:options')));
+			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
 		}
 		return this.retrieve('validator');
 	}
@@ -6215,24 +8433,36 @@
 		return this.get('validator', options).validate();
 	}
 
-});/*
-Script: FormValidator.Inline.js
-	Extends FormValidator to add inline messages.
+});
+//legacy
+var FormValidator = Form.Validator;/*
+---
 
-	License:
-		MIT-style license.
+script: Form.Validator.Inline.js
 
-	Authors:
-		Aaron Newton
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
 */
 
-FormValidator.Inline = new Class({
+Form.Validator.Inline = new Class({
 
-	Extends: FormValidator,
+	Extends: Form.Validator,
 
 	options: {
 		scrollToErrorsOnSubmit: true,
 		scrollFxOptions: {
+			transition: 'quad:out',
 			offset: {
 				y: -20
 			}
@@ -6256,11 +8486,11 @@
 
 	makeAdvice: function(className, field, error, warn){
 		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
-				errorMsg += (this.options.useTitles) ? field.title || error:error;
+			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).set('html', errorMsg).replaces(advice);
+			advice = advice.clone(true, true).set('html', errorMsg).replaces(advice);
 		} else {
 			advice = new Element('div', {
 				html: errorMsg,
@@ -6303,7 +8533,7 @@
 	},
 
 	resetField: function(field){
-		field = $(field);
+		field = document.id(field);
 		if (!field) return this;
 		this.parent(field);
 		field.className.split(' ').each(function(className){
@@ -6338,34 +8568,25 @@
 		//Check for error position prop
 		var props = field.get('validatorProps');
 		//Build advice
-		if (!props.msgPos || !$(props.msgPos)){
+		if (!props.msgPos || !document.id(props.msgPos)){
 			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
-			else advice.inject($(field), 'after');
+			else advice.inject(document.id(field), 'after');
 		} else {
-			$(props.msgPos).grab(advice);
+			document.id(props.msgPos).grab(advice);
 		}
 	},
 
-	validate: function(field, force){
+	validateField: function(field, force){
 		var result = this.parent(field, force);
 		if (this.options.scrollToErrorsOnSubmit && !result){
-			var failed = $(this).getElement('.validation-failed');
-			var par = $(this).getParent();
-			var isScrolled = function(p){
-				return p.getScrollSize().y != p.getSize().y;
-			};
-			var scrolls;
-			while (par != document.body && !isScrolled(par)){
+			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, {
-					transition: 'quad:out',
-					offset: {
-						y: -20
-					}
-				});
+				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
 				par.store('fvScroller', fx);
 			}
 			if (failed){
@@ -6373,27 +8594,37 @@
 				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
 			}
 		}
+		return result;
 	}
 
 });
 /*
-Script: FormValidator.Extras.js
-	Additional validators for the FormValidator class.
+---
 
-	License:
-		MIT-style license.
+script: Form.Validator.Extras.js
 
-	Authors:
-		Aaron Newton
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
 */
-FormValidator.addAllThese([
+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 || $(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
 					fv.enforceField(item);
 				});
 			}
@@ -6406,7 +8637,7 @@
 			if (element.checked){
 				var fv = element.getParent('form').retrieve('validator');
 				if (!fv) return true;
-				(props.toIgnore || $(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
 					fv.ignoreField(item);
 					fv.resetField(item);
 				});
@@ -6417,7 +8648,7 @@
 
 	['validate-nospace', {
 		errorMsg: function(){
-			return FormValidator.getMsg('noSpace');
+			return Form.Validator.getMsg('noSpace');
 		},
 		test: function(element, props){
 			return !element.get('value').test(/\s/);
@@ -6428,7 +8659,7 @@
 		test: function(element, props){
 			var fv = element.getParent('form').retrieve('validator');
 			if (!fv) return true;
-			var eleArr = props.toToggle || $(props.toToggleChildrenOf).getElements('input, select, textarea');
+			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
 			if (!element.checked){
 				eleArr.each(function(item){
 					fv.ignoreField(item);
@@ -6445,10 +8676,10 @@
 
 	['validate-reqchk-bynode', {
 		errorMsg: function(){
-			return FormValidator.getMsg('reqChkByNode');
+			return Form.Validator.getMsg('reqChkByNode');
 		},
 		test: function(element, props){
-			return ($(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
 				return item.checked;
 			});
 		}
@@ -6456,7 +8687,7 @@
 
 	['validate-required-check', {
 		errorMsg: function(element, props){
-			return props.useTitle ? element.get('title') : FormValidator.getMsg('requiredChk');
+			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
 		},
 		test: function(element, props){
 			return !!element.checked;
@@ -6465,7 +8696,7 @@
 
 	['validate-reqchk-byname', {
 		errorMsg: function(element, props){
-			return FormValidator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
 		},
 		test: function(element, props){
 			var grpName = props.groupName || element.get('name');
@@ -6480,23 +8711,23 @@
 
 	['validate-match', {
 		errorMsg: function(element, props){
-			return FormValidator.getMsg('match').substitute({matchName: props.matchName || $(props.matchInput).get('name')});
+			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 = $(props.matchInput) && $(props.matchInput).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 FormValidator.getMsg('afterDate').substitute({
-				label: props.afterLabel || (props.afterElement ? FormValidator.getMsg('startDate') : FormValidator.getMsg('currentDate'))
+			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 = $(props.afterElement) ? Date.parse($(props.afterElement).get('value')) : new Date();
+			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;
 		}
@@ -6504,20 +8735,20 @@
 
 	['validate-before-date', {
 		errorMsg: function(element, props){
-			return FormValidator.getMsg('beforeDate').substitute({
-				label: props.beforeLabel || (props.beforeElement ? FormValidator.getMsg('endDate') : FormValidator.getMsg('currentDate'))
+			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 = $(props.beforeElement) ? Date.parse($(props.beforeElement).get('value')) : new Date();
+			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 FormValidator.getMsg('required');
+			return Form.Validator.getMsg('required');
 		},
 		test: function(element, props){
 			return element.get('value') != props.emptyValue;
@@ -6526,39 +8757,106 @@
 
 	['validate-same-month', {
 		errorMsg: function(element, props){
-			var startMo = $(props.sameMonthAs) && $(props.sameMonthAs).get('value');
+			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
 			var eleVal = element.get('value');
-			if (eleVal != '') return FormValidator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
 		},
 		test: function(element, props){
 			var d1 = Date.parse(element.get('value'));
-			var d2 = Date.parse($(props.sameMonthAs) && $(props.sameMonthAs).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
-	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.
+script: OverText.js
 
-	Authors:
-		Aaron Newton
+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'],
+	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',
@@ -6568,13 +8866,14 @@
 			}
 		},
 		poll: false,
-		pollInterval: 250
+		pollInterval: 250,
+		wrap: false
 	},
 
 	property: 'OverText',
 
 	initialize: function(element, options){
-		this.element = $(element);
+		this.element = document.id(element);
 		if (this.occlude()) return this.occluded;
 		this.setOptions(options);
 		this.attach(this.element);
@@ -6590,27 +8889,50 @@
 	attach: function(){
 		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
 		if (!val) return;
-		this.text = new Element('div', {
-			'class': 'overTxtDiv',
+		this.text = new Element(this.options.element, {
+			'class': 'overTxtLabel',
 			styles: {
 				lineHeight: 'normal',
-				position: 'absolute'
+				position: 'absolute',
+				cursor: 'text'
 			},
 			html: val,
 			events: {
-				click: this.hide.pass(true, this)
+				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();
+		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();
@@ -6622,7 +8944,7 @@
 		//resumeon blur
 		if (this.poller && !stop) return this;
 		var test = function(){
-			if (!this.pollingPaused) this.assert();
+			if (!this.pollingPaused) this.assert(true);
 		}.bind(this);
 		if (stop) $clear(this.poller);
 		else this.poller = test.periodical(this.options.pollInterval, this);
@@ -6635,24 +8957,25 @@
 	},
 
 	focus: function(){
-		if (!this.text.isDisplayed() || this.element.get('disabled')) return;
+		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
 		this.hide();
 	},
 
-	hide: function(){
-		if (this.text.isDisplayed() && !this.element.get('disabled')){
+	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 {
-				this.element.fireEvent('focus').focus();
+				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.isDisplayed()){
+		if (this.text && !this.text.isDisplayed()){
 			this.text.show();
 			this.reposition();
 			this.fireEvent('textShow', [this.text, this.element]);
@@ -6661,8 +8984,8 @@
 		return this;
 	},
 
-	assert: function(){
-		this[this.test() ? 'show' : 'hide']();
+	assert: function(suppressFocus){
+		this[this.test() ? 'show' : 'hide'](suppressFocus);
 	},
 
 	test: function(){
@@ -6671,11 +8994,9 @@
 	},
 
 	reposition: function(){
-		try {
-			this.assert();
-			if (!this.element.getParent() || !this.element.offsetHeight) return this.hide();
-			if (this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
-		} catch(e){	}
+		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;
 	}
 
@@ -6683,28 +9004,62 @@
 
 OverText.instances = [];
 
-OverText.update = function(){
+$extend(OverText, {
 
-	return OverText.instances.map(function(ot){
-		if (ot.element && ot.text) return ot.reposition();
-		return null; //the input or the text was destroyed
-	});
+	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, .overTxtDiv' : false
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
 	});
 }/*
-Script: Fx.Elements.js
-	Effect to change any number of CSS properties of any number of Elements.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Elements.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -6748,14 +9103,24 @@
 	}
 
 });/*
-Script: Fx.Accordion.js
-	An Fx.Elements extension which allows you to easily create accordion type controls.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Accordion.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -6764,26 +9129,28 @@
 
 	options: {/*
 		onActive: $empty(toggler, section),
-		onBackground: $empty(toggler, section),*/
+		onBackground: $empty(toggler, section),
+		fixedHeight: false,
+		fixedWidth: false,
+		*/
 		display: 0,
 		show: false,
 		height: true,
 		width: false,
 		opacity: true,
-		fixedHeight: false,
-		fixedWidth: false,
-		wait: false,
 		alwaysHide: false,
 		trigger: 'click',
-		initialDisplayFx: true
+		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 = $(params.container);
+		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;
@@ -6806,16 +9173,19 @@
 			}
 		}, 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 = $(toggler);
-		element = $(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);
-		toggler.addEvent(this.options.trigger, this.display.bind(this, idx));
+		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;
@@ -6828,31 +9198,63 @@
 		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));
+			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
-	Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Move.js
 
-	Authors:
-		Aaron Newton
+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({
@@ -6899,246 +9301,26 @@
 
 });
 /*
-Script: Fx.Reveal.js
-	Defines Fx.Reveal, a class that shows and hides elements with a transition.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Scroll.js
 
-	Authors:
-		Aaron Newton
+description: Effect to smoothly scroll any element, including the window.
 
-*/
+license: MIT-style license
 
-Fx.Reveal = new Class({
+authors:
+- Valerio Proietti
 
-	Extends: Fx.Morph,
+requires:
+- core:1.2.4/Fx
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
 
-	options: {/*	  
-		onShow: $empty(thisElemeng),
-		onHide: $empty(thisElemeng),
-		onComplete: $empty(thisElemeng),
-		heightOverride: null,
-		widthOverride: null, */
-		styles: ['padding', 'border', 'margin'],
-		transitionOpacity: !Browser.Engine.trident4,
-		mode: 'vertical',
-		display: 'block',
-		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
-	},
+provides: [Fx.Scroll]
 
-	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 = false;
-					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;
-	}
-
-});
-
-Element.Properties.reveal = {
-
-	set: function(options){
-		var reveal = this.retrieve('reveal');
-		if (reveal) reveal.cancel();
-		return this.eliminate('reveal').store('reveal:options', $extend({link: 'cancel'}, 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: Fx.Scroll.js
-	Effect to smoothly scroll any element, including the window.
-
-	License:
-		MIT-style license.
-
-	Authors:
-		Valerio Proietti
+...
 */
 
 Fx.Scroll = new Class({
@@ -7151,11 +9333,11 @@
 	},
 
 	initialize: function(element, options){
-		this.element = this.subject = $(element);
+		this.element = this.subject = document.id(element);
 		this.parent(options);
 		var cancel = this.cancel.bind(this, false);
 
-		if ($type(this.element) != 'element') this.element = $(this.element.getDocument().body);
+		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
 
 		var stopper = this.element;
 
@@ -7171,6 +9353,7 @@
 
 	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]);
 	},
 
@@ -7182,11 +9365,12 @@
 
 	start: function(x, y){
 		if (!this.check(x, y)) return this;
-		var offsetSize = this.element.getSize(), scrollSize = this.element.getScrollSize();
-		var scroll = this.element.getScroll(), values = {x: x, y: y};
+		var scrollSize = this.element.getScrollSize(),
+			scroll = this.element.getScroll(), 
+			values = {x: x, y: y};
 		for (var z in values){
-			var max = scrollSize[z] - offsetSize[z];
-			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z].limit(0, max) : max;
+			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];
 		}
@@ -7210,20 +9394,78 @@
 	},
 
 	toElement: function(el){
-		var position = $(el).getPosition(this.element);
+		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
-	Effect to slide an element in and out of view.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Slide.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -7239,11 +9481,11 @@
 			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 = $(element);
+		this.element = this.subject = document.id(element);
 		this.parent(options);
 		var wrapper = this.element.retrieve('wrapper');
 		this.wrapper = wrapper || new Element('div', {
-			styles: $extend(this.element.getStyles('margin', 'position'), {overflow: 'hidden'})
+			styles: this.element.getStyles('margin', 'position', 'overflow')
 		}).wraps(this.element);
 		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
 		this.now = [];
@@ -7356,14 +9598,24 @@
 
 });
 /*
-Script: Fx.SmoothScroll.js
-	Class for creating a smooth scrolling effect to all internal links on the page.
+---
 
-	License:
-		MIT-style license.
+script: Fx.SmoothScroll.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -7375,7 +9627,7 @@
 		this.doc = context.getDocument();
 		var win = context.getWindow();
 		this.parent(this.doc, options);
-		this.links = this.options.links ? $$(this.options.links) : $$(this.doc.links);
+		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;}
@@ -7392,7 +9644,7 @@
 	useLink: function(link, anchor){
 		var el;
 		link.addEvent('click', function(event){
-			if (el !== false && !el) el = $(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
 			if (el) {
 				event.preventDefault();
 				this.anchor = anchor;
@@ -7401,17 +9653,26 @@
 			}
 		}.bind(this));
 	}
-
 });/*
-Script: Fx.Sort.js
-	Defines Fx.Sort, a class that reorders lists with a transition.
+---
 
-	License:
-		MIT-style license.
+script: Fx.Sort.js
 
-	Authors:
-		Aaron Newton
+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({
@@ -7438,10 +9699,11 @@
 
 	sort: function(newOrder){
 		if ($type(newOrder) != 'array') return false;
-		var top = 0;
-		var left = 0;
-		var zero = {};
-		var vert = this.options.mode == 'vertical';
+		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;
@@ -7475,10 +9737,7 @@
 			if (newOrder.length > this.elements.length)
 				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
 		}
-		top = 0;
-		left = 0;
-		var margin = 0;
-		var next = {};
+		var margin = top = left = 0;
 		newOrder.each(function(item, index){
 			var newPos = {};
 			if (vert){
@@ -7549,20 +9808,32 @@
 		var newOrder = $A(this.currentOrder);
 		newOrder[this.currentOrder.indexOf(one)] = two;
 		newOrder[this.currentOrder.indexOf(two)] = one;
-		this.sort(newOrder);
+		return this.sort(newOrder);
 	}
 
 });/*
-Script: Drag.js
-	The base Drag Class. Can be used to drag and resize Elements using mouse events.
+---
 
-	License:
-		MIT-style license.
+script: Drag.js
 
-	Authors:
-		Valerio Proietti
-		Tom Occhinno
-		Jan Kassens
+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({
@@ -7589,11 +9860,11 @@
 
 	initialize: function(){
 		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
-		this.element = $(params.element);
+		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) : $(this.options.handle)) || this.element;
+		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
 		this.mouse = {'now': {}, 'pos': {}};
 		this.value = {'start': {}, 'now': {}};
 
@@ -7621,6 +9892,7 @@
 	},
 
 	start: function(event){
+		if (event.rightClick) return;
 		if (this.options.preventDefault) event.preventDefault();
 		this.mouse.start = event.page;
 		this.fireEvent('beforeStart', this.element);
@@ -7670,9 +9942,12 @@
 					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]) % 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];
+			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]);
 	},
@@ -7707,17 +9982,30 @@
 
 });
 /*
-Script: Drag.Move.js
-	A Drag extension that provides support for the constraining of draggables to containers and droppables.
+---
 
-	License:
-		MIT-style license.
+script: Drag.Move.js
 
-	Authors:
-		Valerio Proietti
-		Tom Occhinno
-		Jan Kassens*/
+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,
@@ -7735,56 +10023,101 @@
 
 	initialize: function(element, options){
 		this.parent(element, options);
+		element = this.element;
+		
 		this.droppables = $$(this.options.droppables);
-		this.container = $(this.options.container);
-		if (this.container && $type(this.container) != 'element') this.container = $(this.container.getDocument().body);
+		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');
 
-		var position = this.element.getStyle('position');
-		if (position=='static') position = 'absolute';
-		if ([this.element.getStyle('left'), this.element.getStyle('top')].contains('auto')) this.element.position(this.element.getPosition(this.element.offsetParent));
-		this.element.setStyle('position', position);
-
 		this.addEvent('start', this.checkDroppables, true);
 
 		this.overed = null;
 	},
 
 	start: function(event){
-		if (this.container){
-			var ccoo = this.container.getCoordinates(this.element.getOffsetParent()), cbs = {}, ems = {};
+		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){
-				cbs[pad] = this.container.getStyle('border-' + pad).toInt();
-				ems[pad] = this.element.getStyle('margin-' + pad).toInt();
-			}, this);
+		['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 + ems.left + ems.right;
-			var height = this.element.offsetHeight + ems.top + ems.bottom;
+		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) {
-				$each(ems, function(value, key) {
-					ems[key] = 0;
-				});
+		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;
 			}
-			if (this.container == this.element.getOffsetParent()) {
-				this.options.limit = {
-					x: [0 - ems.left, ccoo.right - cbs.left - cbs.right - width + ems.right],
-					y: [0 - ems.top, ccoo.bottom - cbs.top - cbs.bottom - height + ems.bottom]
-				};
+		} else {
+			left -= elementMargin.left;
+			top -= elementMargin.top;
+			
+			if (this.container == offsetParent){
+				right -= containerBorder.left;
+				bottom -= containerBorder.top;
 			} else {
-				this.options.limit = {
-					x: [ccoo.left + cbs.left - ems.left, ccoo.right - cbs.right - width + ems.right],
-					y: [ccoo.top + cbs.top - ems.top, ccoo.bottom - cbs.bottom - height + ems.bottom]
-				};
+				left += containerCoordinates.left + containerBorder.left;
+				top += containerCoordinates.top + containerBorder.top;
 			}
-
 		}
-		if (this.options.precalculate){
-			this.positions = this.droppables.map(function(el) {
-				return el.getCoordinates();
-			});
-		}
-		this.parent(event);
+		
+		return {
+			x: [left, right],
+			y: [top, bottom]
+		};
 	},
 
 	checkAgainst: function(el, i){
@@ -7826,14 +10159,27 @@
 
 });
 /*
-Script: Slider.js
-	Class for creating horizontal and vertical slider controls.
+---
 
-	License:
-		MIT-style license.
+script: Slider.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -7850,6 +10196,7 @@
 			if (this.options.snap) position = this.toPosition(this.step);
 			this.knob.setStyle(this.property, position);
 		},
+		initialStep: 0,
 		snap: false,
 		offset: 0,
 		range: false,
@@ -7860,8 +10207,8 @@
 
 	initialize: function(element, knob, options){
 		this.setOptions(options);
-		this.element = $(element);
-		this.knob = $(knob);
+		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){
@@ -7875,8 +10222,12 @@
 				this.property = 'left';
 				offset = 'offsetWidth';
 		}
-		this.half = this.knob[offset] / 2;
-		this.full = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
+		
+		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;
@@ -7884,25 +10235,22 @@
 		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.offset);
+		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];
 
-		this.bound = {
-			clickedElement: this.clickedElement.bind(this),
-			scrolledElement: this.scrolledElement.bindWithEvent(this),
-			draggedKnob: this.draggedKnob.bind(this)
-		};
-
 		var dragOptions = {
 			snap: 0,
 			limit: limit,
 			modifiers: modifiers,
-			onDrag: this.bound.draggedKnob,
-			onStart: this.bound.draggedKnob,
+			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();
@@ -7919,15 +10267,15 @@
 	},
 
 	attach: function(){
-		this.element.addEvent('mousedown', this.bound.clickedElement);
-		if (this.options.wheel) this.element.addEvent('mousewheel', this.bound.scrolledElement);
+		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.bound.clickedElement);
-		this.element.removeEvent('mousewheel', this.bound.scrolledElement);
+		this.element.removeEvent('mousedown', this.clickedElement);
+		this.element.removeEvent('mousewheel', this.scrolledElement);
 		this.drag.detach();
 		return this;
 	},
@@ -7994,14 +10342,23 @@
 	}
 
 });/*
-Script: Sortables.js
-	Class for creating a drag and drop sorting interface for lists of items.
+---
 
-	License:
-		MIT-style license.
+script: Sortables.js
 
-	Authors:
-		Tom Occhino
+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({
@@ -8026,7 +10383,7 @@
 		this.lists = [];
 		this.idle = true;
 
-		this.addLists($$($(lists) || lists));
+		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));
 	},
@@ -8085,7 +10442,7 @@
 			position: 'absolute',
 			visibility: 'hidden',
 			'width': element.getStyle('width')
-		}).inject(this.list).position(element.getPosition(element.getOffsetParent()));
+		}).inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
 	},
 
 	getDroppables: function(){
@@ -8173,15 +10530,26 @@
 
 });
 /*
-Script: Request.JSONP.js
-	Defines Request.JSONP, a class for cross domain javascript via script injection.
+---
 
-	License:
-		MIT-style license.
+script: Request.JSONP.js
 
-	Authors:
-		Aaron Newton
-		Guillermo Rauch
+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({
@@ -8193,7 +10561,9 @@
 		onRequest: $empty(scriptElement),
 		onComplete: $empty(data),
 		onSuccess: $empty(data),
-		onCancel: $empty(),*/
+		onCancel: $empty(),
+		log: false,
+		*/
 		url: '',
 		data: {},
 		retries: 0,
@@ -8205,6 +10575,7 @@
 
 	initialize: function(options){
 		this.setOptions(options);
+		if (this.options.log) this.enableLog();
 		this.running = false;
 		this.requests = 0;
 		this.triesRemaining = [];
@@ -8222,7 +10593,9 @@
 	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++;
+		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);
@@ -8241,13 +10614,11 @@
 					this.triesRemaining[index] = remaining - 1;
 					if (script){
 						script.destroy();
-						this.request(options, index);
-						this.fireEvent('retry', this.triesRemaining[index]);
+						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
 					}
 				} else if(script && this.options.timeout){
 					script.destroy();
-					this.cancel();
-					this.fireEvent('failure');
+					this.cancel().fireEvent('failure');
 				}
 			}).delay(this.options.timeout, this);
 		}).delay(Browser.Engine.trident ? 50 : 0, this);
@@ -8262,11 +10633,12 @@
 	},
 
 	getScript: function(options){
-		var index = Request.JSONP.counter, data;
+		var index = Request.JSONP.counter,
+				data;
 		Request.JSONP.counter++;
 
 		switch ($type(options.data)){
-			case 'element': data = $(options.data).toQueryString(); break;
+			case 'element': data = document.id(options.data).toQueryString(); break;
 			case 'object': case 'hash': data = Hash.toQueryString(options.data);
 		}
 
@@ -8293,14 +10665,25 @@
 
 Request.JSONP.counter = 0;
 Request.JSONP.request_map = {};/*
-Script: Request.Queue.js
-	Controls several instances of Request and its variants to run only one request at a time.
+---
 
-	License:
-		MIT-style license.
+script: Request.Queue.js
 
-	Authors:
-		Aaron Newton
+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({
@@ -8315,7 +10698,9 @@
 		onComplete: $empty(argsPassedToOnComplete),
 		onCancel: $empty(argsPassedToOnCancel),
 		onException: $empty(argsPassedToOnException),
-		onFailure: $empty(argsPassedToOnFailure),*/
+		onFailure: $empty(argsPassedToOnFailure),
+		onEnd: $empty,
+		*/
 		stopOnFailure: true,
 		autoAdvance: true,
 		concurrent: 1,
@@ -8323,11 +10708,16 @@
 	},
 
 	initialize: function(options){
+		if(options){
+			var requests = options.requests;
+			delete options.requests;	
+		}
 		this.setOptions(options);
 		this.requests = new Hash;
-		this.addRequests(this.options.requests);
 		this.queue = [];
 		this.reqBinders = {};
+		
+		if(requests) this.addRequests(requests);
 	},
 
 	addRequest: function(name, request){
@@ -8337,7 +10727,9 @@
 	},
 
 	addRequests: function(obj){
-		$each(obj, this.addRequest, this);
+		$each(obj, function(req, name){
+			this.addRequest(name, req);
+		}, this);
 		return this;
 	},
 
@@ -8376,11 +10768,13 @@
 	},
 
 	getRunning: function(){
-		return this.requests.filter(function(r){ return r.running; });
+		return this.requests.filter(function(r){
+			return r.running;
+		});
 	},
 
 	isRunning: function(){
-		return !!this.getRunning().getKeys().length;
+		return !!(this.getRunning().getKeys().length);
 	},
 
 	send: function(name, options){
@@ -8450,6 +10844,7 @@
 
 	onComplete: function(){
 		this.fireEvent('complete', arguments);
+		if (!this.queue.length) this.fireEvent('end');
 	},
 
 	onCancel: function(){
@@ -8476,15 +10871,24 @@
 
 });
 /*
-Script: Request.Periodical.js
-	Requests the same url at a time interval that increases when no data is returned from the requested server
+---
 
-	License:
-		MIT-style license.
+script: Request.Periodical.js
 
-	Authors:
-		Christoph Pojer
+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({
@@ -8496,36 +10900,43 @@
 	},
 
 	startTimer: function(data){
-		var fn = (function(){
+		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(j){
+		this.completeCheck = function(response){
 			$clear(this.timer);
-			if (j) this.lastDelay = this.options.delay;
-			else this.lastDelay = (this.lastDelay+this.options.delay).min(this.options.limit);
+			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
 			this.timer = fn.delay(this.lastDelay, this);
 		};
-		this.addEvent('complete', this.completeCheck);
-		return this;
+		return this.addEvent('complete', this.completeCheck);
 	},
 
 	stopTimer: function(){
 		$clear(this.timer);
-		this.removeEvent('complete', this.completeCheck);
-		return this;
+		return this.removeEvent('complete', this.completeCheck);
 	}
 
 });/*
-Script: Assets.js
-	Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+---
 
-	License:
-		MIT-style license.
+script: Assets.js
 
-	Authors:
-		Valerio Proietti
+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 = {
@@ -8539,8 +10950,12 @@
 
 		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;
+		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,
@@ -8560,7 +10975,10 @@
 
 	css: function(source, properties){
 		return new Element('link', $merge({
-			rel: 'stylesheet', media: 'screen', type: 'text/css', href: source
+			rel: 'stylesheet',
+			media: 'screen',
+			type: 'text/css',
+			href: source
 		}, properties)).inject(document.head);
 	},
 
@@ -8571,7 +10989,7 @@
 			onerror: $empty
 		}, properties);
 		var image = new Image();
-		var element = $(image) || new Element('img');
+		var element = document.id(image) || new Element('img');
 		['load', 'abort', 'error'].each(function(name){
 			var type = 'on' + name;
 			var event = properties[type];
@@ -8595,31 +11013,52 @@
 	images: function(sources, options){
 		options = $merge({
 			onComplete: $empty,
-			onProgress: $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, {
+			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
-	Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+---
 
-	License:
-		MIT-style license.
+script: Color.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -8697,15 +11136,16 @@
 Array.implement({
 
 	rgbToHsb: function(){
-		var red = this[0], green = this[1], blue = this[2];
-		var hue, saturation, brightness;
-		var max = Math.max(red, green, blue), min = Math.min(red, green, blue);
+		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;
-		brightness = max / 255;
-		saturation = (max != 0) ? delta / max : 0;
-		if (saturation == 0){
-			hue = 0;
-		} else {
+		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;
@@ -8756,14 +11196,24 @@
 
 });
 /*
-Script: Group.js
-	Class for monitoring collections of events
+---
 
-	License:
-		MIT-style license.
+script: Group.js
 
-	Authors:
-		Valerio Proietti
+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({
@@ -8799,15 +11249,26 @@
 
 });
 /*
-Script: Hash.Cookie.js
-	Class for creating, reading, and deleting Cookies in JSON format.
+---
 
-	License:
-		MIT-style license.
+script: Hash.Cookie.js
 
-	Authors:
-		Valerio Proietti
-		Aaron Newton
+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({
@@ -8845,127 +11306,862 @@
 		return value;
 	});
 });/*
-Script: IframeShim.js
-	Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+---
 
-	License:
-		MIT-style license.
+script: HtmlTable.js
 
-	Authors:
-		Aaron Newton
+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 IframeShim = new Class({
+var HtmlTable = new Class({
 
 	Implements: [Options, Events, Class.Occlude],
 
 	options: {
-		className: 'iframeShim',
-		display: false,
-		zIndex: null,
-		margin: 0,
-		offset: {x: 0, y: 0},
-		browsers: true || (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
+		properties: {
+			cellpadding: 0,
+			cellspacing: 0,
+			border: 0
+		},
+		rows: [],
+		headers: [],
+		footers: []
 	},
 
-	property: 'IframeShim',
+	property: 'HtmlTable',
 
-	initialize: function(element, options){
-		this.element = $(element);
+	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.setOptions(options);
-		this.makeShim();
+		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;
 	},
 
-	makeShim: function(){
-		if(this.options.browsers){
-			var zIndex = this.element.getStyle('zIndex').toInt();
+	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;
+	},
 
-			if (!zIndex){
-				var pos = this.element.getStyle('position');
-				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
-				this.element.setStyle('zIndex', zIndex || 1);
-			}
-			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: (window.location.protocol == 'https') ? '://0' : 'javascript:void(0)',
-				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 (Browser.Engine.trident && !IframeShim.ready) window.addEvent('load', inject);
-			else inject();
-		} else {
-			this.position = this.hide = this.show = this.dispose = $lambda(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);
 		}
 	},
 
-	position: function(){
-		if (!IframeShim.ready) return this;
-		var size = this.element.measure(function(){ return this.getSize(); });
-		if ($type(this.options.margin)){
-			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;
+	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);
 		}
-		if (this.shim) {
-			this.shim.set({width: size.x, height: size.y}).position({
-				relativeTo: this.element,
-				offset: this.options.offset
-			});
+
+		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();
 		}
-		return this;
+
+		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]);
 	},
 
-	hide: function(){
-		if (this.shim) this.shim.setStyle('display', 'none');
+	reSort: function(){
+		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
 		return this;
 	},
 
-	show: function(){
-		if (this.shim) this.shim.setStyle('display', 'block');
-		return this.position();
-	},
-
-	dispose: function(){
-		if (this.shim) this.shim.dispose();
+	enableSort: function(){
+		this.element.addClass(this.options.classSortable);
+		this.attachSorts(true);
+		this.detectParsers();
+		this.sortEnabled = true;
 		return this;
 	},
 
-	destroy: function(){
-		if (this.shim) this.shim.destroy();
+	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;
 	}
 
 });
 
-window.addEvent('load', function(){
-	IframeShim.ready = true;
+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: Scroller.js
-	Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+---
 
-	License:
-		MIT-style license.
+script: Keyboard.js
 
-	Authors:
-		Valerio Proietti
+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],
@@ -8981,8 +12177,8 @@
 
 	initialize: function(element, options){
 		this.setOptions(options);
-		this.element = $(element);
-		this.listener = ($type(this.element) != 'element') ? $(this.element.getDocument().body) : this.element;
+		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),
@@ -8993,16 +12189,17 @@
 
 	start: function(){
 		this.listener.addEvents({
-			mouseenter: this.bound.attach,
-			mouseleave: this.bound.detach
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
 		});
 	},
 
 	stop: function(){
 		this.listener.removeEvents({
-			mouseenter: this.bound.attach,
-			mouseleave: this.bound.detach
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
 		});
+		this.detach();
 		this.timer = $clear(this.timer);
 	},
 
@@ -9036,54 +12233,78 @@
 	}
 
 });/*
-Script: Tips.js
-	Class for creating nice tips that follow the mouse cursor when hovering an element.
+---
 
-	License:
-		MIT-style license.
+script: Tips.js
 
-	Authors:
-		Valerio Proietti
-		Christoph Pojer
+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]
+
+...
 */
 
-var Tips = new Class({
+(function(){
 
+var read = function(option, element){
+	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
 	Implements: [Events, Options],
 
 	options: {
-		onShow: function(tip){
-			tip.setStyle('visibility', 'visible');
+		/*
+		onAttach: $empty(element),
+		onDetach: $empty(element),
+		*/
+		onShow: function(){
+			this.tip.setStyle('display', 'block');
 		},
-		onHide: function(tip){
-			tip.setStyle('visibility', 'hidden');
+		onHide: function(){
+			this.tip.setStyle('display', 'none');
 		},
 		title: 'title',
-		text: function(el){
-			return el.get('rel') || el.get('href');
+		text: function(element){
+			return element.get('rel') || element.get('href');
 		},
 		showDelay: 100,
 		hideDelay: 100,
-		className: null,
+		className: 'tip-wrap',
 		offset: {x: 16, y: 16},
 		fixed: false
 	},
 
 	initialize: function(){
 		var params = Array.link(arguments, {options: Object.type, elements: $defined});
-		if (params.options && params.options.offsets) params.options.offset = params.options.offsets;
 		this.setOptions(params.options);
-		this.container = new Element('div', {'class': 'tip'});
-		this.tip = this.getTip();
+		document.id(this);
 		
 		if (params.elements) this.attach(params.elements);
 	},
 
-	getTip: function(){
-		return new Element('div', {
+	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: {
-				visibility: 'hidden',
 				display: 'none',
 				position: 'absolute',
 				top: 0,
@@ -9097,20 +12318,22 @@
 	},
 
 	attach: function(elements){
-		var read = function(option, element){
-			if (option == null) return '';
-			return $type(option) == 'function' ? option(element) : element.get(option);
-		};
 		$$(elements).each(function(element){
-			var title = read(this.options.title, 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', read(this.options.text, element));
+			element.retrieve('tip:text', text);
+			this.fireEvent('attach', [element]);
 			
 			var events = ['enter', 'leave'];
 			if (!this.options.fixed) events.push('move');
 			
 			events.each(function(value){
-				element.addEvent('mouse' + value, element.retrieve('tip:' + value, this['element' + value.capitalize()].bindWithEvent(this, element)));
+				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);
 		
@@ -9120,12 +12343,12 @@
 	detach: function(elements){
 		$$(elements).each(function(element){
 			['enter', 'leave', 'move'].each(function(value){
-				element.removeEvent('mouse' + value, element.retrieve('tip:' + value) || $empty);
+				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
 			});
 			
-			element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');
+			this.fireEvent('detach', [element]);
 			
-			if ($type(this.options.title) == 'string' && this.options.title == 'title'){
+			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);
 			}
@@ -9135,29 +12358,32 @@
 	},
 
 	elementEnter: function(event, element){
-		$A(this.container.childNodes).each(Element.dispose);
+		this.container.empty();
 		
 		['title', 'text'].each(function(value){
 			var content = element.retrieve('tip:' + value);
-			if (!content) return;
-			
-			this[value + 'Element'] = new Element('div', {'class': 'tip-' + value}).inject(this.container);
-			this.fill(this[value + 'Element'], content);
+			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
 		}, this);
 		
-		this.timer = $clear(this.timer);
+		$clear(this.timer);
 		this.timer = this.show.delay(this.options.showDelay, this, element);
-		this.tip.setStyle('display', 'block');
-		this.position((!this.options.fixed) ? event : {page: element.getPosition()});
+		this.position((this.options.fixed) ? {page: element.getPosition()} : event);
 	},
 
 	elementLeave: function(event, element){
 		$clear(this.timer);
-		this.tip.setStyle('display', 'none');
 		this.timer = this.hide.delay(this.options.hideDelay, this, element);
+		this.fireForParent(event, element);
 	},
 
-	elementMove: function(event){
+	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);
 	},
 
@@ -9180,105 +12406,1443 @@
 		else element.adopt(contents);
 	},
 
-	show: function(el){
-		this.fireEvent('show', [this.tip, el]);
+	show: function(element){
+		this.fireEvent('show', [element]);
 	},
 
-	hide: function(el){
-		this.fireEvent('hide', [this.tip, el]);
+	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.English.US.js
-	Date messages for US English.
+---
 
-	License:
-		MIT-style license.
+script: Date.Danish.js
 
-	Authors:
-		Aaron Newton
+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', {
 
-MooTools.lang.set('en-US', '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'],
 
-	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
+	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'],
-	shortDate: '%m/%d/%Y',
-	shortTime: '%I:%M%p',
+
 	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){
-		//1st, 2nd, 3rd, etc.
-		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][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',
-	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'
+	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: FormValidator.English.js
-	Date messages for English.
+---
 
-	License:
-		MIT-style license.
+script: Date.Spanish.US.js
 
-	Authors:
-		Aaron Newton
+description: Date messages for Spanish.
 
+license: MIT-style license
+
+authors:
+- Ãlfons Sanchez
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Spanish]
+
+...
 */
 
-MooTools.lang.set('en-US', 'FormValidator', {
+MooTools.lang.set('es-ES', 'Date', {
 
-	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.',
+	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: 'Warning: ',
+	warningPrefix: 'Avis: ',
 
-	//FormValidator.Extras
+	//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.'
+	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.'
 
-});// $Id: common.js 478 2009-07-10 14:09:58Z pagameba $
+});/*
+---
+
+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 555 2009-10-23 05:23:59Z jonlb at comcast.net $
 /**
  * Class: Jx
  * Jx is a global singleton object that contains the entire Jx library
@@ -9319,22 +13883,43 @@
     }    
 })();
 */
+
 Class.Mutators.Family = function(self,name) {
     if ($defined(name)){
-        self.$family = {'name': name};
-        $[name] = $.object;
+        self.jxFamily = name;
         return self;
     }
-    else {
-        this.implement('$family',{'name':self});
+    else {   
+        this.implement({'jxFamily':self});
     }
 };
+function $unlink(object){
+    if (object && object.jxFamily){
+        return 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;
+};
 
 /* Setup global namespace
  * If jxcore is loaded by jx.js, then the namespace and baseURL are
  * already established
  */
-if (typeof Jx == 'undefined') {
+if (typeof Jx === 'undefined') {
     var Jx = {};
     (function() {
         var aScripts = document.getElementsByTagName('SCRIPT');
@@ -9369,18 +13954,24 @@
                 
             }
         }
-       /**
-        * Determine if we're running in Adobe AIR. If so, determine which sandbox we're in
-        */
-        var src = aScripts[0].src;
-        if (src.contains('app:')){
-            Jx.isAir = true;
-        } else {
-            Jx.isAir = false;
-        }
+       
     })();
 } 
 
+(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;
+    }
+})();
+
 /**
  * Method: applyPNGFilter
  *
@@ -9407,6 +13998,10 @@
    }
 };
 
+/**
+ * NOTE: We should consider moving the image loading code into a separate
+ * class. Perhaps as Jx.Preloader which could extend Jx.Object
+ */
 Jx.imgQueue = [];   //The queue of images to be loaded
 Jx.imgLoaded = {};  //a hash table of images that have been loaded and cached
 Jx.imagesLoading = 0; //counter for number of concurrent image loads 
@@ -9470,11 +14065,15 @@
  * 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
+        'frameborder':0,
+        'src': Jx.baseURL+'/empty.html'
     });
 };
 /**
@@ -9491,7 +14090,7 @@
 Jx.getNumber = function(n, def) {
   var result = n===null||isNaN(parseInt(n,10))?(def||0):parseInt(n,10);
   return result;
-};
+}
 
 /**
  * Method: getPageDimensions
@@ -9503,8 +14102,14 @@
  */
 Jx.getPageDimensions = function() {
     return {width: window.getWidth(), height: window.getHeight()};
-};
+}
 
+Jx.type = function(obj){
+    if (typeof obj == 'undefined'){
+        return false;
+    }
+    return obj.jxFamily ? obj.jxFamily : $type(obj);
+}
 /**
  * Class: Element
  *
@@ -9517,7 +14122,15 @@
  * 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
+
+    
 Element.implement({
     /**
      * Method: getBoxSizing
@@ -9564,13 +14177,12 @@
      * are the size of the content area of the measured element.
      */
     getContentBoxSize : function() {
-      var w = this.offsetWidth;
-      var h = this.offsetHeight;
-      var padding = this.getPaddingSize();
-      var border = this.getBorderSize();
-      w = w - padding.left - padding.right - border.left - border.right;
-      h = h - padding.bottom - padding.top - border.bottom - border.top;
-      return {width: w, height: h};
+        var w = this.offsetWidth;
+        var h = this.offsetHeight;
+        var s = this.getSizes(['padding','border']);
+        w = w - s.padding.left - s.padding.right - s.border.left - s.border.right;
+        h = h - s.padding.bottom - s.padding.top - s.border.bottom - s.border.top;
+        return {width: w, height: h};
     },
     /**
      * Method: getBorderBoxSize
@@ -9585,9 +14197,9 @@
      * are the size of the border area of the measured element.
      */
     getBorderBoxSize: function() {
-      var w = this.offsetWidth;
-      var h = this.offsetHeight;
-      return {width: w, height: h}; 
+        var w = this.offsetWidth;
+        var h = this.offsetHeight;
+        return {width: w, height: h}; 
     },
     
     /**
@@ -9603,13 +14215,44 @@
      * are the size of the margin area of the measured element.
      */
     getMarginBoxSize: function() {
-        var margins = this.getMarginSize();
-        var w = this.offsetWidth + margins.left + margins.right;
-        var h = this.offsetHeight + margins.top + margins.bottom;
+        var s = this.getSizes(['margin']);
+        var w = this.offsetWidth + s.margin.left + s.margin.right;
+        var h = this.offsetHeight + s.margin.top + s.margin.bottom;
         return {width: w, height: h};
     },
-    
     /**
+     * Method: getSizes
+     * measure the size of various styles on various edges and return
+     * the values.
+     *
+     * Parameters:
+     * styles - array, the styles to compute.  By default, this is ['padding',
+     *     'border','margin'].  If you don't need all the styles, just request
+     *     the ones you need to minimize compute time required.
+     * edges - array, the edges to compute styles for.  By default,  this is
+     *     ['top','right','bottom','left'].  If you don't need all the edges,
+     *     then request the ones you need to minimize compute time.
+     *
+     * Returns:
+     * {Object} an object with one member for each requested style.  Each
+     * style member is an object containing members for each requested edge.
+     * Values are the computed style for each edge in pixels.
+     */
+    getSizes: function(which, edges) {
+      which = which || ['padding','border','margin'];
+      edges = edges || ['left','top','right','bottom'];
+      var result={};
+      which.each(function(style) {
+        result[style]={};
+        edges.each(function(edge) {
+            var e = (style == 'border') ? edge + '-width' : edge;
+            var n = this.getStyle(style+'-'+e);
+            result[style][edge] = n===null||isNaN(parseInt(n,10))?0:parseInt(n,10);
+        }, this);
+      }, this);
+      return result;
+    },    
+    /**
      * Method: setContentBoxSize
      * set either or both of the width and height of an element to
      * the provided size.  This function ensures that the content
@@ -9624,27 +14267,28 @@
      */
     setContentBoxSize : function(size) {
         if (this.getBoxSizing() == 'border-box') {
-            var padding = this.getPaddingSize();
-            var border = this.getBorderSize();
-            if (typeof size.width != 'undefined') {
-                var width = (size.width + padding.left + padding.right + border.left + border.right);
+            var m = this.measure(function() {
+                return this.getSizes(['padding','border']);
+            });
+            if ($defined(size.width)) {
+                var width = size.width + m.padding.left + m.padding.right + m.border.left + m.border.right;
                 if (width < 0) {
                     width = 0;
                 }
                 this.style.width = width + 'px';
             }
-            if (typeof size.height != 'undefined') {
-                var height = (size.height + padding.top + padding.bottom + border.top + border.bottom);
+            if ($defined(size.height)) {
+                var height = size.height + m.padding.top + m.padding.bottom + m.border.top + m.border.bottom;
                 if (height < 0) {
                     height = 0;
                 }
                 this.style.height = height + 'px';
             }
         } else {
-            if (typeof size.width != 'undefined') {
+            if ($defined(size.width) && size.width >= 0) {
                 this.style.width = size.width + 'px';
             }
-            if (typeof size.height != 'undefined') {
+            if ($defined(size.height) && size.height >= 0) {
                 this.style.height = size.height + 'px';
             }
         }
@@ -9664,86 +14308,33 @@
      */
     setBorderBoxSize : function(size) {
       if (this.getBoxSizing() == 'content-box') {
-        var padding = this.getPaddingSize();
-        var border = this.getBorderSize();
-        var margin = this.getMarginSize();
-        if (typeof size.width != 'undefined') {
-          var width = (size.width - padding.left - padding.right - border.left - border.right - margin.left - margin.right);
+          var m = this.measure(function() {
+              return this.getSizes();
+          });
+          
+        if ($defined(size.width)) {
+          var width = size.width - m.padding.left - m.padding.right - m.border.left - m.border.right - m.margin.left - m.margin.right;
           if (width < 0) {
             width = 0;
           }
           this.style.width = width + 'px';
         }
-        if (typeof size.height != 'undefined') {
-          var height = (size.height - padding.top - padding.bottom - border.top - border.bottom - margin.top - margin.bottom);
+        if ($defined(size.height)) {
+          var height = size.height - m.padding.top - m.padding.bottom - m.border.top - m.border.bottom - m.margin.top - m.margin.bottom;
           if (height < 0) {
             height = 0;
           }
           this.style.height = height + 'px';
         }
       } else {
-        if (typeof size.width != 'undefined' && size.width >= 0) {
+        if ($defined(size.width) && size.width >= 0) {
           this.style.width = size.width + 'px';
         }
-        if (typeof size.height != 'undefined' && size.height >= 0) {
+        if ($defined(size.height) && size.height >= 0) {
           this.style.height = size.height + 'px';
         }
       }
     },
-    /**
-     * Method: getPaddingSize
-     * returns the padding for each edge of an element
-     *
-     * Parameters: 
-     * elem - {Object} The element to get the padding for.
-     *
-     * Returns:
-     * {Object} an object with properties left, top, right and bottom
-     * that contain the associated padding values.
-     */
-    getPaddingSize : function () {
-      var l = Jx.getNumber(this.getStyle('padding-left'));
-      var t = Jx.getNumber(this.getStyle('padding-top'));
-      var r = Jx.getNumber(this.getStyle('padding-right'));
-      var b = Jx.getNumber(this.getStyle('padding-bottom'));
-      return {left:l, top:t, right: r, bottom: b};
-    },
-    /**
-     * Method: getBorderSize
-     * returns the border size for each edge of an element
-     *
-     * Parameters: 
-     * elem - {Object} The element to get the borders for.
-     *
-     * Returns:
-     * {Object} an object with properties left, top, right and bottom
-     * that contain the associated border values.
-     */
-    getBorderSize : function() {
-      var l = Jx.getNumber(this.getStyle('border-left-width'));
-      var t = Jx.getNumber(this.getStyle('border-top-width'));
-      var r = Jx.getNumber(this.getStyle('border-right-width'));
-      var b = Jx.getNumber(this.getStyle('border-bottom-width'));
-      return {left:l, top:t, right: r, bottom: b};
-    },
-    /**
-     * Method: getMarginSize
-     * returns the margin size for each edge of an element
-     *
-     * Parameters: 
-     * elem - {Object} The element to get the margins for.
-     *
-     * Returns:
-     *: {Object} an object with properties left, top, right and bottom
-     * that contain the associated margin values.
-     */
-    getMarginSize : function() {
-      var l = Jx.getNumber(this.getStyle('margin-left'));
-      var t = Jx.getNumber(this.getStyle('margin-top'));
-      var r = Jx.getNumber(this.getStyle('margin-right'));
-      var b = Jx.getNumber(this.getStyle('margin-bottom'));
-      return {left:l, top:t, right: r, bottom: b};
-    },
     
     /**
      * Method: descendantOf
@@ -9756,9 +14347,9 @@
      * {Boolean} true if the element is a descendent, false otherwise.
      */
     descendantOf: function(node) {
-        var parent = $(this.parentNode);
+        var parent = document.id(this.parentNode);
         while (parent != node && parent && parent.parentNode && parent.parentNode != parent) {
-            parent = $(parent.parentNode);
+            parent = document.id(parent.parentNode);
         }
         return parent == node;
     },
@@ -9779,17 +14370,322 @@
         var o = this;
         var tagName = o.tagName;
         while (o.tagName != type && o && o.parentNode && o.parentNode != o) {
-            o = $(o.parentNode);
+            o = document.id(o.parentNode);
         }
         return o.tagName == type ? o : false;
     }
 } );
 
+Array.implement({
+    
+    /**
+     * Method: swap
+     * swaps 2 elements of an array
+     * 
+     * Parameters:
+     * a - the first position to swap
+     * b - the second position to swap
+     */
+    'swap': function(a,b){
+        var temp;
+        temp = this[a];
+        this[a] = this[b];
+        this[b] = temp;
+    }
+    
+});
+
+})(document.id || $); // End Wrapper for document.id 
+
 /**
- * Class: Jx.ContentLoader
+ * Class: Jx.Styles
+ * Dynamic stylesheet class. Used for creating and manipulating dynamic 
+ * stylesheets.
+ *
+ * TBD: should we handle the case of putting the same selector in a stylesheet
+ * twice?  Right now the code that stores the index of each rule on the
+ * stylesheet is not really safe for that when combined with delete or get
+ *
+ * This is a singleton and should be called directly, like so:
+ *
+ * (code)
+ *   // create a rule that turns all para text red and 15px.
+ *   var rule = Jx.Styles.insertCssRule("p", "color: red;", "myStyle");
+ *   rule.style.fontSize = "15px";
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ * Additional code by Paul Spencer
  * 
- * ContentLoader is a mix-in class that provides a consistent
- * mechanism for other Jx controls to load content in one of
+ * This file is licensed under an MIT style license
+ *
+ * Inspired by dojox.html.styles, VisitSpy by nwhite,
+ * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
+ *
+ */
+Jx.Styles = new(new Class({
+    /**
+     * 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.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * styleSheetName - <String> the name of the sheet to get the rule from
+     *
+     * Returns:
+     * <CSSRule> - the requested rule
+     */
+    getCssRule: function(selector, styleSheetName) {
+        var ss = this.getDynamicStyleSheet(styleSheetName);
+        var rule = null;
+        if (ss.indicies) {
+            var i = ss.indicies.indexOf(selector);
+            if (i == -1) {
+                rule = this.insertCssRule(selector, '', styleSheetName);
+            } else {
+                if (Browser.Engine.name === 'trident') {
+                    rule = ss.sheet.rules[i];
+                } else {
+                    rule = ss.sheet.cssRules[i];
+                }
+            }
+        }
+        return rule;
+    },
+    /**
+     * Method: 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
+     * 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.
+     */
+    insertCssRule: function (selector, declaration, styleSheetName) {
+        var ss = this.getDynamicStyleSheet(styleSheetName);
+        var rule;
+        var text = selector + " {" + declaration + "}";
+        if (Browser.Engine.name === 'trident') {
+            ss.styleSheet.cssText += text;
+            rule = ss.sheet.rules[ss.insertRule.length];
+        } else {
+            ss.sheet.insertRule(text, ss.indicies.length);
+            rule = ss.sheet.cssRules[ss.indicies.length];
+        }
+        ss.indicies.push(selector);
+        return rule;
+    },
+    /**
+     * Method: 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.
+     *
+     * Returns:
+     * <Boolean> true if the rule was removed, false if it was not.
+     */
+    removeCssRule: function (selector, styleSheetName) {
+        var ss = this.getDynamicStyleSheet(styleSheetName);
+        var i = ss.indicies.indexOf(selector);
+        ss.indicies.splice(i, 1);
+        if (Browser.Engine.name === 'trident') {
+            ss.removeRule(i);
+            return true;
+        } else {
+            ss.sheet.deleteRule(i);
+            return true;
+        }
+        return false;
+    },
+    /**
+     * Method: getDynamicStyleSheet
+     * return a reference to a styleSheet based on its title.  If the sheet
+     * does not already exist, it is created.
+     *
+     * Parameter:
+     * name - <String> the title of the stylesheet to create or obtain
+     *
+     * Returns: 
+     * <StyleSheet> a StyleSheet object with browser dependent capabilities.
+     */
+    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);
+            sheet.indicies = [];
+            this.dynamicStyleMap.set(name, sheet);
+        }
+        return this.dynamicStyleMap.get(name);
+    },
+    /* Method: enableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to enable
+     */
+    enableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = false;
+    },
+    /* Method: disableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to disable
+     */
+    disableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = true;
+    }
+}))();// $Id: $
+/**
+ * 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:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Object = new Class({
+	
+    Implements: [Options, Events],
+	
+    Family: "Jx.Object",
+    
+    plugins: new Hash(),
+    
+    pluginNamespace: 'Other',
+    
+    parameters: ['options'],
+	
+	initialize: function(){
+        //normalize arguments
+        var numArgs = arguments.length;
+        var options = {};
+        
+        if (numArgs > 0) {
+            if (numArgs === 1 
+                    && (Jx.type(arguments[0])==='object' || Jx.type(arguments[0])==='Hash') 
+                    && this.parameters.length === 1 
+                    && this.parameters[0] === 'options') {
+                options = arguments[0];
+            } else {
+                var numParams = this.parameters.length;
+                var index;
+                if (numParams <= numArgs) {
+                    index = numParams;
+                } else {
+                    index = numArgs;
+                }
+                options = {};
+                for (var i = 0; i < index; i++) {
+                    if (this.parameters[i] === 'options') {
+                        options = $merge(options, arguments[i]);
+                    } else {
+                        options[this.parameters[i]] = arguments[i];
+                    }
+                }
+            }
+        }
+        
+        this.setOptions(options);
+        this.fireEvent('preInit');
+        this.init();
+        this.fireEvent('postInit');
+        this.fireEvent('prePluginInit');
+        this.initPlugins();
+        this.fireEvent('postPluginInit');
+        this.fireEvent('initializeDone');
+    },
+    
+    initPlugins: function () {
+        //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') {
+                this.options.plugins.each(function (plugin) {
+                    if (plugin instanceof Jx.Plugin) {
+                        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);
+                        p.attach(this);
+                        this.plugins.set(p.name, p);
+                    }
+                }, this);
+            }
+        }
+    },
+    
+    destroy: function () {
+        this.fireEvent('preDestroy');
+        this.cleanup();
+        this.fireEvent('postDestroy');
+    },
+    
+    cleanup: function () {
+        //detach plugins
+        if (this.plugins.getLength > 0) {
+            this.plugins.each(function (plugin) {
+                plugin.detach();
+                plugin.destroy();
+            }, this);
+        }
+    },
+    
+    init: $empty,
+    
+    registerPlugin: function (plugin) {
+        if (!this.plugins.has(plugin.name)) {
+            this.plugins.set(plugin.name,  plugin);
+        }
+    },
+    
+    deregisterPlugin: function (plugin) {
+        if (this.plugins.has(plugin.name)) {
+            this.plugins.erase(plugin.name);
+        } 
+    }
+
+});
+ // $Id: $
+/**
+ * Class: Jx.Widget
+ * Base class for all widgets (visual classes) in the JxLib Framework. This 
+ * class extends <Jx.Object> and adds the Chrome, ContentLoader, Addable, and 
+ * AutoPosition mixins from the original framework.
+ * 
+ * ContentLoader:
+ * 
+ * ContentLoader functionality provides a consistent
+ * mechanism for descendants of Jx.Widget to load content in one of
  * four different ways:
  *
  * o using an existing element, by id
@@ -9800,26 +14696,81 @@
  *
  * o using a URL to get the content remotely
  *
- * Use the Implements syntax in your Class to add Jx.ContentLoader
- * to your class.
+ * 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
+ * calculations using the margins, borders, and padding of the jxChrome
+ * class and the element it is added to.
  *
- * 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.
+ * Chrome can consist of either pure CSS border and background colors, or
+ * a background-image on the jxChrome class.  Using a background-image on
+ * the jxChrome class creates four images inside the chrome container that
+ * are positioned in the top-left, top-right, bottom-left and bottom-right
+ * corners of the chrome container and are sized to fill 50% of the width
+ * and height.  The images are positioned and clipped such that the 
+ * appropriate corners of the chrome image are displayed in those locations.
  *
- * Option: contentURL
- * the URL to load content from
+ *
  */
-Jx.ContentLoader = new Class ({
-    /**
+Jx.Widget = new Class({
+	
+	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.
+         */
+		content: null,
+		/**
+		 * Option: contentURL
+		 * the URL to load content from
+		 */
+		contentURL: null
+	},
+	
+	/**
+	 * Property: domObj
+	 * The HTMLElement that represents this widget.
+	 */
+	domObj: null,
+	
+	/**
      * Property: contentIsLoaded
      *
      * tracks the load state of the content, specifically useful
      * in the case of remote content.
      */ 
     contentIsLoaded: false,
+    
     /**
+     * Property: chrome
+     * the DOM element that contains the chrome
+     */
+    chrome: null,
+    
+    
+    /**
+     * APIMethod: init
+     * sets up the base widget code and runs the render function. 
+     */
+    init: function(){
+		if (!this.options.deferRender) {
+		    this.fireEvent('preRender');
+		    this.render();
+		    this.fireEvent('postRender');
+		} else {
+		    this.fireEvent('deferRender');
+		}
+	},
+    
+    
+    /**
      * Method: loadContent
      *
      * triggers loading of content based on options set for the current
@@ -9842,13 +14793,13 @@
      *     useful when using the contentURL method of loading content.
      */     
     loadContent: function(element) {
-        element = $(element);
+        element = document.id(element);
         if (this.options.content) {
             var c;
             if (this.options.content.domObj) {
-                c = $(this.options.content.domObj);
+                c = document.id(this.options.content.domObj);
             } else {
-                c = $(this.options.content);
+                c = document.id(this.options.content);
             }
             if (c) {
                 if (this.options.content.addTo) {
@@ -9915,47 +14866,8 @@
                 }
             }
         }, this);
-    }
-});
-
-
-/**
- * It seems AIR never returns an XHR that "fails" by not finding the 
- * appropriate file when run in the application sandbox and retrieving a local
- * file. This affects Jx.ContentLoader in that a "failed" event is never fired. 
- * 
- * To fix this, I've added a timeout that waits about 10 seconds or so in the code above
- * for the XHR to return, if it hasn't returned at the end of the timeout, we cancel the
- * XHR and fire the failure event.
- *
- * This code only gets added if we're in AIR.
- */
-if (Jx.isAir){
-    Jx.ContentLoader.implement({
-        /**
-         * Method: checkRequest()
-         * Is fired after a delay to check the request to make sure it's not
-         * failing in AIR.
-         */
-        checkRequest: function(){
-            if (this.req.xhr.readyState === 1) {
-                //we still haven't gotten the file. Cancel and fire the
-                //failure
-                $clear(this.reqTimeout);
-                this.req.cancel();
-                this.contentIsLoaded = true;
-                this.fireEvent('contentLoadFailed', this);
-            }
-        }
-    });
-}
-
-/**
- * Class: Jx.AutoPosition
- * Mix-in class that provides a method for positioning
- * elements relative to other elements.
- */
-Jx.AutoPosition = new Class({
+    },
+    
     /**
      * Method: position
      * positions an element relative to another element
@@ -10012,8 +14924,8 @@
      *    being positioned as top, right, bottom and left properties.
      */
     position: function(element, relative, options) {
-        element = $(element);
-        relative = $(relative);
+        element = document.id(element);
+        relative = document.id(relative);
         var hor = $splat(options.horizontal || ['center center']);
         var ver = $splat(options.vertical || ['center center']);
         var offsets = $merge({top:0,right:0,bottom:0,left:0}, options.offsets || {});
@@ -10021,12 +14933,12 @@
         var coords = relative.getCoordinates(); //top, left, width, height
         var page;
         var scroll;
-        if (!$(element.parentNode) || element.parentNode ==  document.body) {
+        if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
             page = Jx.getPageDimensions();
-            scroll = $(document.body).getScroll();
+            scroll = document.id(document.body).getScroll();
         } else {
-            page = $(element.parentNode).getContentBoxSize(); //width, height
-            scroll = $(element.parentNode).getScroll();
+            page = document.id(element.parentNode).getContentBoxSize(); //width, height
+            scroll = document.id(element.parentNode).getScroll();
         }
         if (relative == document.body) {
             // adjust coords for the scroll offsets to make the object
@@ -10183,33 +15095,7 @@
                 jxl.options.left = left;
                 jxl.options.top = top;
             }
-        }
-});
-
-/**
- * Class: Jx.Chrome
- * A mix-in class that provides chrome helper functions.  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.
- *
- * Chrome can consist of either pure CSS border and background colors, or
- * a background-image on the jxChrome class.  Using a background-image on
- * the jxChrome class creates four images inside the chrome container that
- * are positioned in the top-left, top-right, bottom-left and bottom-right
- * corners of the chrome container and are sized to fill 50% of the width
- * and height.  The images are positioned and clipped such that the 
- * appropriate corners of the chrome image are displayed in those locations.
- */
-Jx.Chrome = new Class({
-    /**
-     * Property: chrome
-     * the DOM element that contains the chrome
-     */
-    chrome: null,
+    },
     
     /**
      * Method: makeChrome
@@ -10233,7 +15119,9 @@
          * through padding on the chrome object.  Other code can then
          * make use of these offset values to fix positioning.
          */
-        this.chromeOffsets = c.getPaddingSize();
+        this.chromeOffsets = c.measure(function() {
+            return this.getSizes(['padding']).padding;
+        });
         c.setStyle('padding', 0);
         
         /* get the chrome image from the background image of the element */
@@ -10275,6 +15163,7 @@
         c.dispose();    
         this.chrome = c;
     },
+    
     /**
      * Method: showChrome
      * show the chrome on an element.  This creates the chrome if necessary.
@@ -10288,7 +15177,7 @@
      * element - {HTMLElement} the element to show the chrome on.
      */
     showChrome: function(element) {
-        element = $(element);
+        element = document.id(element);
         if (!this.chrome) {
             this.makeChrome(element);
         }
@@ -10297,6 +15186,7 @@
             element.adopt(this.chrome);
         }
     },
+    
     /**
      * Method: hideChrome
      * removes the chrome from the DOM.  If you do this, you can't
@@ -10307,20 +15197,13 @@
             this.chrome.dispose();
         }
     },
+    
     resizeChrome: function(o) {
-        if (this.chrome && Browser.Engine.trident) {
-            this.chrome.setContentBoxSize($(o).getBorderBoxSize());
+        if (this.chrome && Browser.Engine.trident4) {
+            this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
         }
-    }
-});
-
-/**
- * Class: Jx.Addable
- * A mix-in class that provides a helper function that allows an object
- * to be added to an existing element on the page.
- */
-Jx.Addable = new Class({
-    addable: null,
+    },
+    
     /**
      * Method: addTo
      * adds the object to the DOM relative to another element.  If you use
@@ -10339,22 +15222,2228 @@
      * the object itself, which is useful for chaining calls together
      */
     addTo: function(reference, where) {
-        $(this.addable || this.domObj).inject(reference,where);
-        this.fireEvent('addTo',this);
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            ref = document.id(reference);
+            el.inject(ref,where);
+            this.fireEvent('addTo',this);            
+        }
         return this;
     },
     
     toElement: function() {
-        return this.addable || this.domObj;
+        return this.domObj;
+    },
+    
+    /**
+     * APIMethod: processTemplate
+     * This function pulls the needed elements from a provided template
+     * 
+     * Parameters:
+     * template - the template to use in grabbing elements
+     * classes - an array of class names to use in grabbing elements
+     * container - the container to add the template into
+     * 
+     * Returns:
+     * 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)){
+            element = container.set('html',template);
+        } else {
+            element = new Element('div',{html:template,styles:{border:'20px solid red'}});
+        }
+        classes.each(function(klass){
+            var el = element.getElement('.'+klass);
+            if ($defined(el)){
+                h.set(klass,el);
+            }
+        });
+        
+        return h;
+        
+    },
+    
+    /**
+     * Method: generateId
+     * Used to generate a unique ID for Jx Widgets.
+     */
+    generateId: function(prefix){
+        prefix = (prefix) ? prefix : 'jx-';
+        var uid = $uid(this);
+        delete this.uid;
+        return prefix + uid;
+    },
+    
+    remove: function(){
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            el.dispose();
+        }
+    },
+    
+    cleanup: function(){
+        if ($defined(this.domObj)) {
+            this.domObj.destroy();
+        }
+        if ($defined(this.addable)) {
+            this.addable.destroy();
+        }
+        if ($defined(this.domA)) {
+            this.domA.destroy();
+        }
+        this.parent();
+    },
+    
+    render: $empty
+});
+
+
+/**
+ * It seems AIR never returns an XHR that "fails" by not finding the 
+ * appropriate file when run in the application sandbox and retrieving a local
+ * file. This affects Jx.ContentLoader in that a "failed" event is never fired. 
+ * 
+ * To fix this, I've added a timeout that waits about 10 seconds or so in the code above
+ * for the XHR to return, if it hasn't returned at the end of the timeout, we cancel the
+ * XHR and fire the failure event.
+ *
+ * This code only gets added if we're in AIR.
+ */
+if (Jx.isAir){
+    Jx.Widget.implement({
+        /**
+         * Method: checkRequest
+         * Is fired after a delay to check the request to make sure it's not
+         * failing in AIR.
+         */
+        checkRequest: function(){
+            if (this.req.xhr.readyState === 1) {
+                //we still haven't gotten the file. Cancel and fire the
+                //failure
+                $clear(this.reqTimeout);
+                this.req.cancel();
+                this.contentIsLoaded = true;
+                this.fireEvent('contentLoadFailed', this);
+            }
+        }
+    });
+}
+
+Jx.Selection = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Selection',
+    
+    options: {
+        /**
+         * Option: eventToFire
+         * Allows the developer to change the event that is fired in case one
+         * object is using multiple selectionManager instances.
+         */
+        eventToFire: { 
+            select: 'select',
+            unselect: 'unselect'
+        },
+        /**
+         * APIProperty: selectClass
+         * 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.
+         */
+        selectMode: 'single',
+        /**
+         * Option: selectToggle
+         * {Boolean} Default true.  Selection of a selected item will unselect
+         * it.
+         */
+        selectToggle: true,
+        /**
+         * Option: minimumSelection
+         * {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.
+         */
+        minimumSelection: 0
+    },
+    
+    selection: null,
+    
+    init: function () {
+        this.selection = [];
+    },
+    
+    defaultSelect: function(item) {
+        if (this.selection.length < this.options.minimumSelection) {
+            this.select(item);
+        }
+    },
+    
+    select: function (item) {
+        if (this.options.selectMode === 'multiple') {
+            if (this.selection.contains(item)) {
+                this.unselect(item);
+            } else {
+                document.id(item).addClass(this.options.selectClass);
+                this.selection.push(item);
+                this.fireEvent(this.options.eventToFire.select, item, this);
+            }
+        } else if (this.options.selectMode == 'single') {
+            if (!this.selection.contains(item)) {
+                document.id(item).addClass(this.options.selectClass);
+                this.selection.push(item);
+                if (this.selection.length > 1) {
+                    this.unselect(this.selection[0]);
+                }
+            } else {
+                this.unselect(item);
+            }
+            this.fireEvent(this.options.eventToFire.select, item, this);
+        }
+    },
+    
+    unselect: function (item) {
+        if (this.selection.contains(item) && 
+            this.selection.length > this.options.minimumSelection) {
+            document.id(item).removeClass(this.options.selectClass);
+            this.selection.erase(item);
+            this.fireEvent(this.options.eventToFire.unselect, item, this);
+        }
+    },
+    
+    selected: function () {
+        return this.selection;
+    },
+    
+    isSelected: function(item) {
+        return this.selection.contains(item);
     }
-});// $Id: button.js 424 2009-05-12 12:51:44Z pagameba $
+
+});// $Id: $
 /**
+ * Class: Jx.List
+ * 
+ * Manage a list of DOM elements and provide an API and events for managing
+ * those items within a container.  Works with Jx.Selection to manage
+ * selection of items in the list.  You have two options for managing
+ * selections.  The first, and default, option is to specify select: true
+ * in the constructor options and any of the <Jx.Selection> options as well.
+ * This will create a default Jx.Selection object to manage selections.  The
+ * second option is to pass a Jx.Selection object as the third constructor
+ * argument.  This allows sharing selection between multiple lists.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * add - fired when an item is added
+ * remove - fired when an item is removed
+ * mouseenter - fired when the user mouses over an element
+ * mouseleave - fired when the user mouses out of an element
+ * select - fired when an item is selected
+ * unselect - fired when an item is selected
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.List = new Class({
+    Family: 'Jx.List',
+    Extends: Jx.Object,
+    parameters: ['container', 'options', 'selection'],
+    /* does this object own the selection object (and should clean it up) */
+    ownsSelection: false,
+    /**
+     * APIProperty: itemContainer
+     * the element that will contain items as they are added
+     */
+    container: null,
+    /**
+     * APIProperty: selection
+     * <Jx.Selection> a selection object if selection is enabled
+     */
+    selection: null,
+    options: {
+        /**
+         * APIProperty: items
+         * an array of items to add to the list right away
+         */
+        items: null,
+        /**
+         * Option: hover
+         * {Boolean} default true.  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
+         */
+        hover: false,
+        /**
+         * APIProperty: hoverClass
+         * the CSS class name to add to the wrapper element when the mouse is
+         * over an item
+         */
+        hoverClass: 'jxHover',
+
+        /**
+         * Option: press
+         * {Boolean} default true.  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
+         */
+        press: false,
+        /**
+         * APIProperty: pressedClass
+         * the CSS class name to add to the wrapper element when the mouse is
+         * down on an item
+         */
+        pressClass: 'jxPressed',
+        
+        /**
+         * Option: select
+         * {Boolean} default true.  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>
+         */
+        select: false
+    },
+    
+    init: function() {
+        this.container = document.id(this.options.container);
+        this.container.store('jxList', this);
+        
+        var target = this;
+        this.bound = {
+            mousedown: function() {
+                this.addClass(target.options.pressClass);
+                target.fireEvent('mousedown', this, target);
+            },
+            mouseup: function() {
+                this.removeClass(target.options.pressClass);
+                target.fireEvent('mouseup', this, target);
+            },
+            mouseenter: function() {
+                this.addClass(target.options.hoverClass);
+                target.fireEvent('mouseenter', this, target);
+            },
+            mouseleave: function() {
+                this.removeClass(target.options.hoverClass);
+                target.fireEvent('mouseleave', this, target);
+            }
+        };
+        if (this.options.selection) {
+            this.selection = this.options.selection;
+        } else if (this.options.select) {
+            this.selection = new Jx.Selection(this.options);
+            this.ownsSelection = true;
+        }
+            
+        if (this.selection) {
+            this.bound.click = function () {
+                target.selection.select(this, target);
+            };
+            this.selection.addEvents({
+                select: function(item) {
+                    target.fireEvent('select', item);
+                },
+                unselect: function(item) {
+                    target.fireEvent('select', item);
+                }
+            })
+        }
+        if ($defined(this.options.items)) {
+            this.add(this.options.items);
+        }
+    },
+    
+    cleanup: function() {
+        this.container.getChildren().each(function(item){
+            this.remove(item);
+        }, this);
+        this.bound = null;
+        if (this.ownsSelection && this.selection) {
+            this.selection.destroy();
+        }
+        this.container.eliminate('jxList');
+    },
+    
+    /**
+     * APIMethod: add
+     * add an item to the list of items at the specified position
+     *
+     * Parameters:
+     * item - {mixed} the object to add, a DOM element or an
+     * object that provides a getElement method.  An array of items may also
+     * be provided.  All items are inserted sequentially at the indicated
+     * position.
+     * position - {mixed} optional, the position to add the element, either
+     * an integer position in the list or another item to place this item after
+     */
+    add: function(item, position) {
+        if ($type(item) == 'array') {
+            item.each(function(what){ this.add(what, position); }.bind(this) );
+            return;
+        }
+        /* the element being wrapped */
+        var el = document.id(item);
+        if (el) {
+            if (this.options.press && this.options.pressClass) {
+                el.addEvents({
+                    mousedown: this.bound.mousedown,
+                    mouseup: this.bound.mouseup
+                });
+            }
+            if (this.options.hover && this.options.hoverClass) {
+                el.addEvents({
+                    mouseenter: this.bound.mouseenter,
+                    mouseleave: this.bound.mouseleave
+                });
+            }
+            if (this.selection) {
+                el.addEvents({
+                    click: this.bound.click
+                });
+            }
+            if ($defined(position)) {
+                if ($type(position) == 'integer') {
+                    if (position < this.container.childNodes.length) {
+                        el.inject(this.container.childNodes[position],' before');
+                    } else {
+                        el.inject(this.container, 'bottom');
+                    }
+                } else if (this.container.hasChild(position)) {
+                    el.inject(position,'after');
+                }
+                this.fireEvent('add', item, this);
+            } else {
+                el.inject(this.container, 'bottom');
+                this.fireEvent('add', item, this);
+            }
+            if (this.selection) {
+                this.selection.defaultSelect(el);
+            }
+        }
+    },
+    /**
+     * Method: remove
+     * remove an item from the list of items
+     *
+     * Parameters:
+     * item - {mixed} the item to remove or the index of the item to remove.  An
+     * array of items may also be provided.
+     *
+     * Returns:
+     * {mixed} the item that was removed or null if the item is not a member
+     * of this list.
+     */
+    remove: function(item) {
+        if (this.container.hasChild(item)) {
+            this.unselect(item, true);
+            document.id(item).dispose();
+            document.id(item).removeEvents(this.bound);
+            this.fireEvent('remove', item, this);
+            return item;
+        }
+        return null;
+    },
+    /**
+     * Method: replace
+     * replace one item with another
+     *
+     * Parameters:
+     * item - {mixed} the item to replace or the index of the item to replace
+     * withItem - {mixed} the object, DOM element, Jx.Object or an object 
+     * implementing getElement to add
+     *
+     * Returns:
+     * {mixed} the item that was removed
+     */
+    replace: function(item, withItem) {
+        if (this.container.hasChild(item)) {
+            this.add(withItem, item);
+            this.remove(item);
+        }
+    },
+    /**
+     * APIMethod: indexOf
+     * find the index of an item in the list
+     *
+     * Parameters:
+     * item - {mixed} the object, DOM element, Jx.Object or an object 
+     * implementing getElement to find the index of
+     * 
+     * Returns:
+     * {integer} the position of the item or -1 if not found
+     */
+    indexOf: function(item) {
+        return $A(this.container.childNodes).indexOf(item);
+    },
+    /**
+     * APIMethod: count
+     * returns the number of items in the list
+     */
+    count: function() {
+        return this.container.childNodes.length;
+    },
+    /**
+     * APIMethod: items
+     * returns an array of the items in the list
+     */
+    items: function() {
+        return $A(this.container.childNodes);
+    },
+    /**
+     * APIMethod: each
+     * applies the supplied function to each item
+     *
+     * Parameters:
+     * func - {function} the function to apply, it will receive the item and
+     * index of the item as parameters
+     */
+    each: function(f) {
+        $A(this.container.childNodes).each(f);
+    },
+    /**
+     * APIMethod: select
+     * select an item
+     *
+     * Parameters:
+     * item - {mixed} the object to select, a DOM element, a Jx.Object, or an
+     * object that provides a getElement method.  An array of items may also be
+     * provided.
+     */
+    select: function(item) {
+        if (this.selection) {
+            this.selection.select(item);
+        }
+    },
+    /**
+     * APIMethod: unselect
+     * unselect an item or items
+     *
+     * Parameters:
+     * item - {mixed} the object to select, a DOM element, a Jx.Object, or an
+     * object that provides a getElement method.  An array of elements may also
+     * be provided.
+     * force - {Boolean} force deselection even if this violates the minimum
+     * selection constraint (used internally when removing items)
+     */
+    unselect: function(item, force) {
+        if (this.selection) {
+            this.selection.unselect(item);
+        }
+    },
+    /**
+     * APIMethod: selected
+     * returns the selected item or items
+     *
+     * Returns:
+     * {mixed} the selected item or an array of selected items
+     */
+    selected: function() {
+        return this.selection ? this.selection.selected : [];
+    },
+    /**
+     * APIMethod: empty
+     * clears all of the items from the list
+     */
+    empty: function(){
+        this.container.getChildren().each(function(item){
+            this.remove(item);
+        }, this);
+    }
+
+});// $Id: $
+/**
+ * Class: Jx.Compare
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Class that holds functions for doing comparison operations.
+ * This class requires the clientside Date() extensions (deps/date.js).
+ * 
+ * notes:
+ * Each function that does a comparison returns
+ * 
+ * 0 - if equal.
+ * 1 - if the first value is greater that the second.
+ * -1 - if the first value is less than the second.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Compare = new Class({
+    Extends: Jx.Object,
+	
+    options: { separator: '.' },
+	
+    /**
+     * APIMethod: alphanumeric
+     * Compare alphanumeric variables. This is case sensitive
+     * 
+     * Parameters:
+     * a - a value
+     * b - another value
+     */
+    alphanumeric: function (a, b) {
+        return (a === b) ? 0 :(a < b) ? -1 : 1;
+    },
+	
+    /**
+     * APIMethod: numeric
+     * Compares numbers
+     *
+     * Parameters:
+     * a - a number
+     * b - another number
+     */
+    numeric: function (a, b) {
+        return this.alphanumeric(this.convert(a), this.convert(b));
+    },
+	
+    /**
+     * Method: _convert
+     * Normalizes numbers relative to the separator.
+     * 
+     * Parameters:
+     * val - the number to normalize
+     * 
+     * Returns:
+     * the normalized value
+     */
+    convert: function (val) {
+        if (Jx.type(val) === 'string') {
+            val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g, "$1").replace(new RegExp("[^\\\d" + this.options.separator + "]", "g"), '').replace(/,/, '.')) || 0;
+        }
+        return val || 0;
+    },
+	
+    /**
+     * APIMethod: ignorecase
+     * Compares to alphanumeric strings without regard to case.
+     * 
+     * Parameters:
+     * a - a value
+     * b - another value
+     */
+    ignorecase: function (a, b) {
+        return this.alphanumeric(("" + a).toLowerCase(), ("" + b).toLowerCase());
+    },
+	
+    /**
+     * APIMethod: currency
+     * Compares to currency values.
+     * 
+     * Parameters:
+     * a - a currency value without the $
+     * b - another currency value without the $
+     */
+    currency: function (a, b) {
+        return this.numeric(a, b);
+    },
+	
+    /**
+     * APIMethod: date
+     * Compares 2 date values (either a string or an object)
+     * 
+     * Parameters:
+     * a - a date value 
+     * b - another date value
+     */
+    date: function (a, b) {
+        var x = new Date().parse(a);
+        var y = new Date().parse(b);
+        return (x < y) ? -1 : (x > y) ? 1 : 0;
+    },
+    /**
+     * APIMethod: boolean
+     * Compares 2 bolean values 
+     * 
+     * Parameters:
+     * a - a boolean value 
+     * b - another boolean value
+     */
+    'boolean': function (a, b) {
+        return (a === true && b === false) ? -1 : (a === b) ? 0 : 1;
+    }
+	
+});// $Id: $
+/**
+ * Class: Jx.Sort Base class for all of the sorting algorithm classes.
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Events: 
+ * onStart() - called when the sort starts 
+ * onEnd() - called when the sort stops
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Sort = new Class({
+
+    Family : 'Jx.Sort',
+
+    Extends : Jx.Object,
+
+    options : {
+        /**
+         * Option: timeIt
+         * whether to time the sort
+         */
+        timeIt : false,
+        /**
+         * Event: onStart
+         */
+        onStart : $empty,
+        /**
+         * Event: onEnd
+         */
+        onEnd : $empty
+    },
+    
+    /**
+     * Property: timer
+     * holds the timer instance
+     */
+    timer : null,
+    /**
+     * Property: data
+     * The data to sort
+     */
+    data : null,
+    /**
+     * Property: Comparator
+     * The comparator to use in sorting
+     */
+    comparator : $empty,
+    /**
+     * Property: col
+     * The column to sort by
+     */
+    col : null,
+    
+    parameters: ['data','fn','col','options'],
+
+    /**
+     * APIMethod: init
+     */
+    init : function () {
+        this.parent();
+        if (this.options.timeIt) {
+            this.addEvent('start', this.startTimer.bind(this));
+            this.addEvent('stop', this.stopTimer.bind(this));
+        }
+        this.data = this.options.data;
+        this.comparator = this.options.fn;
+        this.col = this.options.col;
+    },
+
+    /**
+     * APIMethod: sort 
+     * Actually does the sorting. Must be overridden by subclasses.
+     */
+    sort : $empty,
+
+    /**
+     * Method: startTimer 
+     * Saves the starting time of the sort
+     */
+    startTimer : function () {
+        this.timer = new Date();
+    },
+
+    /**
+     * Method: stopTimer 
+     * Determines the time the sort took.
+     */
+    stopTimer : function () {
+        this.end = new Date();
+        this.dif = this.timer.diff(this.end, 'ms');
+    },
+
+    /**
+     * APIMethod: setData 
+     * sets the data to sort
+     * 
+     * Parameters: 
+     * data - the data to sort
+     */
+    setData : function (data) {
+        if ($defined(data)) {
+            this.data = data;
+        }
+    },
+
+    /**
+     * APIMethod: setColumn 
+     * Sets the column to sort by
+     * 
+     * Parameters: 
+     * col - the column to sort by
+     */
+    setColumn : function (col) {
+        if ($defined(col)) {
+            this.col = col;
+        }
+    },
+
+    /**
+     * APIMethod: setComparator
+     * Sets the comparator to use in sorting
+     * 
+     * Parameters: 
+     * fn - the function to use as the comparator
+     */
+    setComparator : function (fn) {
+        this.comparator = fn;
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Store 
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is the base store. It keeps track of data. It
+ * allows adding, deleting, iterating, sorting etc...
+ * 
+ * Events: onLoadFinished(store) - fired when the store finishes loading the
+ * data onLoadError(store,data) - fired when there is an error loading the data
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store = new Class({
+
+    Extends : Jx.Object,
+
+    Family : "Jx.Store",
+
+    options : {
+        /**
+         * Option: id
+         * the identifier for this store
+         */
+        id : null,
+        /**
+         * Option: columns
+         * an array listing the columns of the store in order of their 
+         * appearance in the data object formatted as an object 
+         *      {name: 'column name', type: 'column type'} 
+         * where type can be one of alphanumeric, numeric, date, boolean, 
+         * or currency.
+         */
+        columns : [], 
+        /**
+         * Option: defaultSort
+         * The default sorting type, currently set to merge but can be any of the
+         * sorters available
+         */
+        defaultSort : 'merge',
+        /**
+         * Option: separator
+         * The separator to pass to the comparator
+         * constructor (<Jx.Compare>) - defaults to '.'
+         */
+        separator : '.',
+        /**
+         * Option: sortCols
+         * An array of columns to sort by arranged in the order you want 
+         * them sorted.
+         */
+        sortCols : [], 
+        /**
+         * Event: onLoadFinished(store)
+         * event for a completed, successful data load
+         */
+        onLoadFinished : $empty,
+        /**
+         * Event: onLoadError(store,data)
+         * event for an unsuccessful load
+         */
+        onLoadError : $empty, 
+        /**
+         * Event: onColumnChanged
+         * event fired for changes to a column
+         */
+        onColumnChanged : $empty,
+        /**
+         * Option: paginate
+         * Set to true to enable pagination
+         */
+        paginate: false,
+        /**
+         * Option: pageSize
+         * Sets the size of each page. Only used if paginate is true.
+         */
+        pageSize: 0
+   
+    },
+
+    /**
+     * Property: sorters
+     * an object listing the different sorters available
+     */
+    sorters : {
+        quick : "Quicksort",
+        merge : "Mergesort",
+        heap : "Heapsort",
+        'native' : "Nativesort"
+    },
+    /**
+     * Property: data
+     * Holds the data for this store
+     */
+    data : null,
+    /**
+     * Property: index
+     * Holds the current position of the store relative to the data and the pageIndex.
+     * Zero-based index.
+     */
+    index : 0,
+    /**
+     * Property: pageIndex
+     * Holds the current page index
+     */
+    pageIndex: 0,
+    /**
+     * Property: dirty
+     * Tells us if the store is dirty
+     */
+    dirty : false,
+    /**
+     * Property: id
+     * The id of this store.
+     */
+    id : null,
+    /**
+     * Property: loaded
+     * Tells whether the store has been loaded or not
+     */
+    loaded: false,
+
+    /**
+     * APIMethod: load 
+     * Loads data into the store.
+     * 
+     * Parameters: 
+     * data - the data to load
+     */
+    load : function (data) {
+        if ($defined(data)) {
+            this.loaded = false;
+            this.processData(data);
+            
+        } else {
+            this.loaded = false;
+        }
+    },
+
+    /**
+     * APIMethod: hasNext 
+     * Determines if there are more records past the current
+     * one.
+     * 
+     * Returns: true | false (Null if there's a problem)
+     */
+    hasNext : function () {
+        if ($defined(this.data)) {
+            if (this.index < this.data[this.pageIndex].length - 1) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: hasPrevious 
+     * Determines if there are records before the current
+     * one.
+     * 
+     * Returns: true | false
+     */
+    hasPrevious : function () {
+        if ($defined(this.data)) {
+            if (this.index > 0) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: valid 
+     * Tells us if the current index has any data (i.e. that the
+     * index is valid).
+     * 
+     * Returns: true | false
+     */
+    valid : function () {
+        return ($defined(this.data[this.pageIndex][this.index]));
+    },
+
+    /**
+     * APIMethod: next 
+     * Moves the store to the next record
+     * 
+     * Returns: nothing | null if error
+     */
+    next : function () {
+        if ($defined(this.data)) {
+            this.index++;
+            if (this.index === this.data[this.pageIndex].length) {
+                this.index = this.data[this.pageIndex].length - 1;
+            }
+            this.fireEvent('storeMove', this);
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: previous 
+     * moves the store to the previous record
+     * 
+     * Returns: nothing | null if error
+     * 
+     */
+    previous : function () {
+        if ($defined(this.data)) {
+            this.index--;
+            if (this.index < 0) {
+                this.index = 0;
+            }
+            this.fireEvent('storeMove', this);
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: first 
+     * Moves the store to the first record
+     * 
+     * Returns: nothing | null if error
+     * 
+     */
+    first : function () {
+        if ($defined(this.data)) {
+            this.index = 0;
+            this.fireEvent('storeMove', this);
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: last 
+     * Moves to the last record in the store
+     * 
+     * Returns: nothing | null if error
+     */
+    last : function () {
+        if ($defined(this.data)) {
+            this.index = this.data[this.pageIndex].length - 1;
+            this.fireEvent('storeMove', this);
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: count 
+     * Returns the number of records in the store
+     * 
+     * Returns: an integer indicating the number of records in the store or null
+     * if there's an error
+     */
+    count : function () {
+        if ($defined(this.data)) {
+            return this.data[this.pageIndex].length;
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: getPosition 
+     * Tells us where we are in the store
+     * 
+     * Returns: an integer indicating the position in the store or null if
+     * there's an error
+     */
+    getPosition : function () {
+        if ($defined(this.data[this.pageIndex])) {
+            return this.index;
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: moveTo 
+     * Moves the index to a specific record in the store
+     * 
+     * Parameters: 
+     * index - the record to move to
+     * 
+     * Returns: true - if successful false - if not successful null - on error
+     */
+    moveTo : function (index) {
+        if ($defined(this.data) && index >= 0 && index < this.data[this.pageIndex].length) {
+            this.index = index;
+            this.fireEvent('storeMove', this);
+            return true;
+        } else if (!$defined(this.data)) {
+            return null;
+        } else {
+            return false;
+        }
+    },
+
+    /**
+     * APIMethod: get 
+     * Retrieves the data for a specific column of the current
+     * record
+     * 
+     * Parameters: 
+     * col - the column to get (either an integer or a string)
+     * 
+     * Returns: the data in the column or null if the column doesn't exist
+     */
+    get : function (col) {
+        if ($defined(this.data)) {
+            col = this.resolveCol(col);
+            var h = this.data[this.pageIndex][this.index];
+            if (h.has(col.name)) {
+                return h.get(col.name);
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    },
+
+    /**
+     * APIMethod: set 
+     * Puts a value into a specific column of the current record and
+     * sets the dirty flag.
+     * 
+     * Parameters: 
+     * column - the column to put the value in value - the data to put
+     * into the column
+     * 
+     * returns: nothing | null if an error
+     */
+    set : function (column, value) {
+        if ($defined(this.data)) {
+            // set the column to the value and set the dirty flag
+
+            if (Jx.type(column) === 'number' || Jx.type(column) === 'string') {
+                column = this.resolveCol(column);
+            }
+
+            var oldValue = this.data[this.pageIndex][this.index].get(column.name);
+            this.data[this.pageIndex][this.index].set(column.name, value);
+            this.data[this.pageIndex][this.index].set('dirty', true);
+            this.fireEvent('columnChanged', [ this.index, column, oldValue, value ]);
+        } else {
+            return null;
+        }
+    },
+    
+    /**
+     * APIMethod: refresh 
+     * Sets new data into the store
+     * 
+     * Parameters: 
+     * data - the data to set 
+     * reset - flag as to whether to reset the index to 0
+     * 
+     * Returns: nothing or null if no data is passed
+     */
+    refresh : function (data, reset) {
+        if ($defined(data)) {
+            this.processData(data);
+            if (reset) {
+                this.index = 0;
+            }
+        } else {
+            return null;
+        }
+    },
+    
+    /**
+     * APIMethod: isDirty 
+     * Tells us if the store is dirty and needs to be saved
+     * 
+     * Returns: true | false | null on error
+     */
+    isDirty : function () {
+        if ($defined(this.data)) {
+            var dirty = false;
+            this.data.each(function (row) {
+                if (this.isRowDirty(row)) {
+                    dirty = true;
+                    return;
+                }
+            }, this);
+            return dirty;
+        } else {
+            return null;
+        }
+    },
+    
+    /**
+     * APIMethod: newRow 
+     * Adds a new row to the store. It can either be empty or made
+     * from an array of data
+     * 
+     * Parameters: 
+     * data - data to use in the new row (optional)
+     */
+    newRow : function (data) {
+        // check if array is not defined
+        if (!$defined(this.data)) {
+            // if not, then create a new array
+            this.data = [];
+            this.data[this.pageIndex] = [];
+        }
+        
+        var d;
+        
+        if (!$defined(data)) {
+            d = new Hash();
+        } else {
+            var t = Jx.type(data);
+            switch (t) {
+            case 'object':
+                d = new Hash(data);
+                break;
+            case 'hash':
+                d = data;
+                break;
+            }
+        }
+        d.set('dirty', true);
+        this.data[this.pageIndex][this.data[this.pageIndex].length] = d;
+        this.index = this.data[this.pageIndex].length - 1;
+        this.fireEvent('newrow', this);
+    },
+    
+    /**
+     * APIMethod: sort 
+     * Runs the sorting and grouping
+     * 
+     * Parameters: 
+     * cols - Optional. An array of columns to sort/group by 
+     * 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.count()) {
+        
+            this.fireEvent('sortStart', this);
+            
+            var c;
+            if ($defined(cols) && Jx.type(cols) === 'array') {
+                c = this.options.sortCols = cols;
+            } else if ($defined(cols) && Jx.type(cols) === 'string') {
+                this.options.sortCols = [];
+                this.options.sortCols.push(cols);
+                c = this.options.sortCols;
+            } else if ($defined(this.options.sortCols)) {
+                c = this.options.sortCols;
+            } else {
+                return null;
+            }
+            
+            this.sortType = sort;
+            // first sort on the first array item
+            this.data[this.pageIndex] = this.doSort(c[0], sort, this.data[this.pageIndex], true);
+        
+            if (c.length > 1) {
+                this.data[this.pageIndex] = this.subSort(this.data[this.pageIndex], 0, 1);
+            }
+        
+            if ($defined(dir) && dir === 'desc') {
+                this.data[this.pageIndex].reverse();
+            }
+        
+            this.fireEvent('sortFinished', this);
+        }
+    },
+    
+    /**
+     * Method: subSort 
+     * Does the actual group sorting.
+     * 
+     * Parameters: 
+     * data - what to sort 
+     * groupByCol - the column that determines the groups 
+     * sortCol - the column to sort by
+     * 
+     * returns: the result of the grouping/sorting
+     */
+    subSort : function (data, groupByCol, sortByCol) {
+        
+        if (sortByCol >= this.options.sortCols.length) {
+            return data;
+        }
+        /**
+         *  loop through the data array and create another array with just the
+         *  items for each group. Sort that sub-array and then concat it 
+         *  to the result.
+         */
+        var result = [];
+        var sub = [];
+        
+        var groupCol = this.options.sortCols[groupByCol];
+        var sortCol = this.options.sortCols[sortByCol];
+    
+        var group = data[0].get(groupCol);
+        this.sorter.setColumn(sortCol);
+        for (var i = 0; i < data.length; i++) {
+            if (group === (data[i]).get(groupCol)) {
+                sub.push(data[i]);
+            } else {
+                // sort
+    
+                if (sub.length > 1) {
+                    result = result.concat(this.subSort(this.doSort(sortCol, this.sortType, sub, true), groupByCol + 1, sortByCol + 1));
+                } else {
+                    result = result.concat(sub);
+                }
+            
+                // change group
+                group = (data[i]).get(groupCol);
+                // clear sub
+                sub.empty();
+                // add to sub
+                sub.push(data[i]);
+            }
+        }
+        
+        if (sub.length > 1) {
+            this.sorter.setData(sub);
+            result = result.concat(this.subSort(this.doSort(sortCol, this.sortType, sub, true), groupByCol + 1, sortByCol + 1));
+        } else {
+            result = result.concat(sub);
+        }
+        
+        //this.data = result;
+        
+        return result;
+    },
+    
+    /**
+     * Method: doSort 
+     * Called to change the sorting of the data
+     * 
+     * Parameters: 
+     * col - the column to sort by 
+     * sort - the kind of sort to use (see list above) 
+     * data - the data to sort (leave blank or pass null to sort data
+     * existing in the store) 
+     * ret - flag that tells the function whether to pass
+     * back the sorted data or store it in the store 
+     * options - any options needed to pass to the sorter upon creation
+     * 
+     * returns: nothing or the data depending on the value of ret parameter.
+     */
+    doSort : function (col, sort, data, ret, options) {
+        options = {} || options;
+        
+        sort = (sort) ? this.sorters[sort] : this.sorters[this.options.defaultSort];
+        data = data ? data : this.data;
+        ret = ret ? true : false;
+        
+        if (!$defined(this.comparator)) {
+            this.comparator = new Jx.Compare({
+                separator : this.options.separator
+            });
+        }
+        
+        this.col = col = this.resolveCol(col);
+        
+        var fn = this.comparator[col.type].bind(this.comparator);
+        if (!$defined(this.sorter)) {
+            this.sorter = new Jx.Sort[sort](data, fn, col.name, options);
+        } else {
+            this.sorter.setComparator(fn);
+            this.sorter.setColumn(col.name);
+            this.sorter.setData(data);
+        }
+        var d = this.sorter.sort();
+        
+        if (ret) {
+            return d;
+        } else {
+            this.data = d;
+        }
+    },
+    
+    /**
+     * Method: isRowDirty 
+     * Helps determine if a row is dirty
+     * 
+     * Parameters: 
+     * row - the row to check
+     * 
+     * Returns: true | false
+     */
+    isRowDirty : function (row) {
+        if (row.has('dirty')) {
+            return row.get('dirty');
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: resolveCol 
+     * Determines which array index this column refers to
+     * 
+     * Parameters: 
+     * col - a number referencing a column in the store
+     * 
+     * Returns: the name of the column
+     */
+    resolveCol : function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.options.columns[col];
+        } else if (t === 'string') {
+            this.options.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;
+    },
+    
+    /**
+     * Method: processData 
+     * Processes the data passed into the function into the store.
+     * 
+     * Parameters: 
+     * data - the data to put into the store
+     */
+    processData : function (data) {
+        this.loaded = false;
+        this.fireEvent('preload', [ this, data ]);
+    
+        if (!$defined(this.data)) {
+            this.data = [];
+            this.data[this.pageIndex] = [];
+        }
+        
+        if ($defined(data)) {
+            this.data[this.pageIndex].empty();
+            var type = Jx.type(data);
+            // is this an array?
+            if (type === 'array') {
+                if (this.options.paginate) {
+                    var i = 1;
+                    var p = 0;
+                    data.each(function (item) {
+                        this.data[p].include(new Hash(item));
+                        i++;
+                        if (i === this.options.pageSize) {
+                            i = 1;
+                            p++;
+                            this.data[p] = [];
+                        }
+                    }, this);
+                } else {
+                    data.each(function (item, index) {
+                        this.data[this.pageIndex].include(new Hash(item));
+                    }, this);
+                }
+                
+                this.loaded = true;
+                this.fireEvent('loadFinished', this);
+            } else {
+                this.fireEvent('loadError', [this, data]);
+            }
+            
+        } else {
+            this.loaded = false;
+            this.fireEvent('loadError', [ this, data ]);
+        }
+    },
+    
+    /**
+     * APIMethod: getColumns
+     * Allows retrieving the columns array
+     */
+    getColumns: function () {
+        return this.options.columns;
+    },
+    
+    /**
+     * 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
+     * 
+     * Parameters:
+     * column - the name of the column to search by
+     * value - the value to look for
+     * inPage - flag telling method whether to search only in the current page.
+     *          Defaults to true.
+     */
+    findByColumn: function (column, value, inPage) {
+        
+        inPage = $defined(inPage) ? inPage : true;
+        
+        if (!$defined(this.comparator)) {
+            this.comparator = new Jx.Compare({
+                separator : this.options.separator
+            });
+        }
+        
+        column = this.resolveCol(column);
+        
+        var fn = this.comparator[column.type].bind(this.comparator);
+        
+        
+        var i = 0;
+        var index = null;
+        if (inPage) {
+            this.data[this.pageIndex].each(function (record) {
+                if (fn(record.get(column.name), value) === 0) {
+                    index = i;
+                }
+                i++;
+            }, this);
+        } else {
+            this.data.each(function (page) {
+                page.each(function (record) {
+                    if (fn(record.get(column.name), value) === 0) {
+                        index = i;
+                    }
+                    i++;
+                }, this);
+            }, this);
+        }
+        return index;
+    },
+    
+    /**
+     * APIMethod: getRowObject
+     * Allows the user to get all of the data for the current row as an object.
+     * 
+     */
+    getRowObject: function () {
+        return this.data[this.pageIndex][this.index].getClean();
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Store.Remote
+ * 
+ * Extends: <Jx.Store>
+ * 
+ * This class adds the ability to load/save data remotely.
+ *  
+ * Events:
+ * onSaveSuccess() - event fired when all saving happens successfully
+ * onSaveError() - event fired when the server returns an error during saving
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Remote = new Class({
+
+    Extends : Jx.Store,
+
+    options : {
+        /**
+         * Option: dataUrl
+         * The URL to get data from
+         */
+        dataUrl : '',
+        /**
+         * Option: autoSave
+         * Whether to automatically save data changes
+         */
+        autoSave : false,
+        /**
+         * Option: saveUrl
+         * The URL to send data to to be saved
+         */
+        saveUrl : ''
+    },
+
+    saveCount : 0,
+    continueSaving : true,
+    /**
+     * APIMethod: init
+     * Creates the Remote Store.
+     */
+    init : function () {
+        this.parent();
+        this.addEvent('newrow', this.onNewRow.bind(this));
+        this.addEvent('columnChanged', this.saveRow.bind(this));
+    },
+
+    /**
+     * APIMethod: load
+     * Used to load data either locally or remote
+     * 
+     * Parameters:
+     * params - an object of params to pass to load. These will be sent in the request. 
+     */
+    load : function (params) {
+        this.params = $defined(params) ? params : {};
+        this.remoteLoad(params);
+    },
+
+    /** 
+     * APIMethod: refresh
+     * Override of base function <Jx.Store#refresh>. Allow refreshing data from the server
+     * 
+     * Parameters:
+     * params - an object of params to pass to load. These will be sent in the request. 
+     * reset - whether to reset the counter after the refresh
+     */
+    refresh : function (params, reset) {
+        //Call the load function to get the data
+        //from the server and reset the counter if requested
+        if ($defined(this.options.dataUrl)) {
+            this.params = $defined(params) ? params : this.params;
+            this.load(this.params);
+        } else {
+            return null;
+        }
+        if (reset) {
+            this.index = 0;
+        }
+    
+    },
+    
+    /** 
+     * APIMethod: save
+     * Determines if a row is dirty and needs to be saved to the server.
+     */
+    save : function () {
+        if ($defined(this.data)) {
+            //count how many rows to save
+            this.data.each(function (row, index) {
+                if (this.isRowDirty(row)) {
+                    this.saveCount++;
+                }
+            }, this);
+            //save all dirty rows
+            this.data.each(function (row, index) {
+                if (this.isRowDirty(row) && this.continueSaving) {
+                    row.erase('dirty');
+                    this.remoteSave(row);
+                }
+            }, this);
+        } else {
+            return null;
+        }
+    },
+    
+    saveRow: function (index, column, oldValue, newValue) {
+        if (this.options.autoSave) {
+            this.remoteSave(this.data[index]);
+        }
+    },
+    
+    /**
+     * Method: onNewRow
+     * Called when a new row is added (event listener). If autoSave is set, this will
+     * fire off the save method.
+     */
+    onNewRow : function () {
+        if (this.options.autoSave) {
+            this.save();
+        }
+    },
+    
+    /** 
+     * Method: remoteSave
+     * Actually does the work of sending the row to the server for saving.
+     * 
+     * Parameters:
+     * data - the row to save
+     */
+    remoteSave : function (data) {
+        //save the data passed in.
+        if (Jx.type(data) === 'hash' && this.continueSaving) {
+            // save it
+            var d = data.getClean();
+            var req = new Request.JSON({
+                data : d,
+                url : this.options.saveUrl,
+                onSuccess : this.processReturn.bind(this),
+                onFailure : this.handleSaveError.bind(this),
+                method : 'post'
+            });
+            req.send();
+        } else {
+            //don't save it
+            return false;
+        }
+    },
+    
+    /** 
+     * Method: remoteLoad
+     * Calls the server to get data
+     */
+    remoteLoad : function (params) {
+        params = $defined(params) ? params : {};
+        var req = new Request.JSON({
+            url : this.options.dataUrl,
+            data: params,
+            onSuccess : this.processGetReturn.bind(this),
+            onFailure : this.handleLoadError.bind(this),
+            method : 'get'
+        });
+        req.send();
+    },
+    
+    /**
+     * Method: processReturn
+     * processes the return from the save request
+     * 
+     * Parameters:
+     * data - decoded JSON object
+     * text - the JSON object as a string
+     */
+    processReturn : function (data, text) {
+        if ($defined(data) && $defined(data.success) && data.success === true) {
+            this.processSaveReturn(data.data);
+        } else {
+            this.handleSaveError(data, text);
+        }
+    },
+    /**
+     * Method: processGetReturn
+     * Processes returned data from the get request
+     * 
+     * Parameters:
+     * data - decoded JSON object
+     * text - the JSON object as a string
+     */
+    processGetReturn : function (data, text) {
+        if ($defined(data) && $defined(data.success) && data.success === true) {
+            this.processGetData(data.data);
+        } else {
+            this.handleLoadError(data, text);
+        }
+    },
+    /** 
+     * Method: processSaveReturn
+     * Private function. Decreases save counter and fires saveSuccess event when all rows are saved
+     * 
+     * Parameters:
+     * data - json data returned from server
+     */
+    processSaveReturn : function (data) {
+        this.saveCount--;
+        if (this.saveCount === 0) {
+            this.fireEvent('saveSuccess', this);
+        }
+    },
+    
+    /** 
+     * Method: handleSaveError
+     * Private function. Handles the case where the server returns an error (no JSON object, usually a 500 or 404 type error)
+     * Fires saveError event in this case and sets continue saving to false.
+     * 
+     * Parameters:
+     * data - the data returned from the server
+     * text - the text version of the data
+     */
+    handleSaveError : function (data, text) {
+        this.continueSaving = false;
+        this.fireEvent('saveError', [ this, data, text ]);
+    },
+    
+    /**
+     * Method: handleLoadError
+     * Private function. Handles problems with loading data by firing the loadError event.
+     * 
+     * Parameters:
+     * data - the data returned from the server
+     * text - the text version of the data
+     */
+    handleLoadError : function (data, text) {
+        this.fireEvent('loadError', [ this, data ]);
+    },
+    
+    /** 
+     * Method: processGetData
+     * Private function. Used to process data retrieved from the server
+     * 
+     * Parameters:
+     * data - the data returned from the server
+     * text - the text version of the data
+     */
+    processGetData : function (data) {
+        if ($defined(data.columns)) {
+            this.options.columns = data.columns;
+        }
+        this.processData(data.data);
+    }
+
+});
+// $Id: $
+/**
+ * class: Jx.Sort.Mergesort 
+ * 
+ * Extends: <Jx.Sort>
+ * 
+ * Implementation of a mergesort algorithm designed to
+ * work on <Jx.Store> data.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Sort.Mergesort = new Class({
+
+    Extends : Jx.Sort,
+
+    name : 'mergesort',
+
+    /**
+     * APIMethod: sort 
+     * Actually runs the sort on the data
+     * 
+     * returns: the sorted data
+     */
+    sort : function () {
+        this.fireEvent('start');
+        var d = this.mergeSort(this.data);
+        this.fireEvent('stop');
+        return d;
+
+    },
+
+    /**
+     * Method: mergeSort 
+     * Does the physical sorting. Called
+     * recursively.
+     * 
+     * Parameters: 
+     * arr - the array to sort
+     * 
+     * returns: the sorted array
+     */
+    mergeSort : function (arr) {
+        if (arr.length <= 1) {
+            return arr;
+        }
+
+        var middle = (arr.length) / 2;
+        var left = arr.slice(0, middle);
+        var right = arr.slice(middle);
+        left = this.mergeSort(left);
+        right = this.mergeSort(right);
+        var result = this.merge(left, right);
+        return result;
+    },
+
+    /**
+     * Method: merge 
+     * Does the work of merging to arrays in order.
+     * 
+     * parameters: 
+     * left - the left hand array 
+     * right - the right hand array
+     * 
+     * returns: the merged array
+     */
+    merge : function (left, right) {
+        var result = [];
+
+        while (left.length > 0 && right.length > 0) {
+            if (this.comparator((left[0]).get(this.col), (right[0])
+                    .get(this.col)) <= 0) {
+                result.push(left[0]);
+                left = left.slice(1);
+            } else {
+                result.push(right[0]);
+                right = right.slice(1);
+            }
+        }
+        while (left.length > 0) {
+            result.push(left[0]);
+            left = left.slice(1);
+        }
+        while (right.length > 0) {
+            result.push(right[0]);
+            right = right.slice(1);
+        }
+        return result;
+    }
+
+});
+// $Id: $
+/**
+ * Class: Jx.Sort.Heapsort 
+ * 
+ * Extends: <Jx.Sort>
+ * 
+ * Implementation of a heapsort algorithm designed to
+ * work on <Jx.Store> data.
+ * 
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Sort.Heapsort = new Class({
+
+    Extends : Jx.Sort,
+
+    name : 'heapsort',
+
+    /**
+     * APIMethod: sort 
+     * Actually runs the sort on the data
+     * 
+     * Returns: the sorted data
+     */
+    sort : function () {
+        this.fireEvent('start');
+
+        var count = this.data.length;
+
+        if (count === 1) {
+            return this.data;
+        }
+
+        if (count > 2) {
+            this.heapify(count);
+
+            var end = count - 1;
+            while (end > 1) {
+                this.data.swap(end, 0);
+                end = end - 1;
+                this.siftDown(0, end);
+            }
+        } else {
+            // check then order the two we have
+            if ((this.comparator((this.data[0]).get(this.col), (this.data[1])
+                    .get(this.col)) > 0)) {
+                this.data.swap(0, 1);
+            }
+        }
+
+        this.fireEvent('stop');
+        return this.data;
+    },
+
+    /**
+     * Method: heapify 
+     * Puts the data in Max-heap order
+     * 
+     * Parameters: count - the number of records we're sorting
+     */
+    heapify : function (count) {
+        var start = Math.round((count - 2) / 2);
+
+        while (start >= 0) {
+            this.siftDown(start, count - 1);
+            start = start - 1;
+        }
+    },
+
+    /**
+     * Method: siftDown 
+     * 
+     * Parameters: start - the beginning of the sort range end - the end of the
+     * sort range
+     */
+    siftDown : function (start, end) {
+        var root = start;
+
+        while (root * 2 <= end) {
+            var child = root * 2;
+            if ((child + 1 < end) && (this.comparator((this.data[child]).get(this.col),
+                            (this.data[child + 1]).get(this.col)) < 0)) {
+                child = child + 1;
+            }
+            if ((this.comparator((this.data[root]).get(this.col),
+                    (this.data[child]).get(this.col)) < 0)) {
+                this.data.swap(root, child);
+                root = child;
+            } else {
+                return;
+            }
+        }
+    }
+
+});
+// $Id: $
+/**
+ * Class: Jx.Sort.Quicksort 
+ * 
+ * Extends: <Jx.Sort>
+ * 
+ * Implementation of a quicksort algorithm designed to
+ * work on <Jx.Store> data.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Sort.Quicksort = new Class({
+
+    Extends : Jx.Sort,
+
+    name : 'quicksort',
+
+    /**
+     * APIMethod: sort 
+     * Actually runs the sort on the data
+     * 
+     * returns: the sorted data
+     */
+    sort : function (left, right) {
+        this.fireEvent('start');
+
+        if (!$defined(left)) {
+            left = 0;
+        }
+        if (!$defined(right)) {
+            right = this.data.length - 1;
+        }
+
+        this.quicksort(left, right);
+
+        this.fireEvent('stop');
+
+        return this.data;
+
+    },
+
+    /**
+     * Method: quicksort 
+     * Initiates the sorting. Is
+     * called recursively
+     * 
+     * Parameters: 
+     * left - the left hand, or lower, bound of the sort
+     * right - the right hand, or upper, bound of the sort
+     */
+    quicksort : function (left, right) {
+        if (left >= right) {
+            return;
+        }
+
+        var index = this.partition(left, right);
+        this.quicksort(left, index - 1);
+        this.quicksort(index + 1, right);
+    },
+
+    /**
+     * Method: partition
+     * 
+     * Parameters: 
+     * left - the left hand, or lower, bound of the sort
+     * right - the right hand, or upper, bound of the sort
+     */
+    partition : function (left, right) {
+        this.findMedianOfMedians(left, right);
+        var pivotIndex = left;
+        var pivotValue = (this.data[pivotIndex]).get(this.col);
+        var index = left;
+        var i;
+
+        this.data.swap(pivotIndex, right);
+        for (i = left; i < right; i++) {
+            if (this.comparator((this.data[i]).get(this.col),
+                    pivotValue) < 0) {
+                this.data.swap(i, index);
+                index = index + 1;
+            }
+        }
+        this.data.swap(right, index);
+
+        return index;
+
+    },
+
+    /**
+     * Method: findMedianOfMedians
+     * 
+     * Parameters: l
+     * eft - the left hand, or lower, bound of the sort
+     * right - the right hand, or upper, bound of the sort
+     */
+    findMedianOfMedians : function (left, right) {
+        if (left === right) {
+            return this.data[left];
+        }
+
+        var i;
+        var shift = 1;
+        while (shift <= (right - left)) {
+            for (i = left; i <= right; i += shift * 5) {
+                var endIndex = (i + shift * 5 - 1 < right) ? i + shift * 5 - 1 : right;
+                var medianIndex = this.findMedianIndex(i, endIndex,
+                        shift);
+
+                this.data.swap(i, medianIndex);
+            }
+            shift *= 5;
+        }
+
+        return this.data[left];
+    },
+
+    /**
+     * Method: findMedianIndex 
+     * 
+     * Parameters: 
+     * left - the left hand, or lower, bound of the sort
+     * right - the right hand, or upper, bound of the sort
+     */
+    findMedianIndex : function (left, right, shift) {
+        var groups = Math.round((right - left) / shift + 1);
+        var k = Math.round(left + groups / 2 * shift);
+        if (k > this.data.length - 1) {
+            k = this.data.length - 1;
+        }
+        for (var i = left; i < k; i += shift) {
+            var minIndex = i;
+            var v = this.data[minIndex];
+            var minValue = v.get(this.col);
+
+            for (var j = i; j <= right; j += shift) {
+                if (this.comparator((this.data[j]).get(this.col),
+                        minValue) < 0) {
+                    minIndex = j;
+                    minValue = (this.data[minIndex]).get(this.col);
+                }
+            }
+            this.data.swap(i, minIndex);
+        }
+
+        return k;
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Sort.Nativesort
+ * 
+ * Extends: <Jx.Sort>
+ * 
+ * Implementation of a native sort algorithm designed to work on <Jx.Store> data.
+ * 
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Sort.Nativesort = new Class({
+
+    Extends : Jx.Sort,
+
+    name : 'nativesort',
+
+    /**
+     * Method: sort
+     * Actually runs the sort on the data
+     * 
+     * Returns:
+     * the sorted data
+     */
+    sort : function () {
+        this.fireEvent('start');
+
+        var compare = function (a, b) {
+            return this.comparator((this.data[a]).get(this.col), (this.data[b])
+                    .get(this.col));
+        };
+
+        this.data.sort(compare);
+        this.fireEvent('stop');
+        return this.data;
+    }
+
+});
+// $Id: button.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
+/**
  * Class: Jx.Button
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.Addable>
- *
  * Jx.Button creates a clickable element that can be added to a web page.
  * When the button is clicked, it fires a 'click' event.
  *
@@ -10444,7 +17533,7 @@
  */
 Jx.Button = new Class({
     Family: 'Jx.Button',
-    Implements: [Options,Events,Jx.Addable],
+    Extends: Jx.Widget,
     
     /**
      * the HTML element that is inserted into the DOM for this button.  You
@@ -10459,12 +17548,6 @@
          * container.
          */
         id: '',
-        /* Option: type
-         * optional.  A string value that indicates what type of button this
-         * is.  The default value is Button.  The type is used to form the CSS
-         * class names used for various HTML elements within the button.
-         */
-        type: 'Button',
         /* Option: image
          * optional.  A string value that is the url to load the image to
          * display in this button.  The default styles size this image to 16 x
@@ -10487,21 +17570,6 @@
          * default true, whether the button is a toggle button or not.
          */
         toggle: false,
-        /* Option: toggleClass
-         * defaults to Toggle, this is class is added to buttons with the
-         * option toggle: true
-         */
-        toggleClass: 'Toggle',
-        /* Option: halign
-         * horizontal alignment of the button label, 'center' by default. 
-         * Other values are 'left' and 'right'.
-         */
-        halign: 'center',
-        /* Option: valign
-         * {String} vertical alignment of the button label, 'middle' by
-         * default.  Other values are 'top' and 'bottom'.
-         */
-        valign: 'middle',
         /* Option: active
          * optional, default false.  Controls the initial state of toggle
          * buttons.
@@ -10511,123 +17579,115 @@
          * whether the button is enabled or not.
          */
         enabled: true,
-        /* Option: container
-         * the tag name of the HTML element that should be created to contain
-         * the button, by default this is 'div'.
+        /* 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.
          */
-        container: 'div'
+        template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
     },
+    type: 'Button',
+    classes: ['jxButtonContainer', 'jxButton','jxButtonIcon','jxButtonLabel'],
+    elements: null,
     /**
-     * Constructor: Jx.Button
+     * APIMethod: render
      * create a new button.
-     *
-     * Parameters:
-     * options - {Object} an object containing optional properties for this
-     * button as below.
      */
-    initialize : function( options ) {
-        this.setOptions(options);
+    render: function() {
+        this.parent();
+        this.elements = this.processTemplate(this.options.template, this.classes);
         
-        // the main container for the button
-        var d = new Element(this.options.container, {'class': 'jx'+this.options.type+'Container'});
-        if (this.options.toggle && this.options.toggleClass) {
-            d.addClass('jx'+this.options.type+this.options.toggleClass);
+        this.domObj = this.elements.get('jx'+this.type+'Container');
+        this.domA = this.elements.get('jx'+this.type);
+        this.domImg = this.elements.get('jx'+this.type+'Icon');
+        this.domLabel = this.elements.get('jx'+this.type + 'Label');
+        
+        /* is the button toggle-able? */
+        if (this.options.toggle) {
+            this.domObj.addClass('jx'+this.type+'Toggle');
         }
+        
         // the clickable part of the button
-        var hasFocus;
-        var mouseDown;
-        var a = new Element('a', {
-            'class': 'jx'+this.options.type, 
-            href: 'javascript:void(0)', 
-            title: this.options.tooltip, 
-            alt: this.options.tooltip,
-            events: {
+        if (this.domA) {
+            var hasFocus;
+            var mouseDown;
+            this.domA.set({
+                href: 'javascript:void(0)', 
+                title: this.options.tooltip, 
+                alt: this.options.tooltip
+            });
+            this.domA.addEvents({
                 click: this.clicked.bindWithEvent(this),
                 drag: (function(e) {e.stop();}).bindWithEvent(this),
                 mousedown: (function(e) {
-                    this.domA.addClass('jx'+this.options.type+'Pressed');
+                    this.domA.addClass('jx'+this.type+'Pressed');
                     hasFocus = true;
                     mouseDown = true;
                     this.focus();
                 }).bindWithEvent(this),
                 mouseup: (function(e) {
-                    this.domA.removeClass('jx'+this.options.type+'Pressed');
+                    this.domA.removeClass('jx'+this.type+'Pressed');
                     mouseDown = false;
                 }).bindWithEvent(this),
                 mouseleave: (function(e) {
-                    this.domA.removeClass('jx'+this.options.type+'Pressed');
+                    this.domA.removeClass('jx'+this.type+'Pressed');
                 }).bindWithEvent(this),
                 mouseenter: (function(e) {
                     if (hasFocus && mouseDown) {
-                        this.domA.addClass('jx'+this.options.type+'Pressed');
+                        this.domA.addClass('jx'+this.type+'Pressed');
                     }
                 }).bindWithEvent(this),
                 keydown: (function(e) {
                     if (e.key == 'enter') {
-                        this.domA.addClass('jx'+this.options.type+'Pressed');
+                        this.domA.addClass('jx'+this.type+'Pressed');
                     }
                 }).bindWithEvent(this),
                 keyup: (function(e) {
                     if (e.key == 'enter') {
-                        this.domA.removeClass('jx'+this.options.type+'Pressed');
+                        this.domA.removeClass('jx'+this.type+'Pressed');
                     }
                 }).bindWithEvent(this),
                 blur: function() { hasFocus = false; }
+            });
+
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
             }
-        });
-        d.adopt(a);
-        
-        if (typeof Drag != 'undefined') {
-            new Drag(a, {
-                onStart: function() {this.stop();}
-            });
         }
-        
-        var s = new Element('span', {'class': 'jx'+this.options.type+'Content'});
-        a.adopt(s);
-        
-        if (this.options.image || !this.options.label) {
-            var i = new Element('img', {
-                'class':'jx'+this.options.type+'Icon',
-                'src': Jx.aPixel.src,
-                title: this.options.tooltip, 
-                alt: this.options.tooltip
-            });
-            //if image is not a_pixel, set the background image of the image
-            //otherwise let the default css take over.
-            if (this.options.image && this.options.image.indexOf('a_pixel.png') == -1) {
-                i.setStyle('backgroundImage',"url("+this.options.image+")");
+
+        if (this.domImg) {
+            if (this.options.image || !this.options.label) {
+                this.domImg.set({
+                    title: this.options.tooltip, 
+                    alt: this.options.tooltip
+                });
+                if (this.options.image && this.options.image.indexOf('a_pixel.png') == -1) {
+                    this.domImg.setStyle('backgroundImage',"url("+this.options.image+")");
+                }
+                if (this.options.imageClass) {
+                    this.domImg.addClass(this.options.imageClass);
+                }
+            } else {
+                //remove the image if we don't need it
+                this.domImg.setStyle('display','none');
             }
-            s.appendChild(i);
-            if (this.options.imageClass) {
-                i.addClass(this.options.imageClass);
-            }
-            this.domImg = i;
         }
         
-        var l = new Element('span', {
-            html: this.options.label
-        });
-        if (this.options.label) {
-            l.addClass('jx'+this.options.type+'Label');
+        if (this.domLabel) {
+            if (this.options.label) {
+                this.domLabel.set('html',this.options.label);
+            } else {
+                this.domLabel.removeClass('jx'+this.type+'Label');
+            }
         }
-        s.appendChild(l);
         
         if (this.options.id) {
-            d.id = this.options.id;
+            this.domObj.set('id', this.options.id);
         }
-        if (this.options.halign == 'left') {
-            d.addClass('jx'+this.options.type+'ContentLeft');                
-        }
-
-        if (this.options.valign == 'top') {
-            d.addClass('jx'+this.options.type+'ContentTop');
-        }
         
-        this.domA = a;
-        this.domLabel = l;
-        this.domObj = d;        
-
         //update the enabled state
         this.setEnabled(this.options.enabled);
         
@@ -10706,10 +17766,10 @@
         }
         this.options.active = active;
         if (this.options.active) {
-            this.domA.addClass('jx'+this.options.type+'Active');
+            this.domA.addClass('jx'+this.type+'Active');
             this.fireEvent('down', this);
         } else {
-            this.domA.removeClass('jx'+this.options.type+'Active');
+            this.domA.removeClass('jx'+this.type+'Active');
             this.fireEvent('up', this);
         }
     },
@@ -10722,42 +17782,30 @@
      */
     setImage: function(path) {
         this.options.image = path;
-        if (path) {
-            if (!this.domImg) {
-                var i = new Element('img', {
-                    'class':'jx'+this.options.type+'Icon',
-                    'src': Jx.aPixel.src,
-                    alt: '',
-                    title: ''
-                });
-                if (this.options.imageClass) {
-                    i.addClass(this.options.imageClass);
-                }
-                this.domA.firstChild.grab(i, 'top');
-                this.domImg = i;
-            }
-            this.domImg.setStyle('backgroundImage',"url("+this.options.image+")");                        
-        } else if (this.domImg){
-            this.domImg.dispose();
-            this.domImg = null;
+        if (this.domImg) {
+            this.domImg.setStyle('backgroundImage',
+                                 "url("+this.options.image+")");
+            this.domImg.setStyle('display', path ? null : 'none');
         }
     },
     /**
      * Method: setLabel
      * 
-     * sets the text of the button.  Only works if a label was supplied
-     * when the button was constructed
+     * sets the text of the button.
      *
      * Parameters: 
      *
      * label - {String} the new label for the button
      */
     setLabel: function(label) {
-        this.domLabel.set('html', label);
-        if (!label && this.domLabel.hasClass('jxButtonLabel')) {
-            this.domLabel.removeClass('jxButtonLabel');
-        } else if (label && !this.domLabel.hasClass('jxButtonLabel')) {
-            this.domLabel.addClass('jxButtonLabel');
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html', label);
+            if (!label && this.domLabel.hasClass('jxButtonLabel')) {
+                this.domLabel.removeClass('jxButtonLabel');
+            } else if (label && !this.domLabel.hasClass('jxButtonLabel')) {
+                this.domLabel.addClass('jxButtonLabel');
+            }
         }
     },
     /**
@@ -10766,7 +17814,7 @@
      * returns the text of the button.
      */
     getLabel: function() {
-        return this.domLabel ? this.domLabel.innerHTML : '';
+        return this.options.label;
     },
     /**
      * Method: setTooltip
@@ -10798,7 +17846,7 @@
         this.domA.blur();
     }
 });
-// $Id: flyout.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: flyout.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Button.Flyout
  *
@@ -10832,6 +17880,9 @@
  * 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)
@@ -10859,7 +17910,6 @@
 Jx.Button.Flyout = new Class({
     Family: 'Jx.Button.Flyout',
     Extends: Jx.Button,
-    Implements: [Jx.ContentLoader, Jx.AutoPosition, Jx.Chrome],
     
     /**
      * Property: content
@@ -10867,22 +17917,15 @@
      */
     content: null,
     /**
-     * Constructor: initialize
-     * construct a new instance of a flyout button.  The single options
-     * argument takes a combination of options that apply to <Jx.Button>,
-     * <Jx.ContentLoader>, and <Jx.AutoPosition>.
-     *
-     * Parameters: 
-     * options - an options object used to initialize the button, see 
-     * <Jx.Button.Options>, <Jx.ContentLoader.Options>, and
-     * <Jx.AutoPosition.Options> for details.
+     * APIMethod: render
+     * construct a new instance of a flyout button.  
      */
-    initialize: function(options) {
+    render: function() {
         if (!Jx.Button.Flyout.Stack) {
             Jx.Button.Flyout.Stack = [];
         }
-        this.parent(options);
-        this.domA.addClass('jx'+this.options.type+'Flyout');
+        this.parent();
+        this.domA.addClass('jx'+this.type+'Flyout');
         
         this.contentContainer = new Element('div',{
             'class':'jxFlyout'
@@ -10916,14 +17959,14 @@
         /* find out what we are contained by if we don't already know */
         if (!this.owner) {
             this.owner = document.body;
-            var node = $(this.domObj.parentNode);
+            var node = document.id(this.domObj.parentNode);
             while (node != document.body && this.owner == document.body) {
                 var flyout = node.retrieve('jxFlyout');
                 if (flyout) {
                     this.owner = flyout;
                     break;
                 } else {
-                    node = $(node.parentNode);
+                    node = document.id(node.parentNode);
                 }
             }
         }
@@ -10955,9 +17998,9 @@
         Jx.Button.Flyout.Stack.push(this);
 
         this.options.active = true;
-        this.domA.addClass('jx'+this.options.type+'Active');
+        this.domA.addClass('jx'+this.type+'Active');
         this.contentContainer.setStyle('visibility','hidden');
-        $(document.body).adopt(this.contentContainer);
+        document.id(document.body).adopt(this.contentContainer);
         this.content.getChildren().each(function(child) {
             if (child.resize) { 
                 child.resize(); 
@@ -10974,7 +18017,7 @@
         /* we have to size the container for IE to render the chrome correctly
          * there is some horrible peekaboo bug in IE 6
          */
-        this.contentContainer.setContentBoxSize($(this.content).getMarginBoxSize());
+        this.contentContainer.setContentBoxSize(document.id(this.content).getMarginBoxSize());
         
         this.contentContainer.setStyle('visibility','');
 
@@ -11000,7 +18043,7 @@
     /* hide flyout if the user clicks outside of the flyout */
     clickHandler: function(e) {
         e = new Event(e);
-        var elm = $(e.target);
+        var elm = document.id(e.target);
         var flyout = Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1];
         if (!elm.descendantOf(flyout.content) &&
             !elm.descendantOf(flyout.domObj)) {
@@ -11014,14 +18057,12 @@
             Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
         }
     }
-});// $Id: layout.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: layout.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Layout
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  * 
- * Implements: Options, Events
- *
  * Jx.Layout is used to provide more flexible layout options for applications
  *
  * Jx.Layout wraps an existing DOM element (typically a div) and provides
@@ -11047,7 +18088,7 @@
  
 Jx.Layout = new Class({
     Family: 'Jx.Layout',
-    Implements: [Options,Events],
+    Extends: Jx.Object,
     
     options: {
         /* Option: propagate
@@ -11126,17 +18167,20 @@
          */
         maxHeight: -1
     },
+    
     /**
-     * Constructor: Jx.Layout
-     * Create a new instance of Jx.Layout.
-     *
      * Parameters:
      * domObj - {HTMLElement} element or id to apply the layout to
      * options - <Jx.Layout.Options>
      */
-    initialize: function(domObj, options) {
-        this.setOptions(options);
-        this.domObj = $(domObj);
+    parameters: ['domObj','options'],
+    
+    /**
+     * APIMethod: init
+     * Create a new instance of Jx.Layout.
+     */
+    init: function() {
+        this.domObj = document.id(this.options.domObj);
         this.domObj.resize = this.resize.bind(this);
         this.domObj.setStyle('position', this.options.position);
         this.domObj.store('jxLayout', this);
@@ -11190,7 +18234,7 @@
                 needsResize = true;
             }
         }
-        if (!$(this.domObj.parentNode)) {
+        if (!document.id(this.domObj.parentNode)) {
             return;
         }
         
@@ -11198,7 +18242,7 @@
         if (this.domObj.parentNode.tagName == 'BODY') {
             parentSize = Jx.getPageDimensions();
         } else {
-            parentSize = $(this.domObj.parentNode).getContentBoxSize();
+            parentSize = document.id(this.domObj.parentNode).getContentBoxSize();
         }
     
         if (this.lastParentSize && !needsResize) {
@@ -11411,11 +18455,13 @@
         /* apply the new sizes */
         var sizeOpts = {width: w};
         if (this.options.position == 'absolute') {
-            var padding = $(this.domObj.parentNode).getPaddingSize();
+            var m = document.id(this.domObj.parentNode).measure(function(){
+                return this.getSizes(['padding'],['left','top']).padding;
+            });
             this.domObj.setStyles({
                 position: this.options.position,
-                left: l+padding.left,
-                top: t+padding.top
+                left: l+m.left,
+                top: t+m.top
             });
             sizeOpts.height = h;
         } else {
@@ -11437,14 +18483,12 @@
 
         this.fireEvent('sizeChange',this);
     }
-});// $Id: tab.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: tab.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Button.Tab
  *
  * Extends: <Jx.Button>
  *
- * Implements: <Jx.ContentLoader>
- *
  * A single tab in a tab set.  A tab has a label (displayed in the tab) and a
  * content area that is displayed when the tab is active.  A tab has to be
  * added to both a <Jx.TabSet> (for the content) and <Jx.Toolbar> (for the
@@ -11485,48 +18529,44 @@
 Jx.Button.Tab = new Class({
     Family: 'Jx.Button.Tab',
     Extends: Jx.Button,
-    Implements: [Jx.ContentLoader],
     /**
      * Property: content
      * {HTMLElement} The content area that is displayed when the tab is active.
      */
     content: null,
+    
+    options: {
+        template: '<div class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"><img src="'+Jx.aPixel.src+'"></a></div>'
+    },
+    type: 'Tab',
+    classes: ['jxTabContainer','jxTab','jxTabIcon','jxTabLabel','jxTabClose'],
+    
     /**
-     * Constructor: Jx.Button.Tab
+     * APIMethod: 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.
-     *
-     * Parameters:
-     * options - {Object} an object containing options that are used
-     * to control the appearance of the tab.  See <Jx.Button>,
-     * <Jx.ContentLoader::loadContent> and <Jx.Layout::Jx.Layout> for
-     * valid options.
      */
-    initialize : function( options) {
-        this.parent($merge(options, {type:'Tab', toggle:true}));
+    render : function( ) {
+        this.options = $merge(this.options, {toggle:true});
+        this.parent();
         this.content = new Element('div', {'class':'tabContent'});
-        new Jx.Layout(this.content, options);
+        new Jx.Layout(this.content, this.options);
         this.loadContent(this.content);
         var that = this;
         this.addEvent('down', function(){that.content.addClass('tabContentActive');});
         this.addEvent('up', function(){that.content.removeClass('tabContentActive');});
         
-        if (this.options.close) {
-            this.domObj.addClass('jxTabClose');
-            var a = new Element('a', {
-                'class': 'jxTabClose',
-                events: {
-                    'click': (function(){
-                        this.fireEvent('close');                        
-                    }).bind(this)
-                } 
-            });
-            a.adopt(new Element('img', {
-                src: Jx.aPixel.src,
-                alt: '',
-                title: ''
-            }));
-            this.domObj.adopt(a);
+        //remove the close button if necessary
+        var closer = this.elements.get('jx'+this.type+'Close');
+        if (closer) {
+            if (this.options.close) {
+                this.domObj.addClass('jx'+this.type+'Close');
+                closer.addEvent('click', (function(){
+                    this.fireEvent('close');
+                }).bind(this));
+            } else {
+                closer.dispose();
+            }
         }
     },
     /**
@@ -11539,20 +18579,18 @@
             this.setActive(true);            
         }
     }
-});// $Id: colorpalette.js 473 2009-07-08 16:46:21Z pagameba $
+});// $Id: colorpalette.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.ColorPalette
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.Addable>
- *
  * A Jx.ColorPalette presents a user interface for selecting colors.
  * Currently, the user can either enter a HEX colour value or select from a
  * 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> subclass is provided (<Jx.Button.Color>)
+ * 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 
@@ -11573,7 +18611,7 @@
  */
 Jx.ColorPalette = new Class({
     Family: 'Jx.ColorPalette',
-    Implements: [Options, Events, Jx.Addable],
+    Extends: Jx.Widget,
     /**
      * Property: {HTMLElement} domObj
      * the HTML element representing the color panel
@@ -11603,15 +18641,10 @@
         alphaLabel: 'alpha (%)'
     },
     /**
-     * Constructor: Jx.ColorPalette
+     * APIMethod: render
      * initialize a new instance of Jx.ColorPalette
-     *
-     * Parameters:
-     * options - <Jx.ColorPalette.Options>
      */
-    initialize: function(options) {
-        this.setOptions(options);
-
+    render: function() {
         this.domObj = new Element('div', {
             id: this.options.id,
             'class':'jxColorPalette'
@@ -11725,8 +18758,7 @@
                     a.store('swatchColor', bgColor);
                     td.adopt(a);
                 } else {
-                    td.addClass('emptyCell');
-                    var span = new Element('span');
+                    var span = new Element('span', {'class':'emptyCell'});
                     td.adopt(span);
                 }
                 tr.adopt(td);
@@ -11846,7 +18878,7 @@
     }
 });
 
-// $Id: color.js 424 2009-05-12 12:51:44Z pagameba $ 
+// $Id: color.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $ 
 /**
  * Class: Jx.Button.Color
  *
@@ -11903,14 +18935,10 @@
     },
 
     /**
-     * Constructor: Jx.Button.Color
-     * initialize a new color button.
-     *
-     * Parameters:
-     * options - <Jx.Button.Color.Options> initialize instance options.
-     *
+     * APIMethod: render
+     * creates a new color button.
      */
-    initialize: function(options) {
+    render: function() {
         if (!Jx.Button.Color.ColorPalette) {
             Jx.Button.Color.ColorPalette = new Jx.ColorPalette(this.options);
         }
@@ -11923,10 +18951,10 @@
         this.hideFn = this.hide.bind(this);
         /* we need to have an image to replace, but if a label is 
            requested, there wouldn't normally be an image. */
-        options.image = Jx.aPixel.src;
+        this.options.image = Jx.aPixel.src;
 
         /* now we can safely initialize */
-        this.parent(options);
+        this.parent();
         
         // now replace the image with our swatch
         d.replaces(this.domImg);
@@ -12034,14 +19062,12 @@
         this.selectedSwatch.setStyles(styles);
     }
 });
-// $Id: menu.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: menu.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.AutoPosition>, <Jx.Chrome>, <Jx.Addable>
- *
  * A main menu as opposed to a sub menu that lives inside the menu.
  *
  * TODO: Jx.Menu
@@ -12059,21 +19085,8 @@
  */
 Jx.Menu = new Class({
     Family: 'Jx.Menu',
+    Extends: Jx.Widget,
     /**
-     * Implements:
-     * * Options
-     * * Events
-     * * <Jx.AutoPosition>
-     * * <Jx.Chrome>
-     * * <Jx.Addable>
-     */
-    Implements: [Options, Events, Jx.AutoPosition, Jx.Chrome, Jx.Addable],
-    /**
-     * Property: domObj
-     * {HTMLElement} The HTML element containing the menu.
-     */
-    domObj : null,
-    /**
      * Property: button
      * {<Jx.Button>} The button that represents this menu in a toolbar and
      * opens the menu.
@@ -12086,47 +19099,58 @@
      */
     subDomObj : null,
     /**
-     * Property: items
-     * {Array} the items in this menu
+     * Property: list
+     * {<Jx.List>} the list of items in the menu
      */
-    items : null,
+    list: null,
+    
+    parameters: ['buttonOptions', 'options'],
+    
+    options: {
+        template: "<div class='jxMenuContainer'><ul class='jxMenu'></ul></div>",
+        buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>',
+        position: {
+            horizontal: ['left left'],
+            vertical: ['bottom top', 'top bottom']
+        }
+    },
+    
+    classes: ['jxMenuContainer','jxMenu'],
+    
     /**
-     * Constructor: Jx.Menu
+     * APIMethod: render
      * Create a new instance of Jx.Menu.
-     *
-     * Parameters:
-     * options - see <Jx.Button.Options>.  If no options are provided then
-     * no button is created.
      */
-    initialize : function(options) {
-        this.setOptions(options);
+    render : function() {
+        this.parent();
         if (!Jx.Menu.Menus) {
             Jx.Menu.Menus = [];
         }
-        /* stores menu items and sub menus */
-        this.items = [];
+
+        this.elements = this.processTemplate(this.options.template, this.classes);
+
+        this.contentContainer = this.elements.get('jxMenuContainer');
+        this.contentContainer.addEvent('onContextmenu', function(e){e.stop();});
         
-        this.contentContainer = new Element('div',{
-            'class':'jxMenuContainer',
-            events: {
-                contextmenu: function(e){e.stop();}
-            }
-        });
+        this.subDomObj = this.elements.get('jxMenu');
         
-        /* the DOM element that holds the actual menu */
-        this.subDomObj = new Element('ul',{
-            'class':'jxMenu'
+        this.list = new Jx.List(this.subDomObj, {
+            onAdd: function(item) {
+                item.setOwner(this);
+            }.bind(this),
+            onRemove: function(item) {
+                item.setOwner(null);
+            }.bind(this)
         });
-        
-        this.contentContainer.adopt(this.subDomObj);
-        
+
         /* if options are passed, make a button inside an LI so the
            menu can be embedded inside a toolbar */
-        if (options) {
-            this.button = new Jx.Button($merge(options,{
+        if (this.options.buttonOptions) {
+            this.button = new Jx.Button($merge(this.options.buttonOptions,{
+                template: this.options.buttonTemplate,
                 onClick:this.show.bind(this)
             }));
-            this.button.domA.addClass('jxButtonMenu');
+
             this.button.domA.addEvent('mouseover', this.onMouseOver.bindWithEvent(this));
             
             this.domObj = this.button.domObj;
@@ -12148,15 +19172,34 @@
      * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
      * can be added by passing multiple arguments to this function.
      */
-    add : function() {
-        $A(arguments).flatten().each(function(item){
-            this.items.push(item);
-            item.setOwner(this);
-            this.subDomObj.adopt(item.domObj);
-        }, this);
+    add: function(item, position) {
+        this.list.add(item, position);
         return this;
     },
     /**
+     * Method: remove
+     * Remove a menu item from the menu
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to remove
+     */
+    remove: function(item) {
+        this.list.remove(item);
+        return this;
+    },
+    /**
+     * Method: replace
+     * Replace a menu item with another menu item
+     *
+     * Parameters:
+     * what - {<Jx.MenuItem>} the menu item to replace
+     * withWhat - {<Jx.MenuItem>} the menu item to replace it with
+     */
+    replace: function(item, withItem) {
+        this.list.replace(item, withItem);
+        return this;
+    },
+    /**
      * Method: deactivate
      * Deactivate the menu by hiding it.
      */
@@ -12188,7 +19231,7 @@
      * a sub menu of this menu, false otherwise
      */
     eventInMenu: function(e) {
-        var target = $(e.target);
+        var target = document.id(e.target);
         if (!target) {
             return false;
         }
@@ -12211,6 +19254,16 @@
             }
             return false;
         }
+        
+        /*
+        this.list.items().some(
+            function(item) {
+                var menuItem = item.retrieve('jxMenuItem');
+                return menuItem instanceof Jx.Menu.SubMenu && 
+                       menuItem.eventInMenu(e);
+            }
+        );
+        */
     },
     
     /**
@@ -12236,38 +19289,40 @@
         if (this.button && this.button.domA) {
             this.button.domA.removeClass('jx'+this.button.options.type+'Active');            
         }
-        this.items.each(function(item){item.hide(e);});
+        this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
         document.removeEvent('mousedown', this.hideWatcher);
         document.removeEvent('keydown', this.keypressWatcher);
-        this.contentContainer.setStyle('display','none');
+        this.contentContainer.dispose();
         this.fireEvent('hide', this); 
     },
     /**
      * Method: show
      * Show the menu
-     *
-     * Parameters:
-     * e - {Event} the mouse event
      */
-    show : function(o) {
-        var e = o.event;
-        if (Jx.Menu.Menus[0]) {
-            if (Jx.Menu.Menus[0] != this) {
-                Jx.Menu.Menus[0].button.blur();
-                Jx.Menu.Menus[0].hide(e);
-            } else {
-                this.hide();
+    show : function() {
+        if (this.button) {
+            if (Jx.Menu.Menus[0]) {
+                if (Jx.Menu.Menus[0] != this) {
+                    Jx.Menu.Menus[0].button.blur();
+                    Jx.Menu.Menus[0].hide();
+                } else {
+                    this.hide();
+                    return;
+                }  
+            } 
+            Jx.Menu.Menus[0] = this;
+            this.button.focus();
+            if (this.list.count() == 0) {
                 return;
-            }  
-        } 
-        if (this.items.length === 0) {
-            return;
+            } 
         }
-        Jx.Menu.Menus[0] = this;
-        this.button.focus();
-        this.contentContainer.setStyle('visibility','hidden');
-        this.contentContainer.setStyle('display','block');
-        $(document.body).adopt(this.contentContainer);            
+        this.contentContainer.setStyle('display','none');
+        document.id(document.body).adopt(this.contentContainer);            
+        this.contentContainer.setStyles({
+            visibility: 'hidden',
+            display: 'block'
+        });
+        
         /* 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
@@ -12275,22 +19330,16 @@
         this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
         this.showChrome(this.contentContainer);
         
-        this.position(this.contentContainer, this.button.domObj, {
-            horizontal: ['left left'],
-            vertical: ['bottom top', 'top bottom'],
+        this.position(this.contentContainer, this.domObj, $merge({
             offsets: this.chromeOffsets
-        });
+        }, this.options.position));
 
-        this.contentContainer.setStyle('visibility','');
+        this.contentContainer.setStyle('visibility','visible');
         
         if (this.button && this.button.domA) {
             this.button.domA.addClass('jx'+this.button.options.type+'Active');            
         }
-        if (e) {
-            //why were we doing this? it is affecting the closing of
-            //other elements like flyouts (issue 13)
-            //e.stop();
-        }
+
         /* fix bug in IE that closes the menu as it opens because of bubbling */
         document.addEvent('mousedown', this.hideWatcher);
         document.addEvent('keydown', this.keypressWatcher);
@@ -12322,14 +19371,12 @@
     }
 });
 
-// $Id: set.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: set.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.ButtonSet
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
- * Implements: Options, Events
- *
  * 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.
@@ -12357,22 +19404,17 @@
  */
 Jx.ButtonSet = new Class({
     Family: 'Jx.ButtonSet',
-    Implements: [Options,Events],
+    Extends: Jx.Object,
     /**
      * Property: buttons
      * {Array} array of buttons that are managed by this button set
      */
     buttons: null,
     /**
-     * Constructor: Jx.ButtonSet
-     * Create a new instance of <Jx.ButtonSet>
-     *
-     * Parameters:
-     * options - an options object, only event handlers are supported
-     * as options at this time.
+     * APIMethod: init
+     * initializes the button set.
      */
-    initialize : function(options) {
-        this.setOptions(options);
+    init : function() {
         this.buttons = [];
         this.buttonChangedHandler = this.buttonChanged.bind(this);
     },
@@ -12455,17 +19497,17 @@
 
 
 
-// $Id: multi.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: multi.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Button.Multi
  *
  * Extends: <Jx.Button>
  *
- * Implements: 
+ * Implements:
  *
  * Multi buttons are used to contain multiple buttons in a drop down list
  * where only one button is actually visible and clickable in the interface.
- * 
+ *
  * When the user clicks the active button, it performs its normal action.
  * The user may also click a drop-down arrow to the right of the button and
  * access the full list of buttons.  Clicking a button in the list causes
@@ -12476,7 +19518,7 @@
  *
  * This is not really a button, but rather a container for buttons.  The
  * button structure is a div containing two buttons, a normal button and
- * a flyout button.  The flyout contains a toolbar into which all the 
+ * a flyout button.  The flyout contains a toolbar into which all the
  * added buttons are placed.  The main button content is cloned from the
  * last button clicked (or first button added).
  *
@@ -12507,9 +19549,9 @@
  * multiButton.add(b1, b2, b3);
  * (end)
  *
- * License: 
+ * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
- * 
+ *
  * This file is licensed under an MIT style license
  */
 Jx.Button.Multi = new Class({
@@ -12526,123 +19568,112 @@
      * {Array} the buttons added to this multi button
      */
     buttons: null,
+
+    options: {
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti"><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>'
+    },
+    classes: ['jxButtonContainer','jxButton','jxButtonIcon','jxButtonLabel','jxButtonDisclose'],
+
     /**
-     * Constructor: Jx.Button.Multi
+     * APIMethod: render
      * construct a new instance of Jx.Button.Multi.
      */
-    initialize: function(opts) {
-        this.parent(opts);
-
+    render: function() {
+        this.parent();
         this.buttons = [];
 
-        this.domA.addClass('jxButtonMulti');
-        
         this.menu = new Jx.Menu();
         this.menu.button = this;
         this.buttonSet = new Jx.ButtonSet();
-        
+
         this.clickHandler = this.clicked.bind(this);
-        
-        var a = new Element('a', {
-            'class': 'jxButtonDisclose',
-            'href': 'javascript:void(0)'
-        });
-        var button = this;
-        var hasFocus;
-        
-        a.addEvents({
-            'click': (function(e) {
-                if (this.items.length === 0) {
-                    return;
-                }
-                if (!button.options.enabled) {
-                    return;
-                }
-                this.contentContainer.setStyle('visibility','hidden');
-                this.contentContainer.setStyle('display','block');
-                $(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);
+        var a = this.elements.get('jxButtonDisclose');
+        if (a) {
+            var button = this;
+            var hasFocus;
 
-                this.position(this.contentContainer, this.button.domObj, {
-                    horizontal: ['right right'],
-                    vertical: ['bottom top', 'top bottom'],
-                    offsets: this.chromeOffsets
-                });
+            a.addEvents({
+                'click': (function(e) {
+                    if (this.items.length === 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.contentContainer.setStyle('visibility','');
+                    this.showChrome(this.contentContainer);
 
-                document.addEvent('mousedown', this.hideWatcher);
-                document.addEvent('keyup', this.keypressWatcher);
-                
-                /* why were we doing this? It was affecting issue 13
-                if (e.$extended) {
-                    e.stop();                                        
-                } else if (e.event && e.event.$extended) {
-                    e.event.stop();
-                }
-                */
-                this.fireEvent('show', this);
-            }).bindWithEvent(this.menu),
-            'mouseenter':(function(){
-                $(this.domObj.firstChild).addClass('jxButtonHover');
-                if (hasFocus) {
-                    a.addClass('jx'+this.options.type+'Pressed');
-                }
-            }).bind(this),
-            'mouseleave':(function(){
-                $(this.domObj.firstChild).removeClass('jxButtonHover');
-                a.removeClass('jx'+this.options.type+'Pressed');
-            }).bind(this),
-            mousedown: (function(e) {
-                a.addClass('jx'+this.options.type+'Pressed');
-                hasFocus = true;
-                this.focus();
-            }).bindWithEvent(this),
-            mouseup: (function(e) {
-                a.removeClass('jx'+this.options.type+'Pressed');                  
-            }).bindWithEvent(this),
-            keydown: (function(e) {
-                if (e.key == 'enter') {
-                    a.addClass('jx'+this.options.type+'Pressed');
-                }
-            }).bindWithEvent(this),
-            keyup: (function(e) {
-                if (e.key == 'enter') {
-                    a.removeClass('jx'+this.options.type+'Pressed');
-                }
-            }).bindWithEvent(this),
-            blur: function() { hasFocus = false; }
-            
-        });
-        if (typeof Drag != 'undefined') {
-            new Drag(a, {
-                onStart: function() {this.stop();}
+                    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.hideWatcher);
+                    document.addEvent('keyup', this.keypressWatcher);
+
+                    this.fireEvent('show', this);
+                }).bindWithEvent(this.menu),
+                'mouseenter':(function(){
+                    document.id(this.domObj.firstChild).addClass('jxButtonHover');
+                    if (hasFocus) {
+                        a.addClass('jx'+this.type+'Pressed');
+                    }
+                }).bind(this),
+                'mouseleave':(function(){
+                    document.id(this.domObj.firstChild).removeClass('jxButtonHover');
+                    a.removeClass('jx'+this.type+'Pressed');
+                }).bind(this),
+                mousedown: (function(e) {
+                    a.addClass('jx'+this.type+'Pressed');
+                    hasFocus = true;
+                    this.focus();
+                }).bindWithEvent(this),
+                mouseup: (function(e) {
+                    a.removeClass('jx'+this.type+'Pressed');
+                }).bindWithEvent(this),
+                keydown: (function(e) {
+                    if (e.key == 'enter') {
+                        a.addClass('jx'+this.type+'Pressed');
+                    }
+                }).bindWithEvent(this),
+                keyup: (function(e) {
+                    if (e.key == 'enter') {
+                        a.removeClass('jx'+this.type+'Pressed');
+                    }
+                }).bindWithEvent(this),
+                blur: function() { hasFocus = false; }
+
             });
+            if (typeof Drag != 'undefined') {
+                new Drag(a, {
+                    onStart: function() {this.stop();}
+                });
+            }
+            this.discloser = a;
         }
-        
+
         this.menu.addEvents({
             'show': (function() {
-                this.domA.addClass('jxButtonActive');                    
+                this.domA.addClass('jx'+this.type+'Active');
             }).bind(this),
             'hide': (function() {
                 if (this.options.active) {
-                    this.domA.addClass('jxButtonActive');                    
+                    this.domA.addClass('jx'+this.type+'Active');
                 }
             }).bind(this)
         });
-        a.adopt(new Element('img', {
-            src: Jx.aPixel.src,
-            alt: '',
-            title: ''
-        }));
-        this.domObj.adopt(a);
-        this.discloser = a;
         if (this.options.items) {
             this.add(this.options.items);
         }
@@ -12650,11 +19681,11 @@
     /**
      * Method: add
      * adds one or more buttons to the Multi button.  The first button
-     * added becomes the active button initialize.  This function 
+     * added becomes the active button initialize.  This function
      * takes a variable number of arguments, each of which is expected
      * to be an instance of <Jx.Button>.
      *
-     * Parameters: 
+     * Parameters:
      * button - {<Jx.Button>} a <Jx.Button> instance, may be repeated in the parameter list
      */
     add: function() {
@@ -12664,13 +19695,15 @@
             }
             this.buttons.push(theButton);
             var f = this.setButton.bind(this, theButton);
-            var opts = $merge(
-                theButton.options,
-                { toggle: true, onClick: f}
-            );
-            if (!opts.label) {
-                opts.label = '&nbsp;';
-            }
+            var opts = {
+                image: theButton.options.image,
+                imageClass: theButton.options.imageClass,
+                label: theButton.options.label || '&nbsp;',
+                enabled: theButton.options.enabled,
+                tooltip: theButton.options.tooltip,
+                toggle: true,
+                onClick: f
+            };
             if (!opts.image || opts.image.indexOf('a_pixel') != -1) {
                 delete opts.image;
             }
@@ -12722,7 +19755,7 @@
      * Method: setActiveButton
      * update the menu item to be the requested button.
      *
-     * Parameters: 
+     * Parameters:
      * button - {<Jx.Button>} a <Jx.Button> instance that was added to this multi button.
      */
     setActiveButton: function(button) {
@@ -12736,8 +19769,8 @@
             this.domA.addEvent('click', this.clickHandler);
             if (this.options.toggle) {
                 this.options.active = false;
-                this.setActive(true); 
-            }         
+                this.setActive(true);
+            }
         }
         this.activeButton = button;
     },
@@ -12746,14 +19779,14 @@
      * update the active button in the menu item, trigger the button's action
      * and hide the flyout that contains the buttons.
      *
-     * Parameters: 
+     * Parameters:
      * button - {<Jx.Button>} The button to set as the active button
      */
     setButton: function(button) {
         this.setActiveButton(button);
         button.clicked();
     }
-});// $Id: menu.item.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: menu.item.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu.Item
  *
@@ -12787,29 +19820,31 @@
      */
     owner: null,
     options: {
-        enabled: true,
-        image: null,
+        //image: null,
         label: '&nbsp;',
-        toggleClass: 'Toggle'
+        /* Option: template
+         * the HTML structure of the button.  As a minimum, there must be a
+         * containing element with a class of jxMenuItemContainer and an
+         * internal element with a class of jxMenuItem.  jxMenuItemIcon and
+         * jxMenuItemLabel are used if present to put the image and label into
+         * the button.
+         */
+        template: '<li class="jxMenuItemContainer"><a class="jxMenuItem"><span class="jxMenuItemContent"><img class="jxMenuItemIcon" src="'+Jx.aPixel.src+'"><span class="jxMenuItemLabel"></span></span></a></li>'
     },
+    classes: ['jxMenuItemContainer', 'jxMenuItem','jxMenuItemIcon','jxMenuItemLabel'],
+    type: 'MenuItem',
     /**
-     * Constructor: Jx.Menu.Item
+     * APIMethod: render
      * Create a new instance of Jx.Menu.Item
-     *
-     * Parameters:
-     * options - See <Jx.Button.Options>
      */
-    initialize: function(options) {
-        this.parent($merge({
-                image: Jx.aPixel.src
-            },
-            options, {
-                container:'li',
-                type:'MenuItem',
-                toggleClass: (options.image ? null : this.options.toggleClass)
-            }
-        ));
-        this.domObj.addEvent('mouseover', this.onMouseOver.bindWithEvent(this));
+    render: function() {
+        this.options = $merge({image: Jx.aPixel.src}, this.options);
+        this.parent();
+        if (this.options.image) {
+            this.domObj.removeClass('jx'+this.type+'Toggle');
+        }
+        this.domObj.addEvent('mouseover', this.onMouseOver.bind(this));
+        this.domObj.store('jxMenuItem', this);
     },
     /**
      * Method: setOwner
@@ -12854,19 +19889,15 @@
     /**
      * Method: onmouseover
      * handle the mouse moving over the menu item
-     *
-     * Parameters:
-     * e - {Event} the mousemove event
      */
-    onMouseOver: function(e) {
+    onMouseOver: function() {
         if (this.owner && this.owner.setVisibleItem) {
             this.owner.setVisibleItem(this);
         }
-        this.show(e);
     }
 });
 
-// $Id: combo.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: combo.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Button.Combo
  *
@@ -12900,8 +19931,7 @@
  */
 Jx.Button.Combo = new Class({
     Family: 'Jx.Button.Combo',
-    Extends: Jx.Button.Multi,
-    domObj : null,
+    Extends: Jx.Button,
     ul : null,
     /**
      * Property: currentSelection
@@ -12910,79 +19940,34 @@
     currentSelection : null,
     
     options: {
-        /* Option: editable
-         * boolean, default false.  Can the value be edited by the user?
-         */
-        editable: false,
         /* Option: label
          * string, default ''.  The label to display next to the combo.
          */
-        label: ''
-    },
-    
+        label: '',
+        /* Option: template
+         */
+         template: '<span class="jxButtonContainer"><a class="jxButton jxButtonCombo"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
+     },
+        
     /** 
-     * Constructor: Jx.Combo
+     * APIMethod: render
      * create a new instance of Jx.Combo
-     *
-     * Parameters:
-     * options - <Jx.button.Combo.Options>
      */
-    initialize: function(options) {
-        this.parent(); //we don't want to pass options to parent
-        this.setOptions(options);
-        this.domA.removeClass('jxButtonMulti');
-        if (this.options.editable) {
-            // remove the button's normal A tag and replace it with a span
-            // so the input ends up not being inside an A tag - this was
-            // causing all kinds of problems for selecting text inside it
-            // due to some user-select: none classes that were introduced
-            // to make buttons not selectable in the first place.
-            //
-            // Ultimately I think we want to fix this so that the discloser
-            // in Jx.Button.Multi is a separate beast and we can use it here
-            // without inheriting from multi buttons
-            var s = new Element('span', {'class':'jxButton'});
-            s.adopt(this.domA.firstChild);
-            this.domA = s.replaces(this.domA);
-            this.domA.addClass('jxButtonComboDefault');
-            this.domA.addClass('jxButtonEditCombo');
-            this.domInput = new Element('input',{
-                type:'text',
-                events:{
-                    change: this.valueChanged.bindWithEvent(this),
-                    keydown: this.onKeyPress.bindWithEvent(this),
-                    focus: (function() {
-                        if (this.domA.hasClass('jxButtonComboDefault')) {
-                            this.domInput.value = '';
-                            this.domA.removeClass('jxButtonComboDefault');
-                        }
-                    }).bind(this)
-                },
-                value: this.options.label
-            });
-            this.domLabel.empty();
-            this.domLabel.addClass('jxComboInput');
-            this.domLabel.adopt(this.domInput);
-        } else {
-            this.discloser.dispose();
-            this.domA.addClass('jxButtonCombo');
-            this.addEvent('click', (function(e){
-                this.discloser.fireEvent('click', e);
-            }).bindWithEvent(this));
-        }
+    render: function() {
+        this.parent();
+
+        this.menu = new Jx.Menu();
+        this.menu.button = this;
+        this.buttonSet = new Jx.ButtonSet();
+
         this.buttonSet = new Jx.ButtonSet({
             onChange: (function(set) {
                 var button = set.activeButton;            
-                this.domA.removeClass('jxButtonComboDefault');
-                if (this.options.editable) {
-                    this.domInput.value = button.options.label;
-                } else {
-                    var l = button.options.label;
-                    if (l == '&nbsp;') {
-                        l = '';
-                    }
-                    this.setLabel(l);
+                var l = button.options.label;
+                if (l == '&nbsp;') {
+                    l = '';
                 }
+                this.setLabel(l);
                 var img = button.options.image;
                 if (img.indexOf('a_pixel') != -1) {
                     img = '';
@@ -13001,29 +19986,50 @@
         if (this.options.items) {
             this.add(this.options.items);
         }
-        this.setEnabled(this.options.enabled);
-    },
-    
-    /**
-     * Method: setEnabled
-     * enable or disable the combo button.
-     *
-     * Parameters:
-     * enabled - {Boolean} the new enabled state of the button
-     */
-    setEnabled: function(enabled) {
-        this.options.enabled = enabled;
-        if (this.options.enabled) {
-            this.domObj.removeClass('jxDisabled');
-            if (this.domInput) {
-                this.domInput.disabled = false;
+        var button = this;
+        this.addEvent('click', (function(e) {
+            if (this.items.length === 0) {
+                return;
             }
-        } else {
-            this.domObj.addClass('jxDisabled');
-            if (this.domInput) {
-                this.domInput.disabled = true;
+            if (!button.options.enabled) {
+                return;
             }
-        }
+            this.contentContainer.setStyle('visibility','hidden');
+            this.contentContainer.setStyle('display','block');
+            $(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.hideWatcher);
+            document.addEvent('keyup', this.keypressWatcher);
+            
+            this.fireEvent('show', this);
+        }).bindWithEvent(this.menu));
+        
+        this.menu.addEvents({
+            'show': (function() {
+                this.domA.addClass('jxButtonActive');                    
+            }).bind(this),
+            'hide': (function() {
+                if (this.options.active) {
+                    this.domA.addClass('jxButtonActive');                    
+                }
+            }).bind(this)
+        });
+        
     },
     
     /**
@@ -13035,6 +20041,18 @@
     },
     
     /**
+     * Method: getValue
+     * returns the currently selected value
+     */
+    getValue: function() {
+        return this.options.label;
+    },
+    
+    setValue: function() {
+        
+    },
+    
+    /**
      * Method: onKeyPress
      * Handle the user pressing a key by looking for an ENTER key to set the
      * value.
@@ -13076,48 +20094,649 @@
      */
     remove: function(idx) {
         //TODO: implement remove?
+    }
+});// $Id: toolbar.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Toolbar
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A toolbar is a container object that contains other objects such as
+ * buttons.  The toolbar organizes the objects it contains automatically,
+ * wrapping them as necessary.  Multiple toolbars may be placed within
+ * the same containing object.
+ *
+ * Jx.Toolbar includes CSS classes for styling the appearance of a
+ * toolbar to be similar to traditional desktop application toolbars.
+ *
+ * There is one special object, Jx.ToolbarSeparator, that provides
+ * a visual separation between objects in a toolbar.
+ *
+ * While a toolbar is generally a *dumb* container, it serves a special
+ * purpose for menus by providing some infrastructure so that menus can behave
+ * properly.
+ *
+ * In general, almost anything can be placed in a Toolbar, and mixed with 
+ * anything else.
+ *
+ * Example:
+ * The following example shows how to create a Jx.Toolbar instance and place
+ * two objects in it.
+ *
+ * (code)
+ * //myToolbarContainer is the id of a <div> in the HTML page.
+ * function myFunction() {}
+ * var myToolbar = new Jx.Toolbar('myToolbarContainer');
+ * 
+ * var myButton = new Jx.Button(buttonOptions);
+ *
+ * var myElement = document.createElement('select');
+ *
+ * myToolbar.add(myButton, new Jx.ToolbarSeparator(), myElement);
+ * (end)
+ *
+ * Events:
+ * add - fired when one or more buttons are added to a toolbar
+ * remove - fired when on eor more buttons are removed from a toolbar
+ *
+ * Implements: 
+ * Options
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar = new Class({
+    Family: 'Jx.Toolbar',
+    Extends: Jx.Widget,
+    /**
+     * Property: items
+     * {Array} an array of the things in the toolbar.
+     */
+    items : null,
+    /**
+     * Property: domObj
+     * {HTMLElement} the HTML element that the toolbar lives in
+     */
+    domObj : null,
+    /**
+     * Property: isActive
+     * When a toolbar contains <Jx.Menu> instances, they want to know
+     * if any menu in the toolbar is active and this is how they
+     * find out.
+     */
+    isActive : false,
+    options: {
+        type: 'Toolbar',
+        /* Option: position
+         * the position of this toolbar in the container.  The position
+         * affects some items in the toolbar, such as menus and flyouts, which
+         * need to open in a manner sensitive to the position.  May be one of
+         * 'top', 'right', 'bottom' or 'left'.  Default is 'top'.
+         */
+        position: 'top',
+        /* Option: parent
+         * a DOM element to add this toolbar to
+         */
+        parent: null,
+        /* Option: autoSize
+         * if true, the toolbar will attempt to set its size based on the
+         * things it contains.  Default is false.
+         */
+        autoSize: false,
+        /* Option: scroll
+         * if true, the toolbar may scroll if the contents are wider than
+         * the size of the toolbar
+         */
+        scroll: true
     },
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.
+     */
+    render : function() {
+        this.parent();
+        this.items = [];
+        
+        this.domObj = new Element('ul', {
+            id: this.options.id,
+            'class':'jx'+this.options.type
+        });
+        
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+        this.deactivateWatcher = this.deactivate.bindWithEvent(this);
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+    },
     
     /**
-     * Method: setValue
-     * set the value of the Combo
+     * Method: addTo
+     * add this toolbar to a DOM element automatically creating a toolbar
+     * container if necessary
      *
      * Parameters:
-     * value - {Object} the new value.  May be a string, a text node, or
-     * another DOM element.
+     * parent - the DOM element or toolbar container to add this toolbar to.
      */
-    setValue: function(value) {
-        if (this.options.editable) {
-            this.domInput.value = value;
+    addTo: function(parent) {
+        var tbc = document.id(parent).retrieve('jxBarContainer');
+        if (!tbc) {
+            tbc = new Jx.Toolbar.Container({
+                parent: parent, 
+                position: this.options.position, 
+                autoSize: this.options.autoSize,
+                scroll: this.options.scroll
+            });
+        }
+        tbc.add(this);
+        return this;
+    },
+    
+    /**
+     * Method: add
+     * Add an item to the toolbar.  If the item being added is a Jx component
+     * with a domObj property, the domObj is added.  If the item being added
+     * is an LI element, then it is given a CSS class of *jxToolItem*.
+     * Otherwise, the thing is wrapped in a <Jx.ToolbarItem>.
+     *
+     * Parameters:
+     * thing - {Object} the thing to add.  More than one thing can be added
+     * by passing multiple arguments.
+     */
+    add: function( ) {
+        $A(arguments).flatten().each(function(thing) {
+            if (thing.domObj) {
+                thing = thing.domObj;
+            }
+            if (thing.tagName == 'LI') {
+                if (!thing.hasClass('jxToolItem')) {
+                    thing.addClass('jxToolItem');
+                }
+                this.domObj.appendChild(thing);
+            } else {
+                var item = new Jx.Toolbar.Item(thing);
+                this.domObj.appendChild(item.domObj);
+            }            
+        }, this);
+
+        if (arguments.length > 0) {
+            this.fireEvent('add', this);
+        }
+        return this;
+    },
+    /**
+     * Method: remove
+     * remove an item from a toolbar.  If the item is not in this toolbar
+     * nothing happens
+     *
+     * Parameters:
+     * item - {Object} the object to remove
+     *
+     * Returns:
+     * {Object} the item that was removed, or null if the item was not
+     * removed.
+     */
+    remove: function(item) {
+        if (item.domObj) {
+            item = item.domObj;
+        }
+        var li = item.findElement('LI');
+        if (li && li.parentNode == this.domObj) {
+            item.dispose();
+            li.dispose();
+            this.fireEvent('remove', this);
         } else {
-            this.setLabel(value);
+            return null;
         }
     },
+    /**
+     * Method: deactivate
+     * Deactivate the Toolbar (when it is acting as a menu bar).
+     */
+    deactivate: function() {
+        this.items.each(function(o){o.hide();});
+        this.setActive(false);
+    },
+    /**
+     * Method: isActive
+     * Indicate if the toolbar is currently active (as a menu bar)
+     *
+     * Returns:
+     * {Boolean}
+     */
+    isActive: function() { 
+        return this.isActive; 
+    },
+    /**
+     * Method: setActive
+     * Set the active state of the toolbar (for menus)
+     *
+     * Parameters: 
+     * b - {Boolean} the new state
+     */
+    setActive: function(b) { 
+        this.isActive = b;
+        if (this.isActive) {
+            document.addEvent('click', this.deactivateWatcher);
+        } else {
+            document.removeEvent('click', this.deactivateWatcher);
+        }
+    },
+    /**
+     * Method: setVisibleItem
+     * For menus, they want to know which menu is currently open.
+     *
+     * Parameters:
+     * obj - {<Jx.Menu>} the menu that just opened.
+     */
+    setVisibleItem: function(obj) {
+        if (this.visibleItem && this.visibleItem.hide && this.visibleItem != obj) {
+            this.visibleItem.hide();
+        }
+        this.visibleItem = obj;
+        if (this.isActive()) {
+            this.visibleItem.show();
+        }
+    },
+    showItem: function(item) {
+        this.fireEvent('show', item);
+    }
+});
+// $Id: container.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Toolbar.Container
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A toolbar container contains toolbars.  A single toolbar container fills
+ * the available space horizontally.  Toolbars placed in a toolbar container
+ * do not wrap when they exceed the available space.
+ *
+ * Events:
+ * add - fired when one or more toolbars are added to a container
+ * remove - fired when one or more toolbars are removed from a container
+ *
+ * Implements: 
+ * Options
+ * Events
+ * {<Jx.Addable>}
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar.Container = new Class({
+    Family: 'Jx.Toolbar.Container',
+    Extends: Jx.Widget,
+    /**
+     * Property: domObj
+     * {HTMLElement} the HTML element that the container lives in
+     */
+    domObj : null,
+    options: {
+        /* Option: parent
+         * a DOM element to add this to
+         */
+        parent: null,
+        /* Option: position
+         * the position of the toolbar container in its parent, one of 'top',
+         * 'right', 'bottom', or 'left'.  Default is 'top'
+         */
+        position: 'top',
+        /* Option: autoSize
+         * automatically size the toolbar container to fill its container.
+         * Default is false
+         */
+        autoSize: false,
+        /* Option: scroll
+         * Control whether the user can scroll of the content of the
+         * container if the content exceeds the size of the container.  
+         * Default is true.
+         */
+        scroll: true
+    },
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Container
+     */
+    render : function() {
+        this.parent();
+        
+        var d = document.id(this.options.parent);
+        this.domObj = d || new Element('div');
+        this.domObj.addClass('jxBarContainer');
+        
+        if (this.options.scroll) {
+            this.scroller = new Element('div', {'class':'jxBarScroller'});
+            this.domObj.adopt(this.scroller);
+        }
+
+        /* this allows toolbars to add themselves to this bar container
+         * once it already exists without requiring an explicit reference
+         * to the toolbar container
+         */
+        this.domObj.store('jxBarContainer', this);
+        
+        if (['top','right','bottom','left'].contains(this.options.position)) {
+            this.domObj.addClass('jxBar' +
+                           this.options.position.capitalize());            
+        } else {
+            this.domObj.addClass('jxBarTop');
+            this.options.position = 'top';
+        }
+
+        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));
+            
+            //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');
+            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)
+            });
+            
+            this.scrollRight = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.domObj);
+            this.scrollRight.domObj.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)
+            });         
+            
+        } else {
+            this.options.scroll = false;
+        }
+
+        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();
+        }
+    },
+    
+    measure: function() {
+        if (!this.options.scroll) { return; }
+        
+        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);
+                    } else {
+                        this.scroller.setStyle('left',this.scrollWidth);
+                    }
+                }
+            } 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);
+                }
+            }
+        }            
+    },
+    
     /**
-     * Method: getValue
-     * Return the current value
+     * Method: add
+     * Add a toolbar to the container.
      *
+     * Parameters:
+     * toolbar - {Object} the toolbar to add.  More than one toolbar
+     *    can be added by passing multiple arguments.
+     */
+    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('show', this.scrollIntoView.bind(this));                
+            }
+            if (this.scroller) {
+                this.scroller.adopt(thing.domObj);
+            } else {
+                this.domObj.adopt(thing.domObj);
+            }
+            this.domObj.addClass('jx'+thing.options.type+this.options.position.capitalize());
+        }, this);
+        if (this.options.scroll) {
+            this.update();            
+        }
+        if (arguments.length > 0) {
+            this.fireEvent('add', this);
+        }
+        return this;
+    },
+    /**
+     * Method: remove
+     * remove an item from a toolbar.  If the item is not in this toolbar
+     * nothing happens
+     *
+     * Parameters:
+     * item - {Object} the object to remove
+     *
      * Returns:
-     * {Object} returns the currently selected item
+     * {Object} the item that was removed, or null if the item was not
+     * removed.
      */
-    getValue: function() {
-        value = '';
-        if (this.options.editable) {
-            value = this.domInput.value;
+    remove: function(item) {
+        
+    },
+    /**
+     * Method: scrollIntoView
+     * scrolls an item in one of the toolbars into the currently visible
+     * area of the container if it is not already fully visible
+     *
+     * Parameters:
+     * item - the item to scroll.
+     */
+    scrollIntoView: function(item) {
+        var width = this.domObj.getSize().x;
+        var coords = item.domObj.getCoordinates(this.scroller);
+        
+        //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.
+        
+        //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;
         } else {
-            value = this.getLabel();
+            //otherwise, convert to int
+            l = l.toInt();
         }
-        return value;
+        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;
+            }
+        } 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;
+            }
+        }
+                
+        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);
+            }
+        }
     }
-});// $Id: panel.js 429 2009-05-12 16:10:47Z pagameba $
+});
+// $Id: toolbar.item.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
+ * Class: Jx.Toolbar.Item
+ * 
+ * Extends: Object
+ *
+ * Implements: Options
+ *
+ * A helper class to provide a container for something to go into 
+ * a <Jx.Toolbar>.
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar.Item = new Class( {
+    Family: 'Jx.Toolbar.Item',
+    Extends: Jx.Object,
+    options: {
+        /* Option: active
+         * is this item active or not?  Default is true.
+         */
+        active: true
+    },
+    /**
+     * Property: domObj
+     * {HTMLElement} an element to contain the thing to be placed in the
+     * toolbar.
+     */
+    domObj: null,
+    
+    parameters: ['jxThing'],
+    
+    /**
+     * APIMethod: init
+     * Create a new instance of Jx.Toolbar.Item.
+     */
+    init : function() {
+        this.al = [];
+        this.domObj = new Element('li', {'class':'jxToolItem'});
+        if (this.options.jxThing) {
+            if (this.options.jxThing.domObj) {
+                this.domObj.appendChild(this.options.jxThing.domObj);
+                if (this.options.jxThing instanceof Jx.Button.Tab) {
+                    this.domObj.addClass('jxTabItem');
+                }
+            } else {
+                this.domObj.appendChild(this.options.jxThing);
+                if (this.options.jxThing.hasClass('jxTab')) {
+                    this.domObj.addClass('jxTabItem');
+                }
+            }
+        }
+    }
+});// $Id: panel.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
+/**
  * Class: Jx.Panel
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.ContentLoader>
- *
  * A panel is a fundamental container object that has a content
  * area and optional toolbars around the content area.  It also
  * has a title bar area that contains an optional label and
@@ -13140,7 +20759,7 @@
  */
 Jx.Panel = new Class({
     Family: 'Jx.Panel',
-    Implements: [Options, Events, Jx.ContentLoader, Jx.Addable],
+    Extends: Jx.Widget,
     
     toolbarContainers: {
         top: null,
@@ -13150,7 +20769,7 @@
     },
     
      options: {
-        position: 'absolute',
+        position: null,
         type: 'Panel',
         /* Option: id
          * String, an id to assign to the panel's container
@@ -13224,18 +20843,14 @@
     },
     
     /** 
-     * Constructor: Jx.Panel
+     * APIMethod: render
      * Initialize a new Jx.Panel instance
-     *
-     * Options: <Jx.Panel.Options>, <Jx.ContentLoader.Options>
      */
-    initialize : function(options){
-        this.setOptions(options);
-        this.toolbars = options ? options.toolbars || [] : [];
+    render : function(){
+        this.parent();
+        this.toolbars = this.options ? this.options.toolbars || [] : [];
         
-        if ($defined(this.options.height) && !$defined(options.position)) {
-            this.options.position = 'relative';
-        }
+        this.options.position = ($defined(this.options.height) && !$defined(this.options.position)) ? 'relative' : 'absolute';
 
         /* set up the title object */
         this.title = new Element('div', {
@@ -13369,7 +20984,7 @@
         });
         this.domObj.adopt(this.contentContainer);
         
-        if ($type(this.options.toolbars) == 'array') {
+        if (Jx.type(this.options.toolbars) == 'array') {
             this.options.toolbars.each(function(tb){
                 var position = tb.options.position;
                 var tbc = this.toolbarContainers[position];
@@ -13441,16 +21056,16 @@
                     this.toolbarContainers[position].style.height = '';                
                 }
             }, this);
-            if ($type(this.options.toolbars) == 'array') {
+            if (Jx.type(this.options.toolbars) == 'array') {
                 this.options.toolbars.each(function(tb){
                     position = tb.options.position;
                     tbc = this.toolbarContainers[position];
                     // IE 6 doesn't seem to want to measure the width of 
                     // things correctly
                     if (Browser.Engine.trident4) {
-                        var oldParent = $(tbc.parentNode);
+                        var oldParent = document.id(tbc.parentNode);
                         tbc.style.visibility = 'hidden';
-                        $(document.body).adopt(tbc);                    
+                        document.id(document.body).adopt(tbc);                    
                     }
                     var size = tbc.getBorderBoxSize();
                     // put it back into its real parent now we are done 
@@ -13622,8 +21237,10 @@
             if (!this.domObj.hasClass('jx'+this.options.type+'Min')) {
                 this.domObj.addClass('jx'+this.options.type+'Min');
                 this.contentContainer.setStyle('display','none');
-                var margin = this.domObj.getMarginSize();
-                var height = margin.top + margin.bottom;
+                var m = this.domObj.measure(function(){
+                    return this.getSizes(['margin'],['top','bottom']).margin;
+                });
+                var height = m.top + m.bottom;
                 if (this.title.parentNode == this.domObj) {
                     height += this.title.getMarginBoxSize().height;
                 }
@@ -13649,14 +21266,12 @@
         this.fireEvent('close', this);
     }
     
-});// $Id: dialog.js 477 2009-07-09 17:41:50Z pagameba $
+});// $Id: dialog.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Dialog
  *
  * Extends: <Jx.Panel>
  *
- * Implements: <Jx.AutoPosition>, <Jx.Chrome>
- *
  * A Jx.Dialog implements a floating dialog.  Dialogs represent a useful way
  * to present users with certain information or application controls.
  * Jx.Dialog is designed to provide the same types of features as traditional
@@ -13696,7 +21311,7 @@
 Jx.Dialog = new Class({
     Family: 'Jx.Dialog',
     Extends: Jx.Panel,
-    Implements: [Jx.AutoPosition, Jx.Chrome],
+    //Implements: [Jx.AutoPosition, Jx.Chrome],
     
     /**
      * Property: {HTMLElement} blanket
@@ -13751,7 +21366,7 @@
          * the dialog is to be contained by.  The default value is for the dialog
          * to be contained by the body element.
          */
-        parent: null,
+        //parent: null,
         /* Option: resize
          * (optional) {Boolean} determines whether the dialog is
          * resizeable by the user or not.  Default is false.
@@ -13773,26 +21388,23 @@
         close: true
     },
     /**
-     * Constructor: Jx.Dialog
-     * Construct a new instance of Jx.Dialog
-     *
-     * Parameters: 
-     * options - {Object} an object containing options for the dialog.
-     *
-     * Options: <Jx.Dialog.Options>, <Jx.Panel.Options>, <Jx.ContentLoader.Options>
+     * APIMethod: render
+     * renders Jx.Dialog
      */
-    initialize: function(options) {
+    render: function() {
         this.isOpening = false;
         this.firstShow = true;
         
-        /* initialize the panel overriding the type and position */
-        this.parent($merge(
+        this.options = $merge(
             {parent:document.body}, // these are defaults that can be overridden
-            options,
+            this.options,
             {type:'Dialog', position: 'absolute'} // these override anything passed to the options
-        ));
+        );
         
-        this.options.parent = $(this.options.parent);
+        /* initialize the panel overriding the type and position */
+        this.parent();
+        this.openOnLoaded = this.open.bind(this);
+        this.options.parent = document.id(this.options.parent);
         
         if (this.options.modal) {
             this.blanket = new Element('div',{
@@ -13803,7 +21415,7 @@
                 }
             });
             this.blanket.resize = (function() {
-                var ss = $(document.body).getScrollSize();
+                var ss = document.id(document.body).getScrollSize();
                 this.setStyles({
                     width: ss.x,
                     height: ss.y
@@ -13964,9 +21576,11 @@
         }
         
         if (this.options.closed) {
-            var margin = this.domObj.getMarginSize();
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
             var size = this.title.getMarginBoxSize();
-            this.domObj.resize({height: margin.top + size.height + margin.bottom});
+            this.domObj.resize({height: m.top + size.height + m.bottom});
             this.fireEvent('collapse');
         } else {
             this.domObj.resize(this.options);
@@ -14002,9 +21616,11 @@
         }
         
         if (this.options.closed) {
-            var margin = this.domObj.getMarginSize();
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
             var size = this.title.getMarginBoxSize();
-            this.domObj.resize({height: margin.top + size.height + margin.bottom});
+            this.domObj.resize({height: m.top + size.height + m.bottom});
         } else {
             this.domObj.resize(this.options);            
         }
@@ -14053,9 +21669,13 @@
     openURL: function(url) {
         if (url) {
             this.options.contentURL = url;
+            this.options.content = null;  //force Url loading
             this.loadContent(this.content);
+            this.addEvent('contentLoaded', this.openOnLoaded); 
         }
-        this.open();
+        else {
+            this.open();
+        }
     },
     
     /**
@@ -14070,11 +21690,12 @@
             this.isOpening = true;
         }
         if (this.contentIsLoaded) {
+            this.removeEvent('contentLoaded', this.openOnLoaded);
             this.show();
             this.fireEvent('open', this);
             this.isOpening = false;
         } else {
-            this.addEvent('contentLoaded', this.open.bind(this));
+            this.addEvent('contentLoaded', this.openOnLoaded);
         }
     },
     /**
@@ -14086,6 +21707,10 @@
         this.isOpening = false;
         this.hide();
         this.fireEvent('close');
+    },
+    
+    cleanup: function() {
+        this.blanket.destroy();
     }
 });
 
@@ -14097,22 +21722,20 @@
         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*2);
+        var z = Jx.Dialog.BaseZIndex+i;
         if (d.blanket) {
-            d.blanket.setStyle('zIndex',z-1);
+            d.blanket.setStyle('zIndex',z);
         }
         d.domObj.setStyle('zIndex',z);
     });
     
 };
-// $Id: splitter.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: splitter.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Splitter
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
- * Implements: Options
- *
  * a Jx.Splitter creates two or more containers within a parent container
  * and provides user control over the size of the containers.  The split
  * can be made horizontally or vertically.
@@ -14133,7 +21756,7 @@
  
 Jx.Splitter = new Class({
     Family: 'Jx.Splitter',
-    Implements: [Options],
+    Extends: Jx.Object,
     /**
      * Property: domObj
      * {HTMLElement} the element being split
@@ -14217,18 +21840,15 @@
          */
         onFinish: null
     },
+    
+    parameters: ['domObj','options'],
+    
     /**
-     * Constructor: Jx.Splitter
+     * APIMethod: init
      * Create a new instance of Jx.Splitter
-     *
-     * Parameters:
-     * domObj - {HTMLElement} the element or id of the element to split
-     * options - <Jx.Splitter.Options>
      */
-    initialize: function(domObj, options) {
-        this.setOptions(options);  
-        
-        this.domObj = $(domObj);
+    init: function() {
+        this.domObj = document.id(this.options.domObj);
         this.domObj.addClass('jxSplitContainer');
         var jxLayout = this.domObj.retrieve('jxLayout');
         if (jxLayout) {
@@ -14249,10 +21869,10 @@
             for (var i=0; i<nSplits; i++) {
                 var el;
                 if (this.options.elements && this.options.elements[i]) {
-                    if (options.elements[i].domObj) {
-                        el = options.elements[i].domObj;
+                    if (this.options.elements[i].domObj) {
+                        el = this.options.elements[i].domObj;
                     } else {
-                        el = $(this.options.elements[i]);                        
+                        el = document.id(this.options.elements[i]);                        
                     }
                     if (!el) {
                         el = this.prepareElement();
@@ -14380,9 +22000,17 @@
                 new Drag(bar, {
                     //limit: limit,
                     modifiers: modifiers,
-                    onSnap : function(obj) {
+                    onSnap : (function(obj) {
                         obj.addClass('jxSplitBarDrag');
-                    },
+                        this.fireEvent('snap',[obj]);
+                    }).bind(this),
+                    onCancel: (function(obj){
+                        mask.destroy();  
+                        this.fireEvent('cancel',[obj]);
+                    }).bind(this),
+                    onDrag: (function(obj, event){
+                        this.fireEvent('drag',[obj,event]);
+                    }).bind(this),
                     onComplete : (function(obj) {
                         mask.destroy();
                         obj.removeClass('jxSplitBarDrag');
@@ -14390,17 +22018,15 @@
                             return;
                         }
                         fn.apply(this,[obj]);
+                        this.fireEvent('complete',[obj]);
+                        this.fireEvent('finish',[obj]);
                     }).bind(this),
-                    onStart: (function(obj) {
+                    onBeforeStart: (function(obj) {
+                        this.fireEvent('beforeStart',[obj]);
                         mask = new Element('div',{'class':'jxSplitterMask'}).inject(obj, 'after');
-                        if (this.options.onStart) {
-                            this.options.onStart();
-                        }
                     }).bind(this),
-                    onFinish: (function() {
-                        if (this.options.onFinish) {
-                            this.options.onFinish();
-                        }
+                    onStart: (function(obj, event) {
+                        this.fireEvent('start',[obj, event]);
                     }).bind(this)
                 });
             }, this);            
@@ -14422,7 +22048,10 @@
         var leftJxl = leftSide.retrieve('jxLayout');
         var rightJxl = rightSide.retrieve('jxLayout');
         
-        var paddingLeft = this.domObj.getPaddingSize().left;
+        var paddingLeft = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
         
         /* process right side first */
         var rsLeft, rsWidth, rsRight;
@@ -14524,8 +22153,12 @@
         var topJxl = topSide.retrieve('jxLayout');
         var bottomJxl = bottomSide.retrieve('jxLayout');
         
-        var paddingTop = this.domObj.getPaddingSize().top;
+        var paddingTop = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['top']);
+            return m.padding.top;
+        });
         
+        
         /* measure the bar and parent container for later use */
         var size = obj.retrieve('size');
         if (!size) {
@@ -14675,7 +22308,10 @@
         /* account for rounding errors */
         var remainder = availableSpace % nVariable;
         
-        var leftPadding = this.domObj.getPaddingSize().left;
+        var leftPadding = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
 
         var currentPosition = 0;
 
@@ -14779,7 +22415,10 @@
         /* account for rounding errors */
         var remainder = availableSpace % nVariable;
 
-        var paddingTop = this.domObj.getPaddingSize().top;
+        var paddingTop = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['top']);
+            return m.padding.top;
+        });
         
         var currentPosition = 0;
 
@@ -14834,14 +22473,12 @@
              }
          }
     }
-});// $Id: panelset.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: panelset.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.PanelSet
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.Addable>
- *
  * 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
@@ -14870,7 +22507,7 @@
  */
 Jx.PanelSet = new Class({
     Family: 'Jx.PanelSet',
-    Implements: [Options, Events, Jx.Addable],
+    Extends: Jx.Widget,
     
     options: {
         /* Option: parent
@@ -14903,21 +22540,14 @@
      */
     firstLayout: true,
     /**
-     * Constructor: Jx.PanelSet
+     * APIMethod: render
      * Create a new instance of Jx.PanelSet.
-     *
-     * Parameters:
-     * options - <Jx.PanelSet.Options>
-     *
-     * TODO: Jx.PanelSet.initialize
-     * Remove the panels parameter in favour of an add method.
      */
-    initialize: function(options) {
-        if (options && options.panels) {
-            this.panels = options.panels;
-            options.panels = null;
+    render: function() {
+        if (this.options.panels) {
+            this.panels = this.options.panels;
+            this.options.panels = null;
         }
-        this.setOptions(options);
         this.domObj = new Element('div');
         new Jx.Layout(this.domObj);
         
@@ -14946,7 +22576,7 @@
                 
                 var panel = this.panels[i];
                 panel.title.setStyle('visibility', 'hidden');
-                $(document.body).adopt(panel.title);
+                document.id(document.body).adopt(panel.title);
                 var size = panel.title.getBorderBoxSize();
                 bar.adopt(panel.title);
                 panel.title.setStyle('visibility','');
@@ -14958,7 +22588,7 @@
             }).bind(this)
         });
         this.addEvent('addTo', function() {
-            $(this.domObj.parentNode).setStyle('overflow', 'hidden');
+            document.id(this.domObj.parentNode).setStyle('overflow', 'hidden');
             this.domObj.resize();
         });
         if (this.options.parent) {
@@ -15072,451 +22702,4052 @@
         }
         panel.domObj.resize({top: top, height:panelSize, bottom: null});
     }
-});// $Id: grid.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id:$
 /**
+ * Class: Jx.Dialog.Message
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Confirm is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user. It only presents an OK button.
+ *
+ * License: 
+ * Copyright (c) 2009, Jonathan Bomgardner
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Message = new Class({
+    
+    Extends: Jx.Dialog,
+    
+    options: {
+        /**
+         * Option: message
+         * The message to display to the user
+         */
+        message: '',
+        /**
+         * Jx.Dialog option defaults
+         */
+        width: 300,
+        height: 150,
+        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'});
+        this.buttons.add(
+            new Jx.Button({
+                label: 'Ok',
+                onClick: this.onClick.bind(this, 'Ok')
+            })
+        );
+        this.options.toolbars = [this.buttons];
+        if (Jx.type(this.options.message) === 'string') {
+            this.question = new Element('div', {
+                'class': 'jxMessage',
+                html: this.options.message
+            });
+        } else {
+            this.question = this.options.question;
+            $(this.question).addClass('jxMessage');
+        }
+        this.options.content = this.question;
+        this.parent();
+    },
+    /**
+     * Method: onClick
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onClick: function (value) {
+        this.close();
+    }
+    
+    
+});
+// $Id:$
+/**
+ * Class: Jx.Dialog.Confirm
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Confirm is an extension of Jx.Dialog that allows the developer
+ * to prompt their user with e yes/no question.
+ *
+ * License: 
+ * Copyright (c) 2009, Jonathan Bomgardner
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Confirm = new Class({
+    
+    Extends: Jx.Dialog,
+    
+    options: {
+        /**
+         * Option: question 
+         * The question to ask the user
+         */
+        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,
+        height: 150,
+        close: false,
+        resize: true,
+        collapse: false
+    },
+    /**
+     * APIMethod: render
+     * creates the dialog
+     */
+    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)
+            })
+        );
+        this.options.toolbars = [this.buttons];
+        if (Jx.type(this.options.question) === 'string') {
+            this.question = new Element('div', {
+                'class': 'jxConfirmQuestion',
+                html: this.options.question
+            });
+        } else {
+            this.question = this.options.question;
+            $(this.question).addClass('jxConfirmQuestion');
+        }
+        this.options.content = this.question;
+        this.parent();
+    },
+    /**
+     * Method: onClick
+     * called when any button is clicked. It hides the dialog and fires
+     * the close event passing it the value of the button that was pressed.
+     */
+    onClick: function (value) {
+        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));
+    }
+});
+// $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: $
+/**
+ * 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
+ * before creating a new tip. If one is there we remove it and create this new
+ * one.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Tooltip = new Class({
+
+    Extends : Jx.Widget,
+
+    options : {
+        /**
+         * Option: offsets
+         * An object with x and y components for where to put the tip related to
+         * the mouse cursor.
+         */
+        offsets : {
+            x : 15,
+            y : 15
+        },
+        /**
+         * Option: showDelay
+         * The amount of time to delay before showing the tip. This ensures we
+         * don't show a tip if we're just passing over an element quickly.
+         */
+        showDelay : 100,
+        /**
+         * Option: cssClass
+         * a class to be added to the tip's container. This can be used to style
+         * the tip.
+         */
+        cssClass : null
+    },
+    
+    /**
+     * Parameters:
+     * target - The DOM element that triggers the toltip when moused over.
+     * tip - The contents of the tip itself. This can be either a string or
+     *       an Element.
+     * options - <Jx.Tooltip.Options> and <Jx.Widget.Options>
+     */
+    parameters: ['target','tip','options'],
+
+    /**
+     * APIMethod: render
+     * Creates the tooltip
+     * 
+     
+     */
+    render : function () {
+        this.parent();
+        this.target = document.id(this.options.target);
+
+        var t = this.target.retrieve('Tip');
+        if (t) {
+            this.target.eliminate('Tip');
+        }
+
+        //set up the tip options
+        this.domObj = new Element('div', {
+            styles : {
+                'position' : 'absolute',
+                'top' : 0,
+                'left' : 0,
+                'visibility' : 'hidden'
+            }
+        }).inject(document.body);
+    
+        if (Jx.type(this.options.tip) === 'string') {
+            this.domObj.set('html', this.options.tip);
+        } else {
+            this.domObj.grab(this.options.tip);
+        }
+    
+        this.domObj.addClass('jxTooltip');
+        if ($defined(this.options.cssClass)) {
+            this.domObj.addClass(this.options.cssClass);
+        }
+    
+        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));
+    
+    },
+    
+    /**
+     * Method: enter
+     * Method run when the cursor passes over an element with a tip
+     * 
+     * Parameters:
+     * event - the event object
+     * element - the element the cursor passed over
+     */
+    enter : function (event, element) {
+        this.timer = $clear(this.timer);
+        this.timer = (function () {
+            this.domObj.setStyle('visibility', 'visible');
+            this.position(event);
+        }).delay(this.options.delay, this);
+    },
+    /**
+     * Method: leave
+     * Executed when the mouse moves out of an element with a tip
+     * 
+     * Parameters:
+     * event - the event object
+     * element - the element the cursor passed over
+     */
+    leave : function (event, element) {
+        this.timer = $clear(this.timer);
+        this.timer = (function () {
+            this.domObj.setStyle('visibility', 'hidden');
+        }).delay(this.options.delay, this);
+    },
+    /**
+     * Method: move
+     * Called when the mouse moves over an element with a tip.
+     * 
+     * Parameters:
+     * event - the event object
+     */
+    move : function (event) {
+        this.position(event);
+    },
+    /**
+     * Method: position
+     * Called to position the tooltip.
+     * 
+     * Parameters:
+     * event - the event object
+     */
+    position : function (event) {
+        var size = window.getSize(), scroll = window.getScroll();
+        var tipSize = this.domObj.getMarginBoxSize();
+        var tip = {
+            x : this.domObj.offsetWidth,
+            y : this.domObj.offsetHeight
+        };
+        var tipPlacement = { 
+            x: event.page.x + this.options.offsets.x, 
+            y: event.page.y + this.options.offsets.y 
+        };
+        
+        if (event.page.y + this.options.offsets.y + tip.y + tipSize.height - scroll.y > size.y) {
+            tipPlacement.y = event.page.y - this.options.offsets.y - tipSize.height - scroll.y;
+        }
+        
+        if (event.page.x + this.options.offsets.x + tip.x + tipSize.width - scroll.x > size.x) {
+            tipPlacement.x = event.page.x - this.options.offsets.x - tipSize.width - scroll.x;
+        }
+        
+        this.domObj.setStyle('top', tipPlacement.y);
+        this.domObj.setStyle('left', tipPlacement.x);
+    },
+    /**
+     * Method: detach
+     * Called to manually remove a tooltip.
+     */
+    detach : function () {
+        this.target.eliminate('Tip');
+        this.destroy();
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Field 
+ *
+ * Extends: <Jx.Widget>
+ * 
+ * This class is the base class for all form fields.
+ * The class will also allow for displaying error messages generated by
+ * form validation.
+ * 
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field = new Class({
+
+    Extends : Jx.Widget,
+    pluginNamespace: 'Field',
+    
+    options : {
+        /**
+         * Option: id
+         * The ID of the field.
+         */
+        id : null,
+        /**
+         * Option: name
+         * The name of the field (used when submitting to the server). Will also be used for the
+         * name attribute of the field. 
+         */
+        name : null,
+        /**
+         * Option: label
+         * The text that goes next to the field. 
+         */
+        label : null,
+        /**
+         * Option: labelSeparator
+         * A character to use as the separator between the label and the input. 
+         * Make it an empty string for no separator. 
+         */
+        labelSeparator : ":",
+        /**
+         * Option: value
+         * A default value to populate the field with. 
+         */
+        value : null,
+        /**
+         * Option: tag
+         * a string to use as the HTML of the tag element (default is a
+         * <span> element). 
+         */
+        tag : null,
+        /**
+         * Option: tip
+         * A string that will eventually serve as a tooltip for an input field. 
+         * Currently only implemented as OverText for text fields. 
+         */
+        tip : null,
+        /**
+         * Option: template
+         * A string holding the template for the field.
+         */
+        template : null,
+        /**
+         * Option: containerClass
+         * a CSS class that will be added to the containing element. 
+         */
+        containerClass : null,
+        /**
+         * Option: labelClass
+         * a CSS to add to the label 
+         */
+        labelClass : null,
+        /**
+         * Option: fieldClass
+         * a CSS class to add to the input field
+         */
+        fieldClass : null,
+        /**
+         * Option: tagClass
+         * a CSS class to add to the tag field 
+         */
+        tagClass : null,
+        /**
+         * Option: required
+         * Whether the field is required. Setting this to true will trigger
+         * the addition of a "required" validator class and the form
+         * will not submit until it is filled in and validates. 
+         */
+        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.
+         */
+        readonly : false,
+        /**
+         * Option: disabled
+         * {True|False} defaults to false. Whether this field is disabled.
+         */
+        disabled : false
+
+    },
+    
+    /**
+     * Property: overtextOptions
+     * The default options Jx uses for mootools-more's OverText
+     * plugin
+     */
+    overtextOptions : {
+        element : 'label'
+    },
+
+    /**
+     * Property: field 
+     * An element representing the input field itself.
+     */
+    field : null,
+    /**
+     * Property: label 
+     * A reference to the label element for this field
+     */
+    label : null,
+    /**
+     * Property: tag 
+     * A reference to the "tag" field of this input if available
+     */
+    tag : null,
+    /**
+     * Property: id 
+     * The name of this field.
+     */
+    id : null,
+    /**
+     * Property: overText 
+     * The overText instance for this field.
+     */
+    overText : null,
+    /**
+     * Property: type 
+     * Indicates that this is a field type
+     */
+    type : 'field',
+    /**
+     * Property: classes 
+     * The classes to search for in the template. Not
+     * required, but we look for them.
+     */
+    classes : [ 'jxInputLabel', 'jxInputTag' ],
+    
+    /**
+     * APIMethod: render
+     */
+    render : function () {
+        this.parent();
+
+        this.id = ($defined(this.options.id)) ? this.options.id : this
+                .generateId();
+        this.name = this.options.name;
+
+        // first the container
+        this.domObj = new Element('span', {
+            'class' : 'jxInputContainer'
+        });
+        
+        if ($defined(this.type)) {
+            this.domObj.addClass('jxInputContainer'+this.type);
+        }
+        
+        if ($defined(this.options.containerClass)) {
+            this.domObj.addClass(this.options.containerClass);
+        }
+        if ($defined(this.options.required) && this.options.required) {
+            this.domObj.addClass('jxFieldRequired');
+            if ($defined(this.options.validatorClasses)) {
+                this.options.validatorClasses = 'required ' + this.options.validatorClasses;
+            } else {
+                this.options.validatorClasses = 'required';
+            }
+        }
+
+        var field = 'jxInput' + this.type;
+        this.classes.push(field);
+        
+        var name = $defined(this.options.name) ? this.options.name : '';
+        var template = this.options.template.substitute({name:name});
+        var els = this.processTemplate(template, this.classes, this.domObj);
+
+        // LABEL
+        if (els.has('jxInputLabel')) {
+            this.label = els.get('jxInputLabel');
+            if ($defined(this.options.labelClass)) {
+                this.label.addClass(this.options.labelClass);
+            }
+            if ($defined(this.options.label)) {
+                this.label.set('html', this.options.label
+                        + this.options.labelSeparator);
+            }
+            
+            this.label.set('for', this.id);
+            
+            if (this.options.required) {
+                var em = new Element('em', {
+                    'html' : this.options.requiredText,
+                    'class' : 'required'
+                });
+                em.inject(this.label);
+            }
+        }
+
+        // FIELD
+        if (els.has(field)) {
+            this.field = els.get(field);
+            if ($defined(this.options.fieldClass)) {
+                this.field.addClass(this.options.fieldClass);
+            }
+
+            if ($defined(this.options.value)) {
+                this.field.set('value', this.options.value);
+            }
+
+            this.field.set('id', this.id);
+
+            if ($defined(this.options.readonly)
+                    && this.options.readonly) {
+                this.field.set("readonly", "readonly");
+                this.field.addClass('jxFieldReadonly'); 
+            }
+
+            if ($defined(this.options.disabled)
+                    && this.options.disabled) {
+                this.field.set("disabled", "disabled");
+                this.field.addClass('jxFieldDisabled'); 
+            }
+
+            // add validator classes
+            if ($defined(this.options.validatorClasses)) {
+                this.field.addClass(this.options.validatorClasses);
+            }
+
+            this.field.store('field', this);
+        }
+
+        // TAG
+        if (els.has('jxInputTag')) {
+            this.tag = els.get('jxInputTag');
+            if ($defined(this.options.tagClass)) {
+                this.tag.addClass(this.options.tagClass);
+            }
+            if ($defined(this.options.tag)) {
+                this.tag.set('html', this.options.tag);
+            }
+        }
+
+        if ($defined(this.options.form)
+                && this.options.form instanceof Jx.Form) {
+            this.form = this.options.form;
+            this.form.addField(this);
+        }
+
+    },
+    /**
+     * 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);
+    },
+
+    /**
+     * APIMethod: getValue 
+     * Returns the current value of the field.
+     */
+    getValue : function () {
+        return this.field.get("value");
+    },
+
+    /**
+     * APIMethod: reset 
+     * Sets the field back to the value passed in the
+     * original options
+     */
+    reset : function () {
+        this.field.set('value', this.options.value);
+        this.fireEvent('reset', this);
+    },
+    /**
+     * APIMethod: disable
+     * Disabled the field
+     */
+    disable : function () {
+        this.field.set("disabled", "disabled");
+        this.field.addClass('jxFieldDisabled');
+    },
+    /**
+     * APIMethod: enable
+     * Enables the field
+     */
+    enable : function () {
+        this.field.erase("disabled");
+        this.field.removeClass('jxFieldDisabled');
+    }
+
+});
+// $Id: $
+/**
+ * Class: Jx.Field.Text
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a text input field.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Text = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: overText
+         * an object holding options for mootools-more's OverText class. Leave it null to 
+         * not enable it, make it an object to enable.
+         */
+        overText: null,
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span>'
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Text',
+    
+    /**
+     * APIMethod: render
+     * Creates a text input field.
+     */
+    render: function () {
+        this.parent();
+        
+        //create the overText instance if needed
+        if ($defined(this.options.overText)) {
+            var opts = $extend({}, this.options.overText);
+            this.field.set('alt', this.options.tip);
+            this.overText = new OverText(this.field, opts);
+            this.overText.show();
+        }
+        
+    }
+    
+});// $Id: $
+/**
+ * Class: Jx.Field.Hidden
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a hidden input field.
+ *  
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Hidden = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<input class="jxInputHidden" type="hidden" name="{name}"/>'
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Hidden'
+    
+});
+
+
+
+
+// $Id: $
+/**
+ * Class: Jx.Form
+ * 
+ * Extends: <Jx.Widget>
+ * 
+ * A class that represents an HTML form. You add fields using either Jx.Form.add() 
+ * or by using the field's .addTo() method. You can get all form values or set them 
+ * using this class. It also handles validation of fields. 
+ *    
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Form = new Class({
+	
+	Extends: Jx.Widget,
+	
+	options: {
+        /**
+         * Option: method
+         * the method used to submit the form
+         */
+		method: 'post',
+		/**
+         * Option: action
+         * where to submit it to
+         */
+		action: '',
+		/**
+         * Option: fileUpload
+         * whether this form handles file uploads or not. 
+         */
+		fileUpload: false,
+		/**
+         * Option: id
+         * the id of this form
+         */
+		id: null,
+		/**
+         * Option: formClass
+         */
+		formClass: null,
+		/**
+         * Option: name
+         * the name property for the form
+         */
+		name: ''
+	},
+
+	/**
+     * Property: fields
+     * An array of all of the single fields (not contained in a fieldset) for this form
+     */
+    fields : new Hash(),
+    /**
+     * Property: pluginNamespace
+     * required variable for plugins
+     */
+    pluginNamespace: 'Form',
+
+    /**
+     * APIMethod: render
+     * Constructs the form but does not add it to anything to be shown. The caller
+     * should use form.addTo() to add the form to the DOM.
+     */
+    render : function () {
+        //create the form first
+        this.domObj = new Element('form', {
+            'method' : this.options.method,
+            'action' : this.options.action,
+            'class' : 'jxForm',
+            'name' : this.options.name
+        });
+        
+        if (this.options.fileUpload) {
+            this.domObj.set('enctype', 'multipart/form-data');
+        }
+        if ($defined(this.options.id)) {
+            this.domObj.set('id', this.options.id);
+        }
+        if ($defined(this.options.formClass)) {
+            this.domObj.addClass(this.options.formClass);
+        }
+    },
+
+    /**
+     * APIMethod: addField
+     * Adds a <Jx.Field> subclass to this form's fields hash
+     * 
+     * Parameters:
+     * field - <Jx.Field> to add
+     */
+    addField : function (field) {
+        this.fields.set(field.id, field);
+    },
+
+    
+    /**
+     * Method: isValid
+     * Determines if the form passes validation
+     * 
+     * Parameters:
+     * evt - the Mootools event object
+     */
+    isValid : function (evt) {
+        return true;
+    },
+
+    /**
+     * APIMethod: getValues
+     * Gets the values of all the fields in the form as a Hash object. This 
+     * uses the mootools function Element.toQueryString to get the values and 
+     * will either return the values as a querystring or as an object (using 
+     * mootools-more's String.parseQueryString method).
+     * 
+     * Parameters:
+     * asQueryString - {boolean} indicates whether to return the value as a 
+     *                  query string or an object.
+     */
+    getValues : function (asQueryString) {
+        var queryString = this.domObj.toQueryString();
+        if ($defined(asQueryString)) {
+            return queryString;
+        } else {
+            return queryString.parseQueryString();
+        }
+    },
+    /**
+     * APIMethod: setValues
+     * Used to set values on the form
+     * 
+     * Parameters:
+     * values - A Hash of values to set keyed by field name.
+     */
+    setValues : function (values) {
+        //TODO: This may have to change with change to getValues().
+        if (Jx.type(values) === 'object') {
+            values = new Hash(values);
+        }
+        this.fields.each(function (item) {
+            item.setValue(values.get(item.name));
+        }, this);
+    },
+
+    /**
+     * APIMethod: add
+     * 
+     * Parameters:
+     * Pass as many parameters as you like. However, they should all be
+     * <Jx.Field> objects.
+     */
+    add : function () {
+        var field;
+        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 (field instanceof Jx.Field && !$defined(field.form)) {
+                field.form = this;
+                this.addField(field);
+            }
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: reset
+     * 
+     */
+    reset : function () {
+        this.fields.each(function (field, name) {
+            field.reset();
+        }, this);
+        this.fireEvent('reset',this);
+    },
+    
+    getFieldsByName: function (name) {
+        var fields = [];
+        this.fields.each(function(val, id){
+            if (val.name === name) {
+                fields.push(val);
+            }
+        },this);
+        return fields;
+    }
+});
+/**
+ * Class: Jx.Field.File
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class is designed to work with an iFrame and APC upload progress.
+ * APC is a php specific technology but any server side implementation that
+ * works in the same manner should work. You can then wire this class to the 
+ * progress bar class to show progress.
+ * 
+ * The other option is to not use progress tracking and just use the base 
+ * upload which works through a hidden iFrame. In order to use this with Jx.Form
+ * you'll need to add it normally but keep a reference to it. When you call 
+ * Jx.Form.getValues() it will not return any file information. You can then 
+ * call the Jx.Field.File.upload() method for each file input directly and
+ * then submit the rest of the form via ajax.
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.File = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: template
+         * The template used to render the field
+         */
+        template: '<label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="jxInputTag"></span>',
+        /**
+         * Option: autoUpload
+         * Whether to upload the file immediatelly upon selection
+         */
+        autoUpload: false,
+        /**
+         * Option: Progress
+         * Whether to use the APC, or similar, progress method.
+         */
+        progress: false,
+        /**
+         * Option: progressIDUrl
+         * The url to call in order to get the ID, or key, to use
+         * with the APC upload process
+         */
+        progressIDUrl: '',
+        /**
+         * Option: progressName
+         * The name to give the field that holds the generated progress ID retrieved
+         * from the server. Defaults to 'APC_UPLOAD_PROGRESS' which is the default 
+         * for APC.
+         */
+        progressName: 'APC_UPLOAD_PROGRESS',
+        /**
+         * Option: progressId
+         * The id to give the form element that holds the generated progress ID 
+         * retrieved from the server. Defaults to 'progress_key'. 
+         */
+        progressId: 'progress_key',
+        /**
+         * Option: handlerUrl
+         * The url to send the file to.
+         */
+        handlerUrl: '',
+        /**
+         * Option: progressUrl
+         * The url used to retrieve the upload prgress of the file.
+         */
+        progressUrl: '',
+        /**
+         * Option: debug
+         * Defaults to false. If set to true it will prevent the hidden form
+         * and IFrame from being destroyed at the end of the upload so it can be
+         * inspected during development
+         */
+        debug: false,
+        /**
+         * Events
+         */
+        onUploadBegin: $empty,
+        onUploadComplete: $empty,
+        onUploadProgress: $empty,
+        onUploadError: $empty,
+        onFileSelected: $empty
+        
+    },
+    /**
+     * Property: type
+     * The Field type used in rendering
+     */
+    type: 'File',
+    /**
+     * APIMethod: render
+     * renders the file input
+     */
+    render: function () {
+        this.parent();
+        
+        //add a unique ID if no id is defined
+        if (!$defined(this.options.id)) {
+            this.field.set('id', this.generateId());
+        }
+        
+        //now, create the fake inputs
+        this.fake = new Element('div', {
+            'class' : 'jxFileFake'
+        });
+        this.text = new Jx.Field.Text({
+            template : '<input class="jxInputText" type="text" />'
+        });
+        this.browseButton = new Jx.Button({
+            label : 'Browse...'
+        });
+        
+        
+        this.fake.adopt(this.text, this.browseButton);
+        this.field.grab(this.fake, 'after');
+        
+        this.field.addEvents({
+            change : this.copyValue.bind(this),
+            mouseout : this.copyValue.bind(this),
+            mouseenter : this.mouseEnter.bind(this),
+            mouseleave : this.mouseLeave.bind(this)
+        });
+        
+    },
+    /**
+     * Method: copyValue
+     * Called when the value in the actual file input changes and when
+     * the mouse moves out of it to copy the value into the "fake" text box.
+     */
+    copyValue: function () {
+        if (this.field.value !== '' && (this.text.field.value !== this.field.value)) {
+            this.text.field.value = this.field.value;
+            this.fireEvent('fileSelected', this);
+        }
+    },
+    /**
+     * Method: mouseEnter
+     * Called when the mouse enters the actual file input to make the 
+     * fake button highlight.
+     */
+    mouseEnter: function () {
+        this.browseButton.domA.addClass('jxButtonPressed');
+    },
+    /**
+     * Method: mouseLeave
+     * called when the mouse leaves the actual file input to turn off
+     * the highlight of the fake button.
+     */
+    mouseLeave: function () {
+        this.browseButton.domA.removeClass('jxButtonPressed');
+    },
+    /**
+     * APIMethod: upload
+     * Call this to upload the file to the server
+     */
+    upload: function () {
+        this.fireEvent('uploadBegin', this);
+        //create the iframe
+        this.iframe = new IFrame(null, {
+            styles: {
+                display: 'none'
+            },
+            
+            name : this.generateId() 
+        });
+        this.iframe.inject(document.body);
+            
+        //load in the form
+        this.form = new Jx.Form({
+            action : this.options.handlerUrl,
+            name : 'jxUploadForm',
+            fileUpload: true
+        });
+        
+        //iframeBody.grab(this.form);
+        $(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));
+        //if polling the server we need an APC_UPLOAD_PROGRESS id.
+        //get it from the server.
+        if (this.options.progress) {
+            var req = new Request.JSON({
+                url: this.options.progressIDUrl,
+                method: 'get',
+                onSuccess: this.submitUpload.bind(this)
+            });
+            req.send();
+        } else {
+            this.submitUpload();
+        }
+    },
+    /**
+     * Method: submitUpload
+     * Called either after upload() or as a result of a successful call
+     * to get a progress ID.
+     * 
+     * Parameters:
+     * data - Optional. The data returned from the call for a progress ID. 
+     */
+    submitUpload: function (data) {
+        //check for ID in data
+        if ($defined(data) && data.success && $defined(data.id)) {
+            this.progressID = data.id;
+            //if have id, create hidden progress field
+            var id = new Jx.Field.Hidden({
+                name : this.options.progressName,
+                id : this.options.progressId,
+                value : this.progressID
+            });
+            id.addTo(this.form, 'top');
+        }
+        this.iframe.addEvent('load', this.processIFrameUpload.bind(this));
+        
+        
+        //submit the form
+        $(this.form).submit();
+        //begin polling if needed
+        if (this.options.progress && $defined(this.progressID)) {
+            this.pollUpload();
+        }
+    },
+    /**
+     * Method: pollUpload
+     * polls the server for upload progress information
+     */
+    pollUpload: function () {
+        var d = { id : this.progressID };
+        var r = new Request.JSON({
+            data: d,
+            url : this.options.progressUrl,
+            method : 'get',
+            onSuccess : this.processProgress.bind(this),
+            onFailure : this.uploadFailure.bind(this)
+        });
+        r.send();
+    },
+    
+    /**
+     * Method: processProgress
+     * process the data returned from the request
+     * 
+     * Parameters:
+     * data - The data from the request as an object.
+     */
+    processProgress: function (data) {
+        if ($defined(data)) {
+            this.fireEvent('uploadProgress', [data, this]);
+            if (data.current < data.total) {
+                this.polling = true;
+                this.pollUpload();
+            } else {
+                this.polling = false;
+                if (this.done) {
+                    this.uploadCleanUp();
+                    this.fireEvent('uploadComplete', [this.doneData, this]);
+                }
+            }
+        }
+    },
+    /**
+     * Method: uploadFailure
+     * called if there is a problem getting progress on the upload
+     */
+    uploadFailure: function (xhr) {
+        this.fireEvent('uploadProgressError', this);
+    },
+    /**
+     * Method: processIFrameUpload
+     * Called if we are not using progress and the IFrame finished loading the
+     * server response.
+     */
+    processIFrameUpload: function () {
+        //the body text should be a JSON structure
+        //get the body
+        var iframeBody = this.iframe.contentDocument.defaultView.document.body.innerHTML;
+        
+        var data = JSON.decode(iframeBody);
+        if ($defined(data.success) && data.success) {
+            this.done = true;
+            this.doneData = data;
+            if (!this.polling) {
+                this.uploadCleanUp();
+                this.fireEvent('uploadComplete', [data, this]);
+            }
+        } else {
+            this.fireEvent('uploadError', [data , this]);
+        }
+    },
+    /**
+     * Method: uploadCleanUp
+     * Cleans up the hidden form and IFrame after a completed upload. Set 
+     * this.options.debug to true to keep this from happening
+     */
+    uploadCleanUp: function () {
+        if (!this.options.debug) {
+            this.form.destroy();
+            this.iframe.destroy();
+        }
+    },
+    /**
+     * APIMethod: getFileName
+     * Allows caller to get the filename of the file we're uploading
+     */
+    getFileName: function () {
+        var fn = this.field.get('value');
+        return fn.slice(fn.lastIndexOf('/') + 1);
+    },
+    /**
+     * Method: getExt
+     * Returns the 3-letter extension of this file.
+     */
+    getExt: function () {
+        var fn = this.getFileName();
+        return fn.slice(fn.length - 3);
+    }
+});
+/**
+ * Class: Jx.Progressbar
+ *
+ * 
+ * Example:
+ * The following just uses the defaults.
+ * (code)
+ * var progressBar = new Jx.Progressbar();
+ * progressBar.addEvent('update',function(){alert('updated!');});
+ * progressBar.addEvent('complete',function(){
+ *      alert('completed!');
+ *      this.destroy();
+ * });
+ * 
+ * progressbar.addTo('container');
+ * 
+ * var total = 90;
+ * for (i=0; i < total; i++) {
+ *      progressbar.update(total, i);
+ * }
+ * (end)
+ * 
+ * Events:
+ * onUpdate - Fired when the bar is updated
+ * onComplete - fires when the progress bar completes it's fill
+ * 
+ */
+Jx.Progressbar = new Class({
+    
+    Extends: Jx.Widget,
+    
+    options: {
+        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
+         * have the bar calculate its own width.
+         */
+        bar: {
+            width: 'auto',
+            height: 20
+        },
+        /**
+         * Option: parent
+         * The element to put this progressbar into
+         */
+        parent: null,
+        /**
+         * Option: template
+         * The template used to create the progressbar
+         */
+        template: '<div class="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div>'
+    },
+    /**
+     * Property: classes
+     * The classes used in the template
+     */
+    classes: [
+        'jxProgressBar-message', 
+        'jxProgressBar',
+        'jxProgressBar-outline',
+        'jxProgressBar-fill',
+        'jxProgressBar-text'],
+    /**
+     * Property: bar
+     * the bar that is filled
+     */
+    bar: null,
+    /**
+     * Property: text
+     * the element that contains the text that's shown on the bar (if any).
+     */
+    text: null,
+    
+    /**
+     * APIMethod: render
+     * Creates a new progressbar.
+     */
+    render: function () {
+            
+        this.domObj = new Element('div', {
+            'class': 'jxProgressBar-container'
+        });
+        
+        var els = this.processTemplate(this.options.template,this.classes,this.domObj);
+        
+        if ($defined(this.options.parent)) {
+            this.domObj.inject($(this.options.parent));
+        }
+        
+        //determine width of progressbar
+        if (this.options.bar.width === 'auto') {
+            //get width of container
+            this.options.bar.width = this.domObj.getStyle('width').toInt();
+        }
+        
+        //determine height
+        if (this.options.bar.height === 'auto') {
+            this.options.bar.height = this.domObj.getStyle('height').toInt() - 4;
+        }
+        
+        //Message
+        if (els.has('jxProgressBar-message')) {
+            this.message = els.get('jxProgressBar-message');
+            if ($defined(this.options.messageText)) {
+                this.message.set('html', this.options.messsageText);
+            } else {
+                this.message.destroy();
+            }
+        }
+        
+        //bar container itself
+        if (els.has('jxProgressBar')) {
+            this.container = els.get('jxProgressBar');
+            this.container.setStyles({
+                'position': 'relative',
+                'width': this.options.bar.width,
+                'height' : this.options.bar.height + 4
+            });
+        }
+        
+        //Outline
+        if (els.has('jxProgressBar-outline')) {
+            this.outline = els.get('jxProgressBar-outline');
+            this.outline.setStyles({
+                'width': this.options.bar.width,
+                'height' : this.options.bar.height
+            });
+        }
+        
+        //Fill
+        if (els.has('jxProgressBar-fill')) {
+            this.fill = els.get('jxProgressBar-fill');
+            this.fill.setStyles({
+                'width': 0,
+                'height' : this.options.bar.height
+            });
+        }
+        
+        //TODO: check for {progress} and {total} in progressText
+        var obj = {};
+        if (this.options.progressText.contains('{progress}')) {
+            obj.progress = 0;
+        }
+        if (this.options.progressText.contains('{total}')) {
+            obj.total = 0;
+        }
+        
+        //Progress text
+        if (els.has('jxProgressBar-text')) {
+            this.text = els.get('jxProgressBar-text');
+            this.text.set('html', this.options.progressText.substitute(obj));
+        }
+        
+    },
+    /**
+     * APIMethod: update
+     * called to update the progress bar with new percentage.
+     * 
+     * Parameters: 
+     * total - the total # to progress up to
+     * progress - the current position in the progress (must be less than or
+     *              equal to the total)
+     */
+    update: function (total, progress) {
+        var newWidth = (progress * this.options.bar.width) / total;
+        
+        //update bar width
+        //TODO: animate this
+        
+        this.fill.get('tween', {property: 'width', onComplete: (function () {
+            var obj = {};
+            if (this.options.progressText.contains('{progress}')) {
+                obj.progress = progress;
+            }
+            if (this.options.progressText.contains('{total}')) {
+                obj.total = total;
+            }
+            var t = this.options.progressText.substitute(obj);
+            this.text.set('text', t);
+            
+            if (total === progress) {
+                this.complete = true;
+                this.fireEvent('complete');
+            } else {
+                this.fireEvent('update');
+            }
+        }).bind(this)}).start(newWidth);
+        
+    }
+    
+});
+/**
+ * Class: Jx.Panel.FileUpload
+ * 
+ * Extends: <Jx.Panel>
+ * 
+ * This class extends Jx.Panel to provide a consistent interface for uploading
+ * files in an application. 
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.FileUpload = new Class({
+    
+    Extends: Jx.Panel,
+    
+    options: {
+        /**
+         * Option: file
+         * An object containing the options for Jx.Field.File
+         */
+        file: {
+            autoUpload: false,
+            progress: false,
+            progressIDUrl: '',
+            handlerUrl: '',
+            progressUrl: ''
+        },
+        /**
+         * Option: onFileUploadComplete
+         * An event handler that is called when a file has been uploaded
+         */
+        onFileComplete: $empty,
+        /**
+         * Option: onComplete
+         * An event handler that is called when all files have been uploaded
+         */
+        onComplete: $empty,
+        /**
+         * Option: prompt
+         * The prompt to display at the top of the panel - before the 
+         * file input
+         */
+        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
+         */
+        removeOnComplete: false
+    },
+    /**
+     * Property: domObjA
+     * An HTML Element used to hold the interface while it is being
+     * constructed.
+     */
+    domObjA: null,
+    /**
+     * Property: fileQueue
+     * An array holding Jx.Field.File elements that are to be uploaded
+     */
+    fileQueue: [],
+    /**
+     * APIMethod: render
+     * Sets up the upload panel.
+     */
+    render: function () {
+        //first create panel content
+        this.domObjA = new Element('div', {'class' : 'jxFileUploadPanel'});
+        
+        
+        if ($defined(this.options.prompt)) {
+            var desc;
+            if (Jx.type(this.options.prompt === 'string')) {
+                desc = new Element('p', {
+                    html: this.options.prompt
+                });
+            } else {
+                desc = this.options.prompt;
+            }
+            desc.inject(this.domObjA);
+        }
+        
+        //add the file field
+        this.fileOpt = this.options.file;
+        this.fileOpt.template = '<div class="jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';
+        
+        this.currentFile = new Jx.Field.File(this.fileOpt);
+        this.currentFile.addEvent('fileSelected', this.moveToQueue.bind(this));
+        this.currentFile.addTo(this.domObjA);
+        
+        //now the 'queue' listing with delete button
+        
+        this.queueDiv = new Element('div', {
+            'class': 'jxUploadQueue'
+        });
+        this.queueDiv.inject(this.domObjA);
+        this.uploadBtn = new Jx.Button({
+            label : this.options.buttonText,
+            onClick: this.upload.bind(this)
+        });
+        var tlb = new Jx.Toolbar({position: 'bottom'}).add(this.uploadBtn);
+        this.uploadBtn.setEnabled(false);
+        this.options.toolbars = [tlb]; 
+        //then pass it on to the Panel constructor 
+        this.options.content = this.domObjA;
+        this.parent(this.options);
+    },
+    /**
+     * Method: moveToQueue
+     * Called by Jx.Field.File's fileSelected event. Moves the selected file into the 
+     * upload queue.
+     */
+    moveToQueue: function (file) {
+        var cf = this.currentFile;
+        var name = cf.getFileName();
+        
+        this.fileQueue.push(this.currentFile);
+        
+        this.currentFile = new Jx.Field.File(this.fileOpt);
+        this.currentFile.addEvent('fileSelected', this.moveToQueue.bind(this));
+        $(this.currentFile).replaces($(cf));
+        
+        //add to queue div
+                
+        cf.queuedDiv = new Element('div', {id : name});
+        var s = new Element('span', {
+            html : name,
+            'class' : 'jxUploadFileName'
+        });
+        var del = new Element('span', {
+            'class' : 'jxUploadFileDelete',
+            title : 'Remove from queue'
+        });
+        
+        del.addEvent('click', this.removeFromQueue.bind(this, cf));
+        cf.queuedDiv.adopt(s, del);
+        cf.queuedDiv.inject(this.queueDiv);
+        if (!this.uploadBtn.isEnabled()) {
+            this.uploadBtn.setEnabled(true);
+        }
+        
+    },
+    /**
+     * Method: upload
+     * Called when the user clicks the upload button. Runs the upload process.
+     */
+    upload: function () {
+        var file = this.fileQueue.shift();
+        file.addEvent('uploadComplete', this.fileUploadComplete.bind(this));
+        file.addEvent('uploadError', this.fileUploadError.bind(this));
+        
+        if (this.options.file.progress) {
+            file.addEvent('uploadProgress', this.fileUploadProgress.bind(this));
+            //progressbar
+            //setup options
+            var options = {
+                containerClass: 'progress-container',
+                messageText: null,
+                messageClass: 'progress-message',
+                progressText: 'uploading ' + file.getFileName(),
+                progressClass: 'progress-bar',
+                bar: {
+                    width: file.queuedDiv.getStyle('width').toInt(),
+                    height: file.queuedDiv.getFirst().getStyle('height').toInt()
+                }
+            };
+            var pb = new Jx.Progressbar(options);
+            file.pb = pb;
+            $(pb).replaces(file.queuedDiv);
+        } else {
+            file.queuedDiv.getLast().removeClass('jxUploadFileDelete').addClass('jxUploadFileProgress');
+        }
+        file.upload();
+    },
+    /**
+     * Method: fileUploadComplete
+     * Called when a single file is uploaded completely (called by 
+     * Jx.Field.File's uploadComplete event). 
+     * 
+     * Parameters:
+     * data - the data returned from the event
+     * file - the file we're tracking
+     */
+    fileUploadComplete: function (data, file) {
+        if ($defined(data.success) && data.success ){
+            this.removeUploadedFile(file);
+        } else {
+            this.fileUploadError(data, file);
+        }
+    },
+    /**
+     * Method: fileUploadError
+     * Called when there is an error uploading a file.
+     * 
+     * Parameters:
+     * data - the data passed back from the server, if any.
+     * file - the file we're tracking
+     */
+    fileUploadError: function (data, file) {
+        var icon = file.queuedDiv.getLast(); 
+        icon.erase('title');
+        if (icon.hasClass('jxUploadFileProgress')) {
+            icon.removeClass('jxUploadFileProgress').addClass('jxUploadFileError');
+        } else {
+            //queued div is hidden, show it
+            file.queuedDiv.replaces(file.pb);
+            icon.removeClass('jxUploadFileDelete').addClass('jxUploadFileError');
+        }
+        if ($defined(data.error.message)) {
+            var tt = new Jx.Tooltip(icon, data.error.message, {
+                cssClass : 'jxUploadFileErrorTip'
+            });
+        }
+    },
+    /**
+     * Method: removeUploadedFile
+     * Removes the passed file from the upload queue upon it's completion.
+     * 
+     * Parameters:
+     * file - the file we're tracking
+     */
+    removeUploadedFile: function (file) {
+        
+        if (this.options.removeOnComplete) {
+            if ($defined(file.pb)) {
+                file.pb.destroy();
+            }
+            file.queuedDiv.dispose();
+            var name = file.getFileName();
+            this.fileQueue.erase(name);
+        } else {
+            if ($defined(file.pb)) {
+                file.queuedDiv.replaces(file.pb);
+                file.pb.destroy();
+            }
+            var l = file.queuedDiv.getLast();
+            if (l.hasClass('jxUploadFileDelete')) {
+                l.removeClass('jxUploadFileDelete').addClass('jxUploadFileComplete');
+            } else if (l.hasClass('jxUploadFileProgress')) {
+                l.removeClass('jxUploadFileProgress').addClass('jxUploadFileComplete');
+            }
+        }
+        
+        this.fireEvent('fileComplete', file);
+        if (this.fileQueue.length > 0) {
+            this.upload();
+        } else {
+            this.fireEvent('complete');
+        }
+    },
+    /**
+     * Method: fileUploadProgress
+     * Function to pass progress information to the progressbar instance 
+     * in the file. Only used if we're tracking progress.
+     */
+    fileUploadProgress: function (data, file) {
+        file.pb.update(data.total, data.current);
+    },
+    /**
+     * Method: removeFromQueue
+     * Called when the delete icon is clicked for an individual file. It 
+     * removes the file from the queue, disposes of it, and does NOT upload 
+     * the file to the server.
+     * 
+     * Pparameters:
+     * file - the file we're getting rid of.
+     */
+    removeFromQueue: function (file) {
+        var name = file.getFileName();
+        //TODO: Should prompt the user to be sure - use Jx.Dialog.Confirm?
+        $(name).destroy();
+        this.fileQueue = this.fileQueue.erase(file);
+        if (this.fileQueue.length === 0) {
+            this.uploadBtn.setEnabled(false);
+        }
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.ListItem
+ *
+ * Extends: <Jx.Widget>
+ *
+ * Events:
+ *
+ * License: 
+ * Copyright (c) 2009, DM Solutions Group.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.ListItem = new Class({
+
+    Extends: Jx.Widget,
+    
+    options: {
+        template: '<li class="jxListItemContainer jxListItem"></li>'
+    },
+    
+    classes: ['jxListItemContainer','jxListItem'],
+    
+    /**
+     * APIMethod: render
+     */
+    render: function () {
+        this.parent();
+        this.elements = this.processTemplate(this.options.template, this.classes);
+        this.domObj = this.elements.get('jxListItemContainer');
+        this.domContent = this.elements.get('jxListItem')
+        this.loadContent(this.domContent);
+    }
+});// $Id: $
+/**
+ * Class: Jx.ListView
+ *
+ * Extends: <Jx.Widget>
+ *
+ * Events:
+ *
+ * License: 
+ * Copyright (c) 2009, DM Solutions Group.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.ListView = new Class({
+
+    Extends: Jx.Widget,
+    
+    options: {
+        template: '<ul class="jxListView"></ul>',
+        /**
+         * Option: listOptions
+         * control the behaviour of the list, see <Jx.List>
+         */
+        listOptions: {
+            hover: true,
+            press: true,
+            select: true
+        }
+    },
+    
+    classes: ['jxListView'],
+    
+    /**
+     * APIMethod: render
+     */
+    render: function () {
+        this.parent();
+        this.elements = this.processTemplate(this.options.template, this.classes);
+        this.domObj = this.elements.get('jxListView');
+        
+        if (this.options.selection) {
+            this.selection = this.options.selection;
+        } else if (this.options.select) {
+            this.selection = new Jx.Selection(this.options);
+            this.ownsSelection = true;
+        }
+        
+        this.list = new Jx.List(this.domObj, this.listOptions, this.selection);
+        
+    },
+    
+    cleanup: function() {
+        if (this.ownsSelection) {
+            this.selection.destroy();
+        }
+        this.list.destroy();
+    },
+    
+    add: function(item, where) {
+        this.list.add(item, where);
+        return this;
+    },
+    
+    remove: function(item) {
+        this.list.remove(item);
+        return this;
+    },
+    
+    replace: function(item, withItem) {
+        this.list.replace(item, withItem);
+        return this;
+    }
+});// $Id: $
+/**
+ * Class: Jx.Column
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * The class used for defining columns for grids.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Column = new Class({
+
+    Extends: Jx.Object,
+
+    options: {
+        /**
+         * Option: header
+         * The text to be used as a header for this column
+         */
+        header: null,
+        /**
+         * 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
+         */
+        width: null,
+        /**
+         * Option: isEditable
+         * allows/disallows editing of the column contents
+         */
+        isEditable: false,
+        /**
+         * Option: isSortable
+         * allows/disallows sorting based on this column
+         */
+        isSortable: false,
+        /**
+         * Option: isResizable
+         * allows/disallows resizing this column dynamically
+         */
+        isResizable: false,
+        /**
+         * Option: isHidden
+         * determines if this column can be shown or not
+         */
+        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
+         */
+        dataType: 'alphanumeric',
+        /**
+         * 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.
+         */
+        templates: {
+            header: {
+                tag: 'span',
+                cssClass: null
+            },
+            cell: {
+                tag: 'span',
+                cssClass: null
+            }
+        }
+    
+    },
+    /**
+     * Property: model
+     * holds a reference to the model (an instance of <Jx.Store> or subclass)
+     */
+    model: null,
+    
+    parameters: ['options','grid'],
+    
+    /**
+     * Constructor: Jx.Column
+     * initializes the column object
+     */
+    init : function () {
+        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);
+            }
+        }
+    },
+    /**
+     * 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;
+    },
+    
+    setWidth: function(newWidth) {
+        if (this.rule && parseInt(newWidth) >= 0) {
+            this.width = parseInt(newWidth);
+            this.rule.style.width = parseInt(newWidth) + "px";
+        }
+    },
+    /**
+     * APIMethod: getWidth
+     * 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;
+        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;
+                    }
+                }
+    
+                //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;
+                    }
+                }
+                if (!rowHeader) {
+                    this.width = maxWidth;
+                }
+                model.moveTo(oldPos);
+            }
+        }
+        if (!rowHeader) {
+            return this.width;
+        } else {
+            return maxWidth;
+        }
+    },
+    /**
+     * Method: measure
+     * This method does the dirty work of actually measuring a cell
+     * 
+     * Parameters:
+     * text - the text to measure
+     * klass - a string indicating and extra classes to add so that 
+     *          css classes can be taken into account.
+     */
+    measure : function (text, klass, rowHeader) {
+        if ($defined(this.options.formatter)
+                && text !== this.options.header) {
+            text = this.options.formatter.format(text);
+        }
+        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());
+        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 (!rowHeader) {
+                return this.getFirst().getContentBoxSize();
+            } else {
+                return this.getMarginBoxSize();
+            }
+        });
+        d.destroy();
+        return s;
+    },
+    /**
+     * APIMethod: isEditable
+     * Returns whether this column can be edited
+     */
+    isEditable : function () {
+        return this.options.isEditable;
+    },
+    /**
+     * APIMethod: isSortable
+     * Returns whether this column can be sorted
+     */
+    isSortable : function () {
+        return this.options.isSortable;
+    },
+    /**
+     * APIMethod: isResizable
+     * Returns whether this column can be resized
+     */
+    isResizable : function () {
+        return this.options.isResizable;
+    },
+    /**
+     * APIMethod: isHidden
+     * Returns whether this column is hidden
+     */
+    isHidden : function () {
+        return this.options.isHidden;
+    },
+    /**
+     * 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
+     */
+    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;
+    }
+
+});// $Id: $
+/**
+ * Class: Jx.Columns
+ *
+ * Extends: <Jx.Object>
+ *
+ * This class is the container for all columns needed for a grid. It
+ * consolidates many functions that didn't make sense to put directly
+ * in the column class. Think of it as a model for columns.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Columns = new Class({
+
+    Extends : Jx.Object,
+
+    options : {
+        /**
+         * Option: headerRowHeight
+         * the default height of the header row. Set to null or 'auto' to
+         * have this class attempt to figure out a suitable height.
+         */
+        headerRowHeight : 20,
+        /**
+         * Option: useHeaders
+         * Determines if the column headers should be displayed or not
+         */
+        useHeaders : false,
+        /**
+         * Option: columns
+         * an array holding all of the column instances or objects containing
+         * configuration info for the column
+         */
+        columns : []
+    },
+    /**
+     * Property: columns
+     * an array holding the actual instantiated column objects
+     */
+    columns : [],
+    
+    parameters: ['options','grid'],
+    
+    /**
+     * APIMethod: init
+     * Creates the class.
+     */
+    init : function () {
+        this.parent();
+
+        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
+            this.grid = this.options.grid;
+        }
+
+        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));
+                }
+
+            }, this);
+    },
+    /**
+     * APIMethod: getHeaderHeight
+     * returns the height of the column header row
+     *
+     * Parameters:
+     * recalculate - determines if we should recalculate the height. Currently does nothing.
+     */
+    getHeaderHeight : function (recalculate) {
+        if (!$defined(this.height) || recalculate) {
+            if ($defined(this.options.headerRowHeight)
+                    && this.options.headerRowHeight !== 'auto') {
+                this.height = this.options.headerRowHeight;
+            } else {
+                //figure out a height.
+            }
+        }
+        return this.height;
+    },
+    /**
+     * APIMethod: useHeaders
+     * returns whether the grid is/should display headers or not
+     */
+    useHeaders : function () {
+        return this.options.useHeaders;
+    },
+    /**
+     * APIMethod: getByName
+     * Used to get a column object by the name of the column
+     *
+     * Parameters:
+     * colName - the name of the column
+     */
+    getByName : function (colName) {
+        var ret;
+        this.columns.each(function (col) {
+            if (col.name === colName) {
+                ret = col;
+            }
+        }, this);
+        return ret;
+    },
+    /**
+     * APIMethod: getByField
+     * Used to get a column by the model field it represents
+     *
+     *  Parameters:
+     *  field - the field name to search by
+     */
+    getByField : function (field) {
+        var ret;
+        this.columns.each(function (col) {
+            if (col.options.modelField === field) {
+                ret = col;
+            }
+        }, this);
+        return ret;
+    },
+    /**
+     * APIMethod: getByGridIndex
+     * Used to get a column when all you know is the cell index in the grid
+     *
+     * Parameters:
+     * 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 cell = headers[index];
+        var hClasses = cell.get('class').split(' ').filter(function (cls) {
+            return cls.test('jxColHead-');
+        });
+        var parts = hClasses[0].split('-');
+        return this.getByName(parts[1]);
+    },
+    
+    /**
+     * APIMethod: getHeaders
+     * Returns a row with the headers in it.
+     *
+     * Parameters:
+     * row - the row to add the headers to.
+     */
+    getHeaders : function (row) {
+        var r = this.grid.row.useHeaders();
+        var hf = this.grid.row.getRowHeaderField();
+        this.columns.each(function (col, idx) {
+            if (r && hf === col.options.modelField) {
+                //do nothing
+            } else if (!col.isHidden()) {
+                var th = new Element('td', {
+                    'class' : 'jxGridColHead jxGridCol'+idx
+                });
+                th.adopt(col.getHeaderHTML());
+                // th.setStyle('width', col.getWidth());
+                th.addClass('jxColHead-' + col.options.modelField);
+                //add other styles for different attributes
+                if (col.isEditable()) {
+                    th.addClass('jxColEditable');
+                }
+                if (col.isResizable()) {
+                    th.addClass('jxColResizable');
+                }
+                if (col.isSortable()) {
+                    th.addClass('jxColSortable');
+                }
+                // col.header = th;
+                row.appendChild(th);
+            }
+        }, this);
+        return row;
+    },
+    /**
+     * APIMethod: getColumnCells
+     * Appends the cells from each column for a specific row
+     *
+     * Parameters:
+     * row - the row (tr) to add the cells to.
+     */
+    getColumnCells : function (row) {
+        var r = this.grid.row;
+        var f = r.getRowHeaderField();
+        var h = r.useHeaders();
+        this.columns.each(function (col, idx) {
+            if (h && col.options.modelField !== f && !col.isHidden()) {
+                row.appendChild(this.getColumnCell(col, idx));
+            } else if (!h && !col.isHidden()) {
+                row.appendChild(this.getColumnCell(col, idx));
+            }
+        }, this);
+        return row;
+    },
+    /**
+     * APIMethod: getColumnCell
+     * Returns the cell (td) for a particular column.
+     *
+     * Paremeters:
+     * col - the column to get a cell for.
+     */
+    getColumnCell : function (col, idx) {
+
+        var td = new Element('td', {
+            'class' : 'jxGridCell'
+        });
+        td.adopt(col.getHTML());
+        td.addClass('jxCol-' + col.options.modelField);
+        td.addClass('jxGridCol'+idx);
+        //add other styles for different attributes
+        if (col.isEditable()) {
+            td.addClass('jxColEditable');
+        }
+        if (col.isResizable()) {
+            td.addClass('jxColResizable');
+        }
+        if (col.isSortable()) {
+            td.addClass('jxColSortable');
+        }
+
+        return td;
+    },
+
+    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);
+            col.rule.style.width = col.getWidth() + "px";
+        }, this);
+    },
+
+    /**
+     * APIMethod: getColumnCOunt
+     * returns the number of columns in this model (including hidden).
+     */
+    getColumnCount : function () {
+        return this.columns.length;
+    },
+    /**
+     * APIMethod: getIndexFromGrid
+     * Gets the index of a column from its place in the grid.
+     *
+     * Parameters:
+     * name - the name of the column to get an index for
+     */
+    getIndexFromGrid : function (name) {
+        var headers = this.grid.colTableBody.getFirst().getChildren();
+        var c;
+        var i = -1;
+        headers.each(function (h) {
+            i++;
+            var hClasses = h.get('class').split(' ').filter(function (cls) {
+                return cls.test('jxColHead-'); 
+            });
+            hClasses.each(function (cls) {
+                if (cls.test(name)) {
+                    c = i;
+                }
+            });
+        }, this);
+        return c;
+    }
+
+});
+// $Id: $
+/**
+ * Class: Jx.Row
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * A class defining a grid row.
+ * 
+ * Inspired by code in the original Jx.Grid class
+ * 
+ * License: 
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Row = new Class(
+{
+
+    Extends : Jx.Object,
+
+    options : {
+        /**
+         * Option: useHeaders
+         * defaults to false.  If set to true, then a column of row header
+         * cells are displayed.
+         */
+        useHeaders : false,
+        /**
+         * Option: alternateRowColors
+         * defaults to false.  If set to true, then alternating CSS classes
+         * are used for rows.
+         */
+        alternateRowColors : false,
+        /**
+         * Option: rowClasses
+         * object containing class names to apply to rows
+         */
+        rowClasses : {
+            odd : 'jxGridRowOdd',
+            even : 'jxGridRowEven',
+            all : 'jxGridRowAll'
+        },
+        /**
+         * Option: rowHeight
+         * The height of the row. Make it null or 'auto' to auto-calculate
+         */
+        rowHeight : 20,
+        /**
+         * Option: headerWidth
+         * The width of the row header. Make it null or 'auto' to auto-calculate
+         */
+        headerWidth : 20,
+        /**
+         * Option: headerField
+         * The field 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
+            }
+        }
+        
+    },
+    /**
+     * Property: grid
+     * A reference to the grid that this row model belongs to
+     */
+    grid : null,
+    
+    parameters: ['options','grid'],
+    
+    /**
+     * APIMethod: init
+     * Creates the row model object.
+     */
+    init : function () {
+        this.parent();
+
+        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
+            this.grid = this.options.grid;
+        }
+    },
+    /**
+     * APIMethod: getGridRowElement
+     * Used to create the TR for the main grid row
+     */
+    getGridRowElement : function () {
+
+        var tr = new Element('tr');
+        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;
+        }
+        return tr;
+    },
+    /**
+     * Method: getRowHeaderCell
+     * creates the TH for the row's header
+     */
+    getRowHeaderCell : function () {
+        //get and set text for element
+        var model = this.grid.getModel();
+        var th = new Element('td', {
+            '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);
+            }
+        }
+        
+        return th;
+        
+    },
+    /**
+     * APIMethod: getRowHeaderWidth
+     * determines the row header's width.
+     */
+    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);
+    },
+    
+    /**
+     * APIMethod: getHeight
+     * determines and returns the height of a row
+     */
+    getHeight : function () {
+        //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;
+    },
+    /**
+     * APIMethod: useHeaders
+     * determines and returns whether row headers should be used
+     */
+    useHeaders : function () {
+        return this.options.useHeaders;
+    },
+    /**
+     * APIMethod: getRowHeader
+     * creates and returns the header for the current row
+     */
+    getRowHeader : function () {
+        var rowHeight = this.getHeight();
+        var tr = new Element('tr', {
+            styles : {
+                height : rowHeight
+            }
+        });
+        var th = this.getRowHeaderCell();
+        if (this.grid.model.getPosition() === 0) {
+            var rowWidth = this.getRowHeaderWidth();
+            th.setStyle("width", rowWidth);
+        }
+        tr.appendChild(th);
+        return tr;
+    },
+    /**
+     * APIMethod: getRowHeaderField
+     * returns the name of the model field that is used for the header
+     */
+    getRowHeaderField : function () {
+        return this.options.headerField;
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Grid.Plugin
+ * 
+ * Extend: <Jx.Object>
+ * 
+ * Base class for all plugins. In order for a plugin to be used it must 
+ * extend from this class.
+ * 
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin = new Class({
+    
+    Extends: Jx.Object,
+    
+    Family: "Jx.Plugin",
+    
+    options: {},
+    
+    /**
+     * APIMethod: attach
+     * Empty method that must be overridden by subclasses. It is 
+     * called by the user of the plugin to setup the plugin for use.
+     */
+    attach: function(obj){
+        obj.registerPlugin(this);
+    },
+    
+    /**
+     * APIMethod: detach
+     * Empty method that must be overridden by subclasses. It is 
+     * called by the user of the plugin to remove the plugin.
+     */
+    detach: function(obj){
+        obj.deregisterPlugin(this);
+    }
+    
+});// $Id: $
+/**
+ * Class: Jx.Plugin.Grid
+ * Grid plugin namespace
+ * 
+ * 
+ * License: 
+ * This version Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid = {};// $Id: grid.js 572 2009-10-29 05:53:36Z jonlb at comcast.net $
+/**
  * Class: Jx.Grid
  * 
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.Addable>
+ * A tabular control that has fixed, optional, scrolling headers on the rows and
+ * columns like a spreadsheet.
  *
- * A tabular control that has fixed scrolling headers on the rows and columns
- * like a spreadsheet.
- *
  * Jx.Grid is a tabular control with convenient controls for resizing columns,
- * sorting, and inline editing.  It is created inside another element, typically a
- * div.  If the div is resizable (for instance it fills the page or there is a
+ * sorting, and inline editing.  It is created inside another element, typically
+ * a div.  If the div is resizable (for instance it fills the page or there is a
  * user control allowing it to be resized), you must call the resize() method
  * of the grid to let it know that its container has been resized.
  *
  * When creating a new Jx.Grid, you can specify a number of options for the grid
- * that control its appearance and functionality.
+ * that control its appearance and functionality. You can also specify plugins
+ * to load for additional functionality. Currently Jx provides the following
+ * plugins 
+ * 
+ * Prelighter - prelights rows, columns, and cells 
+ * Selector - selects rows, columns, and cells
+ * Sorter - sorts rows by specific column
  *
  * Jx.Grid renders data that comes from an external source.  This external 
- * source, called the model, must implement the following interface.
+ * source, called the model, must be a Jx.Store or extended from it (such as 
+ * Jx.Store.Remote).
  *
  *
- * Example:
- * (code)
- * (end)
  *
  * License: 
  * Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Jon Bomgardner.
  * 
  * This file is licensed under an MIT style license
  */
 Jx.Grid = new Class({
-    Family: 'Jx.Grid',
-    Implements: [Options, Events, Jx.Addable],
-    domObj : null,
-    model : null,
-    options: {
-         /* Option: parent
-          * the HTML element to create the grid inside. The grid will resize
-          * to fill the domObj.
-          */
-         parent: null,
-         /* Option: alternateRowColors
-          * defaults to false.  If set to true, then alternating CSS classes
-          * are used for rows.
-          */
-        alternateRowColors: false,
-         /* Option: rowHeaders
-          * defaults to false.  If set to true, then a column of row header
-          * cells are displayed.
-          */
-        rowHeaders: false,
-         /* Option: columnHeaders
-          * defaults to false.  If set to true, then a column of row header
-          * cells are displayed.
-          */
-        columnHeaders: false,
-         /* Option: rowSelection
-          * defaults to false.  If set to true, allow the user to select rows.
-          */
-        rowSelection: false,
-         /* Option: columnSelection
-          * defaults to false.  If set to true, allow the user to select
-          * columns.
-          */
-        columnSelection: false,
-         /* Option: cellPrelight
-          * defaults to false.  If set to true, the cell under the mouse is
-          * highlighted as the mouse moves.
-          */
-        cellPrelight: false,
-         /* Option: rowPrelight
-          * defaults to false.  If set to true, the row under the mouse is
-          * highlighted as the mouse moves.
-          */
-        rowPrelight: false,
-         /* Option: columnPrelight
-          * defaults to false.  If set to true, the column under the mouse is
-          * highlighted as the mouse moves.
-          */
-        columnPrelight: false,
-        /* Option: rowHeaderPrelight
-         * defaults to false.  If set to true, the row header of the row under
-         * the mouse is highlighted as the mouse moves.
+
+    Family : 'Jx.Grid',
+    Extends : Jx.Widget,
+
+    options : {
+        /**
+         * Option: parent
+         * the HTML element to create the grid inside. The grid will resize
+         * to fill the domObj.
          */
-        rowHeaderPrelight: false,
-        /* Option: columnHeaderPrelight
-         * defaults to false.  If set to true, the column header of the column
-         * under the mouse is highlighted as the mouse moves.
+        parent : null,
+
+        /**
+         * Options: columns
+         * an object consisting of a columns array that defines the individuals
+         * columns as well as containing any options for Jx.Grid.Columns or 
+         * a Jx.Grid.Columns object itself.
          */
-        columnHeaderPrelight: false,
-         /* Option: cellSelection
-          * defaults to false.  If set to true, allow the user to select
-          * cells.
-          */
-        cellSelection: false
+        columns : {
+            columns : []
+        },
+
+        /**
+         * Option: row
+         * Either a Jx.Grid.Row object or a json object defining options for
+         * the class
+         */
+        row : null,
+
+        /**
+         * Option: plugins
+         * an array containing Jx.Grid.Plugin subclasses or an object 
+         * that indicates the name of a predefined plugin and its options.
+         */
+        plugins : [],
+
+        /**
+         * Option: model
+         * An instance of Jx.Store or one of its descendants
+         */
+        model : null,
+        
+        deferRender: true
+
     },
     /**
+     * Property: model
+     * holds a reference to the <Jx.Store> that is the model for this
+     * grid
+     */
+    model : null,
+    /**
+     * Property: columns
+     * holds a reference to the columns object
+     */
+    columns : null,
+    /**
+     * Property: row
+     * Holds a reference to the row object
+     */
+    row : null,
+    /**
+     * Property: currentCell
+     * holds an object indicating the current cell that the mouse is over
+     */
+    currentCell : null,
+    
+    /**
+     * Property: styleSheet
+     * the name of the dynamic style sheet to use for manipulating styles
+     */
+    styleSheet: 'JxGridStyles',
+    /**
+     * Property: pluginNamespace
+     * the required variable for plugins
+     */
+    pluginNamespace: 'Grid',
+
+    /**
      * Constructor: Jx.Grid
-     * construct a new instance of Jx.Grid within the domObj
-     *
-     * Parameters:
-     * options - <Jx.Grid.Options>
      */
-    initialize : function( options ) {
-        this.setOptions(options);
+    init : function () {
+        this.uniqueId = this.generateId('jxGrid_');
 
-        this.domObj = new Element('div');
-        new Jx.Layout(this.domObj, {
-            onSizeChange: this.resize.bind(this)
+        if ($defined(this.options.model)
+                && this.options.model instanceof Jx.Store) {
+            this.model = this.options.model;
+            this.model.addEvent('columnChanged', this.modelChanged
+                    .bind(this));
+            this.model.addEvent('sortFinished', this.render.bind(this));
+        }
+
+        if ($defined(this.options.columns)) {
+            if (this.options.columns instanceof Jx.Columns) {
+                this.columns = this.options.columns;
+            } else if (Jx.type(this.options.columns) === 'object') {
+                var opts = this.options.columns;
+                opts.grid = this;
+                this.columns = new Jx.Columns(opts);
+            }
+        }
+
+        //check for row
+        if ($defined(this.options.row)) {
+            if (this.options.row instanceof Jx.Row) {
+                this.row = this.options.row;
+            } else if (Jx.type(this.options.row) === "object") {
+                var opts = this.options.row;
+                opts.grid = this;
+                this.row = new Jx.Row(opts);
+            }
+        } else {
+            this.row = new Jx.Row({grid: this});
+        }
+
+        //initialize the grid
+        this.domObj = new Element('div', {'class':this.uniqueId});
+        var l = new Jx.Layout(this.domObj, {
+            onSizeChange : this.resize.bind(this)
         });
-        
+
         if (this.options.parent) {
             this.addTo(this.options.parent);
-        }        
-        
-        this.rowColObj = new Element('div', {'class':'jxGridContainer'});
-        
-        this.colObj = new Element('div', {'class':'jxGridContainer'});
-        this.colTable = new Element('table', {'class':'jxGridTable'});
-        this.colTableHead = new Element('thead');
-        this.colTable.appendChild(this.colTableHead);
+        }
+
+        //top left corner
+        this.rowColObj = new Element('div', {
+            'class' : 'jxGridContainer'
+        });
+
+        //holds the column headers
+        this.colObj = new Element('div', {
+            'class' : 'jxGridContainer'
+        });
+        this.colTable = new Element('table', {
+            'class' : 'jxGridTable jxGridHeader'
+        });
         this.colTableBody = new Element('tbody');
         this.colTable.appendChild(this.colTableBody);
         this.colObj.appendChild(this.colTable);
-        
-        this.rowObj = new Element('div', {'class':'jxGridContainer'});
-        this.rowTable = new Element('table', {'class':'jxGridTable'});
+
+        //hold the row headers
+        this.rowObj = new Element('div', {
+            'class' : 'jxGridContainer jxGridHeader'
+        });
+        this.rowTable = new Element('table', {
+            'class' : 'jxGridTable'
+        });
         this.rowTableHead = new Element('thead');
         this.rowTable.appendChild(this.rowTableHead);
         this.rowObj.appendChild(this.rowTable);
-        
-        this.gridObj = new Element('div', {'class':'jxGridContainer',styles:{overflow:'scroll'}});
-        this.gridTable = new Element('table', {'class':'jxGridTable'});
+
+        //The actual body of the grid
+        this.gridObj = new Element('div', {
+            'class' : 'jxGridContainer',
+            styles : {
+                overflow : 'auto'
+            }
+        });
+        this.gridTable = new Element('table', {
+            'class' : 'jxGridTable'
+        });
         this.gridTableBody = new Element('tbody');
         this.gridTable.appendChild(this.gridTableBody);
         this.gridObj.appendChild(this.gridTable);
-        
+
         this.domObj.appendChild(this.rowColObj);
         this.domObj.appendChild(this.rowObj);
         this.domObj.appendChild(this.colObj);
         this.domObj.appendChild(this.gridObj);
-                        
+
         this.gridObj.addEvent('scroll', this.onScroll.bind(this));
-        this.gridObj.addEvent('click', this.onClickGrid.bindWithEvent(this));
-        this.rowObj.addEvent('click', this.onClickRowHeader.bindWithEvent(this));
-        this.colObj.addEvent('click', this.onClickColumnHeader.bindWithEvent(this));
-        this.gridObj.addEvent('mousemove', this.onMouseMoveGrid.bindWithEvent(this));
-        this.rowObj.addEvent('mousemove', this.onMouseMoveRowHeader.bindWithEvent(this));
-        this.colObj.addEvent('mousemove', this.onMouseMoveColumnHeader.bindWithEvent(this));
+        this.gridObj.addEvent('click', this.onGridClick
+                .bindWithEvent(this));
+        this.rowObj.addEvent('click', this.onGridClick
+                .bindWithEvent(this));
+        this.colObj.addEvent('click', this.onGridClick
+                .bindWithEvent(this));
+        this.gridObj.addEvent('mousemove', this.onMouseMove
+                .bindWithEvent(this));
+        this.rowObj.addEvent('mousemove', this.onMouseMove
+                .bindWithEvent(this));
+        this.colObj.addEvent('mousemove', this.onMouseMove
+                .bindWithEvent(this));
+
+        this.parent();
+        
+        this.domObj.store('grid', this);
     },
-    
+
     /**
      * Method: onScroll
      * handle the grid scrolling by updating the position of the headers
      */
-    onScroll: function() {
+    onScroll : function () {
         this.colObj.scrollLeft = this.gridObj.scrollLeft;
-        this.rowObj.scrollTop = this.gridObj.scrollTop;        
+        this.rowObj.scrollTop = this.gridObj.scrollTop;
     },
+
+    /**
+     * Method: onMouseMove
+     * Handle the mouse moving over the grid. This determines
+     * what column and row it's over and fires the gridMove event 
+     * with that information for plugins to respond to.
+     *
+     * Parameters:
+     * e - {Event} the browser event object
+     */
+    onMouseMove : function (e) {
+        var rc = this.getRowColumnFromEvent(e);
+        if (!$defined(this.currentCell)
+                || (this.currentCell.row !== rc.row || this.currentCell.column !== rc.column)) {
+            this.currentCell = rc;
+            this.fireEvent('gridMove', rc);
+        }
+
+    },
+    /**
+     * Method: onGridClick
+     * handle the user clicking on the grid. Fires gridClick
+     * event for plugins to respond to.
+     *
+     * Parameters:
+     * e - {Event} the browser event object
+     */
+    onGridClick : function (e) {
+        var rc = this.getRowColumnFromEvent(e);
+        this.fireEvent('gridClick', rc);
+    },
+
+    /**
+     * Method: getRowColumnFromEvent
+     * retrieve the row and column indexes from an event click.
+     * This function is used by the grid, row header and column
+     * header to safely get these numbers.
+     *
+     * If the event isn't valid (i.e. it wasn't on a TD or TH) then
+     * the returned values will be -1, -1
+     *
+     * Parameters:
+     * e - {Event} the browser event object
+     *
+     * @return Object an object with two properties, row and column,
+     *         that contain the row and column that was clicked
+     */
+    getRowColumnFromEvent : function (e) {
+        var td = e.target;
+        if (td.tagName === 'SPAN') {
+            td = document.id(td).getParent();
+        }
+        if (td.tagName !== 'TD' && td.tagName !== 'TH') {
+            return {
+                row : -1,
+                column : -1
+            };
+        }
+
+        var colheader = false;
+        var rowheader = false;
+        //check if this is a header (row or column)
+        if (td.descendantOf(this.colTable)) {
+            colheader = true;
+        }
     
+        if (td.descendantOf(this.rowTable)) {
+            rowheader = true;
+        }
+    
+        var tr = td.parentNode;
+        var col = td.cellIndex;
+        var row = tr.rowIndex;
+        /*
+         * if this is not a header cell, then increment the row and col. We do this
+         * based on whether the header is shown. This way the row/col remains consistent
+         * to the grid but also takes into account the headers. It also allows
+         * us to refrain from having to fire a separate event for headers.
+         * 
+         *  Plugins/event listeners should always take into account whether headers
+         *  are displayed or not.
+         */
+        if (this.row.useHeaders() && !rowheader) {
+            col++;
+        }
+        if (this.columns.useHeaders() && !colheader) {
+            row++;
+        }
+    
+        if (Browser.Engine.webkit) {
+            /* bug in safari (webkit) returns 0 for cellIndex - only choice seems
+             * to be to loop through the row
+             */
+            for (var i = 0; i < tr.childNodes.length; i++) {
+                if (tr.childNodes[i] === td) {
+                    col = i;
+                    break;
+                }
+            }
+        }
+        return {
+            row : row,
+            column : col
+        };
+    },
+    
     /**
-     * Method: resize
+     * APIMethod: resize
      * resize the grid to fit inside its container.  This involves knowing something
      * about the model it is displaying (the height of the column header and the
      * width of the row header) so nothing happens if no model is set
      */
-    resize: function() {
+    resize : function () {
         if (!this.model) {
             return;
         }
-        
-        /* TODO: Jx.Grid.resize
-         * if not showing column or row, should we handle the resize differently
-         */
-        var colHeight = this.options.columnHeaders ? this.model.getColumnHeaderHeight() : 1;
-        var rowWidth = this.options.rowHeaders ? this.model.getRowHeaderWidth() : 1;
-        
-        var size = Element.getContentBoxSize(this.domObj);
-        
+
+        var colHeight = this.columns.useHeaders() ? this.columns
+                .getHeaderHeight() : 1;
+        var rowWidth = this.row.useHeaders() ? this.row
+                .getRowHeaderWidth() : 1;
+
+        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) {
+            if (col.options.modelField !== this.row.getRowHeaderField()
+                    && !col.isHidden()) {
+                totalCols += col.getWidth();
+            }
+        }, this);
+
         /* -1 because of the right/bottom borders */
         this.rowColObj.setStyles({
-            width: rowWidth-1, 
-            height: colHeight-1
+            width : rowWidth - 1,
+            height : colHeight - 1
         });
         this.rowObj.setStyles({
-            top:colHeight,
-            left:0,
-            width:rowWidth-1,
-            height:size.height-colHeight-1
+            top : colHeight,
+            left : 0,
+            width : rowWidth - 1,
+            height : size.height - colHeight - 1
         });
 
         this.colObj.setStyles({
-            top: 0,
-            left: rowWidth,
-            width: size.width - rowWidth - 1,
-            height: colHeight - 1
+            top : 0,
+            left : rowWidth,
+            width : size.width - rowWidth - 1,
+            height : colHeight - 1
         });
 
         this.gridObj.setStyles({
-            top: colHeight,
-            left: rowWidth,
-            width: size.width - rowWidth - 1,
-            height: size.height - colHeight - 1 
+            top : colHeight,
+            left : rowWidth,
+            width : size.width - rowWidth - 1,
+            height : size.height - colHeight - 1
         });
+
     },
-    
+
     /**
-     * Method: setModel
+     * APIMethod: setModel
      * set the model for the grid to display.  If a model is attached to the grid
-     * it is removed and the new model is displayed.
+     * it is removed and the new model is displayed. However, It needs to have 
+     * the same columns
      * 
      * Parameters:
      * model - {Object} the model to use for this grid
      */
-    setModel: function(model) {
+    setModel : function (model) {
         this.model = model;
         if (this.model) {
-            if (this.domObj.resize) {
-                this.domObj.resize();
-            }
-            this.createGrid();
-            this.resize();
+            this.render();
+            this.domObj.resize();
         } else {
             this.destroyGrid();
         }
     },
-    
+
     /**
-     * Method: destroyGrid
+     * APIMethod: getModel
+     * gets the model set for this grid.
+     */
+    getModel : function () {
+        return this.model;
+    },
+
+    /**
+     * APIMethod: destroyGrid
      * destroy the contents of the grid safely
      */
-    destroyGrid: function() {
-        var n = this.colTableHead.cloneNode(false);
-        this.colTable.replaceChild(n, this.colTableHead);
-        this.colTableHead = n;
-        
-        n = this.colTableBody.cloneNode(false);
+    destroyGrid : function () {
+
+        var n = this.colTableBody.cloneNode(false);
         this.colTable.replaceChild(n, this.colTableBody);
         this.colTableBody = n;
-        
+
         n = this.rowTableHead.cloneNode(false);
         this.rowTable.replaceChild(n, this.rowTableHead);
         this.rowTableHead = n;
-        
+
         n = this.gridTableBody.cloneNode(false);
         this.gridTable.replaceChild(n, this.gridTableBody);
         this.gridTableBody = n;
-        
+
     },
-    
+
     /**
-     * Method: createGrid
-     * create the grid for the current model
+     * APIMethod: render
+     * Create the grid for the current model
      */
-    createGrid: function() {
+    render : function () {
         this.destroyGrid();
+
+        this.fireEvent('beginCreateGrid', this);
+
         if (this.model) {
             var model = this.model;
-            var nColumns = model.getColumnCount();
-            var nRows = model.getRowCount();
+            var nColumns = this.columns.getColumnCount();
+            var nRows = model.count();
+            var th;
             
             /* create header if necessary */
-            if (this.options.columnHeaders) {
-                var colHeight = model.getColumnHeaderHeight();
-                var trHead = new Element('tr');
-                this.colTableHead.appendChild(trHead);
-                var trBody = new Element('tr');
+            if (this.columns.useHeaders()) {
+                this.colTableBody.setStyle('visibility', 'visible');
+                var colHeight = this.columns.getHeaderHeight();
+                var trBody = new Element('tr', {
+                    styles : {
+                        height : colHeight
+                    }
+                });
                 this.colTableBody.appendChild(trBody);
-                
-                var th = new Element('th', {styles:{width:0,height:0}});
-                trHead.appendChild(th);
-                th = th.cloneNode(true);
-                th.setStyle('height',colHeight);
-                trBody.appendChild(th);
-                for (var i=0; i<nColumns; i++) {
-                    var colWidth = model.getColumnWidth(i);
-                    th = new Element('th', {'class':'jxGridColHeadHide',styles:{width:colWidth}});
-                    var p = new Element('p', {styles:{height:0,width:colWidth}});
-                    th.appendChild(p);
-                    trHead.appendChild(th);
-                    th = new Element('th', {
-                        'class':'jxGridColHead', 
-                        html:model.getColumnHeaderHTML(i)
-                    });
-                    trBody.appendChild(th);
-                }
+
+                this.columns.getHeaders(trBody);
+
                 /* one extra column at the end for filler */
-                var th = new Element('th',{styles:{width:1000,height:0}});
-                trHead.appendChild(th);
-                th = th.cloneNode(true);
-                th.setStyle('height',colHeight - 1);
-                th.className = 'jxGridColHead';
-                trBody.appendChild(th);
-                
+                th = new Element('td', {
+                    'class':'jxGridColHead'
+                }).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.options.rowHeaders) {
-                var rowWidth = model.getRowHeaderWidth();
-                var tr = new Element('tr');
-                var td = new Element('td', {styles:{width:0,height:0}});
-                tr.appendChild(td);
-                var th = new Element('th', {styles:{width:rowWidth,height:0}});
-                tr.appendChild(th);
-                this.rowTableHead.appendChild(tr);
-                for (var i=0; i<nRows; i++) {
-                    var rowHeight = model.getRowHeight(i);
-                    var tr = new Element('tr');
-                    var td = new Element('td', {'class':'jxGridRowHeadHide', styles:{width:0,height:rowHeight}});
-                    var p = new Element('p', {styles:{width:0,height:rowHeight}});
-                    td.appendChild(p);
-                    tr.appendChild(td);
-                    var th = new Element('th', {'class':'jxGridRowHead', html:model.getRowHeaderHTML(i)});
-                    tr.appendChild(th);
+            if (this.row.useHeaders()) {
+                this.rowTableHead.setStyle('visibility', 'visible');
+                
+                var tr;
+                //loop through all rows and add header
+                this.model.first();
+                while (this.model.valid()) {
+                    tr = this.row.getRowHeader();
                     this.rowTableHead.appendChild(tr);
+                    if (this.model.hasNext()) {
+                        this.model.next();
+                    } else {
+                        break;
+                    }
                 }
                 /* one extra row at the end for filler */
-                var tr = new Element('tr');
-                var td = new Element('td',{
-                    styles:{
-                        width:0,
-                        height:1000
+                tr = new Element('tr').inject(this.rowTableHead);
+                th = new Element('td', {
+                    'class' : 'jxGridRowHead',
+                    styles : {
+                        width : this.row.getRowHeaderWidth(),
+                        height : 1000
                     }
-                });
-                tr.appendChild(td);
-                var th = new Element('th',{
-                    'class':'jxGridRowHead',
-                    styles:{
-                        width:rowWidth,
-                        height:1000
-                    }
-                });
-                tr.appendChild(th);
-                this.rowTableHead.appendChild(tr);
+                }).inject(tr);
+            } else {
+                //hide row headers
+                this.rowTableHead.setStyle('visibility', 'hidden');
             }
             
-            var colHeight = model.getColumnHeaderHeight();
-            var trBody = new Element('tr');
-            this.gridTableBody.appendChild(trBody);
+            colHeight = this.columns.getHeaderHeight();
             
-            var td = new Element('td', {styles:{width:0,height:0}});
-            trBody.appendChild(td);
-            for (var i=0; i<nColumns; i++) {
-                var colWidth = model.getColumnWidth(i);
-                td = new Element('td', {'class':'jxGridColHeadHide', styles:{width:colWidth}});
-                var p = new Element('p', {styles:{width:colWidth,height:0}});
-                td.appendChild(p);
-                trBody.appendChild(td);
-            }
-            
-            for (var j=0; j<nRows; j++) {
-                var rowHeight = model.getRowHeight(j);
-                var actualRowHeight = rowHeight;
-                var tr = new Element('tr');
+            //This section actually adds the rows
+            this.model.first();
+            while (this.model.valid()) {
+                tr = this.row.getGridRowElement();
                 this.gridTableBody.appendChild(tr);
-                
-                var td = new Element('td', {
-                    'class':'jxGridRowHeadHide',
-                    styles: {
-                        width: 0,
-                        height:rowHeight
-                    }
-                });
-                var p = new Element('p',{styles:{height:rowHeight}});
-                td.appendChild(p);
-                tr.appendChild(td);
-                for (var i=0; i<nColumns; i++) {
-                    var colWidth = model.getColumnWidth(i);
-                    td = new Element('td', {'class':'jxGridCell'});
-                    td.innerHTML = model.getValueAt(j,i);
-                    tr.appendChild(td);
-                    var tdSize = td.getSize();
-                    if (tdSize.height > actualRowHeight) {
-                        actualRowHeight = tdSize.height;
-                    }
-                }
-                /* some notes about row sizing
-                 * In Safari, the height of a TR is always returned as 0
-                 * In Safari, the height of any given TD is the height it would
-                 * render at, not the actual height of the row
-                 * In IE, the height is returned 1px bigger than any other browser
-                 * Firefox just works
-                 *
-                 * So, for Safari, we have to measure every TD and take the highest one
-                 * and if its IE, we subtract 1 from the overall height, making all
-                 * browsers identical
-                 *
-                 * Using document.all is not a good hack for this
-                 */
-                if (document.all) {
-                    actualRowHeight -= 1;
-                }
-                if (this.options.rowHeaders) {
-                    this.setRowHeaderHeight(j, actualRowHeight);                    
-                }
-                /* if we apply the class before adding content, it
-                 * causes a rendering error in IE (off by 1) that is 'fixed'
-                 * when another class is applied to the row, causing dynamic
-                 * shifting of the row heights
-                 */
-                if (this.options.alternateRowColors) {
-                    tr.className = (j%2) ? 'jxGridRowOdd' : 'jxGridRowEven';
+        
+                //Actually add the columns 
+                this.columns.getColumnCells(tr);
+        
+                if (this.model.hasNext()) {
+                    this.model.next();
                 } else {
-                    tr.className = 'jxGridRowAll';
+                    break;
                 }
+        
             }
             
+            Jx.Styles.enableStyleSheet(this.styleSheet);
+            this.columns.createRules(this.styleSheet, "."+this.uniqueId);
         }
+        this.domObj.resize();
+        this.fireEvent('doneCreateGrid', this);
     },
     
     /**
-     * Method: setRowHeaderHeight
-     * set the height of a row.  This is used internally to adjust the height of
-     * the row header when cell contents wrap.  A limitation of the table structure
-     * is that overflow: hidden on a td will work horizontally but not vertically
+     * Method: modelChanged
+     * Event listener that is fired when the model changes in some way
+     */
+    modelChanged : function (row, col) {
+        //grab new TD
+        var column = this.columns.getIndexFromGrid(col.name);
+        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));
+        newTD.replaces(td);
+
+        this.model.moveTo(currentRow);    
+    }
+
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Selector
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Grid plugin to select rows, columns, and/or cells.
+ * 
+ * Original selection code from Jx.Grid's original class
+ * 
+ * License: 
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Selector = new Class({
+
+    Extends : Jx.Plugin,
+
+    options : {
+        /**
+         * Option: cell
+         * determines if cells are selectable
+         */
+        cell : false,
+        /**
+         * Option: row
+         * determines if rows are selectable
+         */
+        row : false,
+        /**
+         * Option: column
+         * determines if columns are selectable
+         */
+        column : false
+    },
+    /**
+     * 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.select = this.select.bind(this);
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it 
+     * will be monitoring
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+            return;
+        }
+        this.grid = grid;
+        this.grid.addEvent('gridClick', this.bound.select);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridClick', this.bound.select);
+        }
+        this.grid = null;
+    },
+    /**
+     * Method: select
+     * dispatches the grid click to the various selection methods
+     */
+    select : function (rc) {
+        if ($defined(rc) && rc.column !== -1 && rc.row !== -1) {
+            var row = rc.row;
+            if (this.grid.columns.useHeaders()) {
+                row--;
+            }
+            var column = rc.column;
+            if (this.grid.row.useHeaders()) {
+                column--;
+            }
+            if (this.options.cell) {
+                this.selectCell(row, column);
+            }
+            if (this.options.row) {
+                this.selectRow(row);
+            }
+            if (this.options.column) {
+                this.selectColumn(column);
+            }
+        }
+    },
+    /** 
+     * Method: selectCell
+     * Select a cell and apply the jxGridCellSelected style to it.
+     * This deselects a previously selected cell.
      *
+     * If the model supports cell selection, it should implement
+     * a cellSelected function to receive notification of the selection.
+     *
      * Parameters:
-     * row - {Integer} the row to set the height for
-     * height - {Integer} the height to set the row (in pixels)
+     * row - {Integer} the row of the cell to select
+     * col - {Integer} the column of the cell to select
      */
-    setRowHeaderHeight: function(row, height) {
-        //this.rowTableHead.childNodes[row+1].childNodes[0].style.height = (height) + 'px';
-        this.rowTableHead.childNodes[row+1].childNodes[0].childNodes[0].style.height = (height) + 'px';
+    selectCell : function (row, col) {
+        var td = (row >= 0 && col >= 0
+                && row < this.grid.gridTableBody.rows.length && col < this.grid.gridTableBody.rows[row].cells.length) ? this.grid.gridTableBody.rows[row].cells[col]
+                : null;
+        if (!td) {
+            return;
+        }
+
+        if (this.selectedCell) {
+            this.selectedCell.removeClass('jxGridCellSelected');
+        }
+        this.selectedCell = td;
+        this.selectedCell.addClass('jxGridCellSelected');
     },
-    
-    /**
-     * Method: gridChanged
-     * called through the grid listener interface when data has changed in the
-     * underlying model
+    /** 
+     * Method: selectRow
+     * Select a row and apply the jxGridRowSelected style to it.
      *
      * Parameters:
-     * model - {Object} the model that changed
-     * row - {Integer} the row that changed
-     * col - {Integer} the column that changed
-     * value - {Mixed} the new value
+     * row - {Integer} the row to select
      */
-    gridChanged: function(model, row, col, value) {
-        if (this.model == model) {
-            this.gridObj.childNodes[row].childNodes[col].innerHTML = value;
+    selectRow : function (row) {
+        var tr = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row]
+                : null;
+        if (this.selectedRow !== tr) {
+            if (this.selectedRow) {
+                this.selectedRow.removeClass('jxGridRowSelected');
+            }
+            this.selectedRow = tr;
+            this.selectedRow.addClass('jxGridRowSelected');
+            this.selectRowHeader(row);
         }
     },
-    
     /** 
+     * Method: selectRowHeader
+     * Apply the jxGridRowHea}derSelected style to the row header cell of a
+     * selected row.
+     *
+     * Parameters:
+     * row - {Integer} the row header to select
+     */
+    selectRowHeader : function (row) {
+        if (!this.grid.row.useHeaders()) {
+            return;
+        }
+        var cell = (row >= 0 && row < this.grid.rowTableHead.rows.length) ? this.grid.rowTableHead.rows[row].cells[0]
+                : null;
+        if (!cell) {
+            return;
+        }
+        if (this.selectedRowHead !== cell) {
+            if (this.selectedRowHead) {
+                this.selectedRowHead
+                        .removeClass('jxGridRowHeaderSelected');
+            }
+            this.selectedRowHead = cell;
+            cell.addClass('jxGridRowHeaderSelected');
+        }
+    },
+    /** 
+     * Method: selectColumn
+     * Select a column.
+     * This deselects a previously selected column.
+     *
+     * Parameters:
+     * col - {Integer} the column to select
+     */
+    selectColumn : function (col) {
+        if (col >= 0 && col < this.grid.gridTable.rows[0].cells.length) {
+            if (col !== this.selectedCol) {
+                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');
+                    }
+                }
+                this.selectedCol = col;
+                for (i = 0; i < this.grid.gridTable.rows.length; i++) {
+                    this.grid.gridTable.rows[i].cells[col]
+                            .addClass('jxGridColumnSelected');
+                }
+                this.selectColumnHeader(col);
+            }
+        }
+    },
+    /** 
+     * method: selectColumnHeader
+     * Apply the jxGridColumnHeaderSelected style to the column header cell of a
+     * selected column.
+     *
+     * Parameters:
+     * col - {Integer} the column header to select
+     */
+    selectColumnHeader : function (col) {
+        if (this.grid.colTableBody.rows.length === 0
+                || !this.grid.row.useHeaders()) {
+            return;
+        }
+
+        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 !== cell) {
+            if (this.selectedColHead) {
+                this.selectedColHead
+                        .removeClass('jxGridColumnHeaderSelected');
+            }
+            this.selectedColHead = cell;
+            cell.addClass('jxGridColumnHeaderSelected');
+        }
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Prelighter
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Grid plugin to prelight rows, columns, and cells
+ * 
+ * Inspired by the original code in Jx.Grid
+ * 
+ * License: 
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Prelighter = new Class({
+
+    Extends : Jx.Plugin,
+
+    options : {
+        /**
+         * Option: cell
+         * defaults to false.  If set to true, the cell under the mouse is
+         * highlighted as the mouse moves.
+         */
+        cell : false,
+        /**
+         * Option: row
+         * defaults to false.  If set to true, the row under the mouse is
+         * highlighted as the mouse moves.
+         */
+        row : false,
+        /**
+         * Option: column
+         * defaults to false.  If set to true, the column under the mouse is
+         * highlighted as the mouse moves.
+         */
+        column : false,
+        /**
+         * Option: rowHeader
+         * defaults to false.  If set to true, the row header of the row under
+         * the mouse is highlighted as the mouse moves.
+         */
+        rowHeader : false,
+        /**
+         * Option: columnHeader
+         * defaults to false.  If set to true, the column header of the column
+         * under the mouse is highlighted as the mouse moves.
+         */
+        columnHeader : false
+    },
+    /**
+     * 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.prelight = this.prelight.bind(this);
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and connects it to the grid
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+            return;
+        }
+        this.grid = grid;
+        this.grid.addEvent('gridMove', this.bound.prelight);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridMove', this.bound.prelight);
+        }
+        this.grid = null;
+    },
+    /**
+     * Method: prelight
+     * dispatches the event to the various prelight methods.
+     */
+    prelight : function (rc) {
+        if ($defined(rc) && rc.column !== -1 && rc.row !== -1) {
+
+            var row = rc.row;
+            if (this.grid.columns.useHeaders()) {
+                row--;
+            }
+            var column = rc.column;
+            if (this.grid.row.useHeaders()) {
+                column--;
+            }
+
+            if (this.options.cell) {
+                this.prelightCell(row, column);
+            }
+            if (this.options.row) {
+                this.prelightRow(row);
+            }
+            if (this.options.column) {
+                this.prelightColumn(column);
+            }
+            if (this.options.rowHeader) {
+                this.prelightRowHeader(row);
+            }
+            if (this.options.columnHeader) {
+                this.prelightColumnHeader(column);
+            }
+        }
+    },
+    /** 
      * Method: prelightRowHeader
      * apply the jxGridRowHeaderPrelight style to the header cell of a row.
      * This removes the style from the previously pre-lit row header.
@@ -15524,19 +26755,21 @@
      * Parameters:
      * row - {Integer} the row to pre-light the header cell of
      */
-    prelightRowHeader: function(row) {
-        var cell = (row >= 0 && row < this.rowTableHead.rows.length-1) ? this.rowTableHead.rows[row+1].cells[1] : null;
-        if (this.prelitRowHeader != cell) {
+    prelightRowHeader : function (row) {
+        var cell = (row >= 0 && row < this.grid.rowTableHead.rows.length) ? this.grid.rowTableHead.rows[row].cells[0]
+                : null;
+        if (this.prelitRowHeader !== cell) {
             if (this.prelitRowHeader) {
-                this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+                this.prelitRowHeader
+                        .removeClass('jxGridRowHeaderPrelight');
             }
             this.prelitRowHeader = cell;
             if (this.prelitRowHeader) {
-                this.prelitRowHeader.addClass('jxGridRowHeaderPrelight');
+                this.prelitRowHeader
+                        .addClass('jxGridRowHeaderPrelight');
             }
         }
     },
-    
     /** 
      * Method: prelightColumnHeader
      * apply the jxGridColumnHeaderPrelight style to the header cell of a column.
@@ -15545,22 +26778,25 @@
      * Parameters:
      * col - {Integer} the column to pre-light the header cell of
      */
-    prelightColumnHeader: function(col) {
-        if (this.colTableBody.rows.length == 0) {
+    prelightColumnHeader : function (col) {
+        if (this.grid.colTableBody.rows.length === 0) {
             return;
         }
-        var cell = (col >= 0 && col < this.colTableBody.rows[0].cells.length-1) ? this.colTableBody.rows[0].cells[col+1] : null;
-        if (this.prelitColumnHeader != cell) {
+
+        var cell = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col]
+                : null;
+        if (this.prelitColumnHeader !== cell) {
             if (this.prelitColumnHeader) {
-                this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+                this.prelitColumnHeader
+                        .removeClass('jxGridColumnHeaderPrelight');
             }
             this.prelitColumnHeader = cell;
             if (this.prelitColumnHeader) {
-                this.prelitColumnHeader.addClass('jxGridColumnHeaderPrelight');
+                this.prelitColumnHeader
+                        .addClass('jxGridColumnHeaderPrelight');
             }
         }
     },
-    
     /** 
      * Method: prelightRow
      * apply the jxGridRowPrelight style to row.
@@ -15569,10 +26805,11 @@
      * Parameters:
      * row - {Integer} the row to pre-light
      */
-    prelightRow: function(row) {
-        var tr = (row >= 0 && row < this.gridTableBody.rows.length-1) ? this.gridTableBody.rows[row+1] : null;
-        
-        if (this.prelitRow != row) {
+    prelightRow : function (row) {
+        var tr = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row]
+                : null;
+
+        if (this.prelitRow !== row) {
             if (this.prelitRow) {
                 this.prelitRow.removeClass('jxGridRowPrelight');
             }
@@ -15583,7 +26820,6 @@
             }
         }
     },
-    
     /** 
      * Method: prelightColumn
      * apply the jxGridColumnPrelight style to a column.
@@ -15591,29 +26827,23 @@
      * 
      * Parameters:
      * col - {Integer} the column to pre-light
-     *
-     * TODO: Jx.Grid.prelightColumn
-     * Not Yet Implemented.
      */
-    prelightColumn: function(col) {
-        /* TODO: Jx.Grid.prelightColumn
-         * implement column prelighting (possibly) 
-         */
-        if (col >= 0 && col < this.gridTable.rows[0].cells.length) {
+    prelightColumn : function (col) {
+        if (col >= 0 && col < this.grid.gridTable.rows[0].cells.length) {
             if ($chk(this.prelitColumn)) {
-                for (var i=0; i<this.gridTable.rows.length; i++) {
-                    this.gridTable.rows[i].cells[this.prelitColumn + 1].removeClass('jxGridColumnPrelight');
+                for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                    this.grid.gridTable.rows[i].cells[this.prelitColumn]
+                            .removeClass('jxGridColumnPrelight');
                 }
             }
             this.prelitColumn = col;
-            for (var i=0; i<this.gridTable.rows.length; i++) {
-                this.gridTable.rows[i].cells[col + 1].addClass('jxGridColumnPrelight');
-            }            
+            for (i = 0; i < this.grid.gridTable.rows.length; i++) {
+                this.grid.gridTable.rows[i].cells[col]
+                        .addClass('jxGridColumnPrelight');
+            }
+            this.prelightColumnHeader(col);
         }
-        
-        this.prelightColumnHeader(col);
     },
-    
     /** 
      * Method: prelightCell
      * apply the jxGridCellPrelight style to a cell.
@@ -15623,9 +26853,11 @@
      * row - {Integer} the row of the cell to pre-light
      * col - {Integer} the column of the cell to pre-light
      */
-    prelightCell: function(row, col) {
-         var td = (row >=0 && col >=0 && row < this.gridTableBody.rows.length - 1 && col < this.gridTableBody.rows[row+1].cells.length - 1) ? this.gridTableBody.rows[row+1].cells[col+1] : null;
-        if (this.prelitCell != td) {
+    prelightCell : function (row, col) {
+        var td = (row >= 0 && col >= 0
+                && row < this.grid.gridTableBody.rows.length && col < this.grid.gridTableBody.rows[row].cells.length) ? this.grid.gridTableBody.rows[row].cells[col]
+                : null;
+        if (this.prelitCell !== td) {
             if (this.prelitCell) {
                 this.prelitCell.removeClass('jxGridCellPrelight');
             }
@@ -15633,483 +26865,850 @@
             if (this.prelitCell) {
                 this.prelitCell.addClass('jxGridCellPrelight');
             }
-        }    
-    },
-    
-    /** 
-     * Method: selectCell
-     * Select a cell and apply the jxGridCellSelected style to it.
-     * This deselects a previously selected cell.
-     *
-     * If the model supports cell selection, it should implement
-     * a cellSelected function to receive notification of the selection.
-     *
-     * Parameters:
-     * row - {Integer} the row of the cell to select
-     * col - {Integer} the column of the cell to select
+        }
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Sorter
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * 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.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Sorter = new Class({
+
+    Extends : Jx.Plugin,
+
+    options : {},
+    /**
+     * Property: current
+     * refernce to the currently sorted column
      */
-    selectCell: function(row, col) {
-         var td = (row >=0 && col >=0 && row < this.gridTableBody.rows.length - 1 && col < this.gridTableBody.rows[row+1].cells.length - 1) ? this.gridTableBody.rows[row+1].cells[col+1] : null;
-         if (!td) {
-             return;
-         }
-         
-         if (this.selectedCell) {
-             this.selectedCell.removeClass('jxGridCellSelected');
-         }
-         this.selectedCell = td;
-         this.selectedCell.addClass('jxGridCellSelected');
+    current : null,
+    /**
+     * Property: direction
+     * tell us what direction the sort is in (either 'asc' or 'desc')
+     */
+    direction : null,
+    /**
+     * Property: currentGridIndex
+     * Holds the index of the column in the grid
+     */
+    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);
     },
-    
-    /** 
-     * Method: selectRowHeader
-     * Apply the jxGridRowHeaderSelected style to the row header cell of a
-     * selected row.
-     *
-     * Parameters:
-     * row - {Integer} the row header to select
-     * selected - {Boolean} the new state of the row header
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it 
+     * will be monitoring
      */
-    selectRowHeader: function(row, selected) {
-        var cell = (row >= 0 && row < this.rowTableHead.rows.length-1) ? this.rowTableHead.rows[row+1].cells[1] : null;
-        if (!cell) {
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
             return;
         }
-        if (selected) {
-            cell.addClass('jxGridRowHeaderSelected');
-        } else {
-            cell.removeClass('jxGridRowHeaderSelected');
+
+        this.grid = grid;
+
+        this.grid.addEvent('gridClick', this.bound.sort);
+        this.boundAddHeader = this.addHeaderClass.bind(this);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridClick', this.bound.sort);
         }
+        this.grid = null;
     },
-    
-    /** 
-     * Method: selectRow
-     * Select a row and apply the jxGridRowSelected style to it.
-     *
-     * If the model supports row selection, it should implement
-     * a rowSelected function to receive notification of the selection.
-     *
+    /**
+     * Method: sort
+     * called when a grid header is clicked.
+     * 
      * Parameters:
-     * row - {Integer} the row to select
-     * selected - {Boolean} the new state of the row
+     * rc - an object holding the row and column indexes for the clicked header
      */
-    selectRow: function(row, selected) {
-        var tr = (row >= 0 && row < this.gridTableBody.rows.length - 1) ? this.gridTableBody.rows[row+1] : null;
-        if (tr) {
-            if (selected) {
-                tr.addClass('jxGridRowSelected');
-            } else {
-                tr.removeClass('jxGridRowSelected');
+    sort : function (rc) {
+        if ($defined(rc) && rc.column !== -1 && rc.row !== -1) {
+            //check to find the header
+            if (rc.row === 0) {
+                if (this.grid.row.useHeaders()) {
+                    rc.column--;
+                }
+                var column = this.grid.columns.getByGridIndex(rc.column);
+                if (column.isSortable()) {
+                    if (column === this.current) {
+                        //reverse sort order
+                        this.direction = (this.direction === 'asc') ? 'desc' : 'asc';
+                    } else {
+                        this.current = column;
+                        this.direction = 'asc';
+                        this.currentGridIndex = rc.column;
+                    }
+    
+                    //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);
+                    //sort the store
+                    var model = this.grid.getModel();
+                    model.sort(this.current.name, null, this.direction);
+                }
+        
             }
-            this.selectRowHeader(row, selected);
         }
     },
+    /**
+     * Method: addHeaderClass
+     * Event listener that adds the proper sorted column class to the
+     * column we sorted by so that the sort arrow shows 
+     */
+    addHeaderClass : function () {
+        this.grid.removeEvent('doneCreateGrid', this.bound.addHeaderClass);
+        
+        //get header TD
+        var th = this.grid.colTable.rows[0].cells[this.currentGridIndex];
+        th.addClass('jxGridColumnSorted' + this.direction.capitalize());
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Resize
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Grid plugin to enable dynamic resizing of column width and row height
+ *
+ * 
+ * License: 
+ * Copyright (c) 2009, DM Solutions Group.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Resize = new Class({
+
+    Extends : Jx.Plugin,
     
-    /** 
-     * method: selectColumnHeader
-     * Apply the jxGridColumnHeaderSelected style to the column header cell of a
-     * selected column.
-     *
-     * Parameters:
-     * col - {Integer} the column header to select
-     * selected - {Boolean} the new state of the column header
+    options: {
+        /**
+         * Option: columns
+         * set to true to make column widths resizeable
+         */
+        columns: false,
+        /**
+         * Option: rows
+         * set to true to make row heights resizeable
+         */
+        rows: false,
+        /**
+         * Option: tooltip
+         * the tooltip to display for the draggable portion of the
+         * cell header
+         */
+        tooltip: 'Drag to resize, double click to auto-size.'
+    },
+    /**
+     * Property: els
+     * the DOM elements by which the rows/columns are resized.
      */
-    selectColumnHeader: function(col, selected) {
-        if (this.colTableBody.rows.length == 0) {
+    els: [],
+    
+    /**
+     * Property: drags
+     * the Drag instances
+     */
+    drags: [],
+    
+    /**
+     * 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
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
             return;
         }
-        var cell = (col >= 0 && col < this.colTableBody.rows[0].cells.length-1) ? this.colTableBody.rows[0].cells[col+1] : null;
-        if (cell == null) { 
-            return; 
+        this.grid = grid;
+        this.grid.addEvent('doneCreateGrid', this.bound.createResizeHandles);
+        this.grid.addEvent('beginCreateGrid', this.bound.removeResizeHandles);
+        this.createResizeHandles();
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('doneCreateGrid', this.bound.createResizeHandles);
+            this.grid.removeEvent('beginCreateGrid', this.bound.removeResizeHandles);
         }
+        this.grid = null;
+    },
+    
+    removeResizeHandles: function() {
+        this.els.each(function(el) { el.dispose(); } );
+        this.els = [];
+        this.drags.each(function(drag){ drag.detach(); });
+        this.drags = [];
+    },
+    
+    createResizeHandles: function() {
+        if (this.options.columns && this.grid.columns.useHeaders()) {
+            this.grid.columns.columns.each(function(col, idx) {
+                if (col.header) {
+                    var el = new Element('div', {
+                        'class':'jxGridColumnResize',
+                        title: this.options.tooltip,
+                        events: {
+                            dblclick: function() {
+                                col.options.width = 'auto';
+                                col.setWidth(col.getWidth(true));
+                            }
+                        }
+                    }).inject(col.header);
+                    this.els.push(el);
+                    this.drags.push(new Drag(el, {
+                        limit: {y:[0,0]},
+                        onDrag: function(el) {
+                            var w = el.getPosition(el.parentNode).x.toInt();
+                            col.setWidth(w);
+                        }
+                    }));
+                }
+            }, this);
+        }
         
-        if (selected) {
-            cell.addClass('jxGridColumnHeaderSelected');
+        if (this.options.rows && this.grid.row.useHeaders()) {
+            
+        }
+    }
+});
+/**
+ * Namespace: Jx.Plugin.DataView
+ * The namespace for all dataview plugins
+ */
+Jx.Plugin.DataView = {};
+/**
+ * Class: Jx.Slide
+ * Hides and shows an element without depending on a fixed width or height
+ * 
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slide = new Class({
+    
+    Implements: Jx.Object,
+    
+    options: {
+        /**
+         * Option: target
+         * The element to slide
+         */
+        target: null,
+        /**
+         * Option: trigger
+         * The element that will have a click event added to start the slide
+         */
+        trigger: null,
+        /**
+         * Option: type
+         * The type of slide. Can be either "width" or "height". defaults to "height"
+         */
+        type: 'height',
+        /**
+         * Option: setOpenTo
+         * Allows the caller to determine what the open target is set to. Defaults to 'auto'.
+         */
+        setOpenTo: 'auto',
+        /**
+         * Option: onSlideOut
+         * function called when the target is revealed.
+         */
+        onSlideOut: $empty, 
+        /**
+         * Option: onSlideIn
+         * function called when a panel is hidden.
+         */
+        onSlideIn: $empty
+    },
+    /**
+     * APIMethod: init
+     * sets up the slide
+     */
+    init: function () {
+        
+        this.target = $(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.target.store('slider', this);
+
+    },
+    /**
+     * APIMethod: handleClick
+     * event handler for clicks on the trigger. Starts the slide process
+     */
+    handleClick: function () {
+        var sizes = this.target.getMarginBoxSize();
+        if (sizes.height === 0) {
+            this.slide('in');
         } else {
-            cell.removeClass('jxGridColumnHeaderSelected');
+            this.slide('out');
         }
     },
-    
-    /** 
-     * Method: selectColumn
-     * Select a column.
-     * This deselects a previously selected column.
-     *
+    /**
+     * Method: setDisplay
+     * called at the end of the animation to set the target's width or
+     * height as well as other css values to the appropriate values
+     */
+    setDisplay: function () {
+        var h = this.target.getStyle(this.options.type).toInt();
+        if (h === 0) {
+            this.target.setStyle('display', 'none');
+            this.fireEvent('slideOut', this.target);
+        } else {
+            //this.target.setStyle('overflow', 'auto');
+            if (this.target.getStyle('position') !== 'absolute') {
+                this.target.setStyle(this.options.type, this.options.setOpenTo);
+            }
+            this.fireEvent('slideIn', this.target);
+        }   
+    },
+    /**
+     * APIMethod: slide
+     * Actually determines how to slide and initiates the animation.
+     * 
      * Parameters:
-     * col - {Integer} the column to select
-     * selected - {Boolean} the new state of the column
+     * dir - the direction to slide (either "in" or "out")
      */
-    selectColumn: function(col, selected) {
-        /* todo: implement column selection */
-        if (col >= 0 && col < this.gridTable.rows[0].cells.length) {
-            if (selected) {
-                for (var i=0; i<this.gridTable.rows.length; i++) {
-                    this.gridTable.rows[i].cells[col + 1].addClass('jxGridColumnSelected');
-                }
+    slide: function (dir) {
+        var h;
+        if (dir === 'in') {
+            h = this.target.retrieve(this.options.type);
+            this.target.setStyles({
+                'overflow': 'hidden',
+                'display': 'block'
+            });
+            this.target.setStyle(this.options.type, 0);
+            this.target.tween(this.options.type, h);    
+        } else {
+            if (this.options.type === 'height') {
+                h = this.target.getMarginBoxSize().height;
             } else {
-                for (var i=0; i<this.gridTable.rows.length; i++) {
-                    this.gridTable.rows[i].cells[col + 1].removeClass('jxGridColumnSelected');
-                }   
+                h = this.target.getMarginBoxSize().width;
             }
-            this.selectColumnHeader(col, selected);
-        }
+            this.target.store(this.options.type, h);
+            this.target.setStyle('overflow', 'hidden');
+            this.target.setStyle(this.options.type, h);
+            this.target.tween(this.options.type, 0);
+        }       
+    }
+});
+/**
+ * Class: Jx.Plugin.DataView.GroupFolder
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Plugin for DataView - allows folding/unfolding of the groups in the 
+ * grouped dataview
+ * 
+ * License: 
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.DataView.GroupFolder = new Class({
+    
+    Extends: Jx.Plugin,
+    
+    options: {
+        /**
+         * Option: headerClass
+         * The base for styling the header. Gets '-open' or '-closed' added
+         * to it.
+         */
+        headerClass: null
     },
-    
     /**
-     * Method: onMouseMoveGrid
-     * handle the mouse moving over the main grid.  This pre-lights the cell,
-     * and subsquently the row and column (and headers).
-     *
-     * Parameters:
-     * e - {Event} the browser event object
+     * Property: headerState
+     * Hash that holds the open/closed state of each header
      */
-    onMouseMoveGrid: function(e) {
-        var rc = this.getRowColumnFromEvent(e);
-        if (this.options.cellPrelight) {
-            this.prelightCell(rc.row, rc.column);            
+    headerState: new Hash(),
+    /**
+     * APIMethod: attach
+     * Attaches this plugin to a dataview
+     */
+    attach: function (dataView) {
+        if (!$defined(dataView) && !(dataview instanceof Jx.Panel.DataView)) {
+            return;
         }
-        if (this.options.rowPrelight) {
-            this.prelightRow(rc.row);            
-        }
-        if (this.options.rowHeaderPrelight) {
-            this.prelightRowHeader(rc.row);            
-        }
-        if (this.options.columnPrelight) {
-            this.prelightColumn(rc.column);
-        }        
-        if (this.options.columnHeaderPrelight) {
-            this.prelightColumnHeader(rc.column);
-        }        
+        
+        this.dv = dataView;
+        this.dv.addEvent('renderDone', this.setHeaders.bind(this));
     },
+    /**
+     * Method: setHeaders
+     * Called after the dataview is rendered. Sets up the Jx.Slide instance
+     * for each header. It also sets the initial state of each header so that 
+     * if the dataview is redrawn for some reason the open/closed state is 
+     * preserved.
+     */
+    setHeaders: function () {
+        var headers = this.dv.domA.getElements('.' + this.dv.options.groupHeaderClass);
+        
+        headers.each(function (header) {
+            var id = header.get('id');
+            var s = new Jx.Slide({
+                target: header.getNext(),
+                trigger: id,
+                onSlideOut: this.onSlideOut.bind(this, header),
+                onSlideIn: this.onSlideIn.bind(this, header)
+            });
+            
+            if (this.headerState.has(id)) {
+                var state = this.headerState.get(id);
+                if (state === 'open') {
+                    s.slide('in');
+                } else {
+                    s.slide('out');
+                }
+            } else {
+                s.slide('in');
+            }
+        }, this);
+    },
     
     /**
-     * Method: onMouseMoveRowHeader
-     * handle the mouse moving over the row header cells.  This pre-lights
-     * the row and subsequently the row header.
-     *
+     * Method: onSlideIn
+     * Called when a group opens.
+     * 
      * Parameters:
-     * e - {Event} the browser event object
+     * header - the header that was clicked.
      */
-    onMouseMoveRowHeader: function(e) {
-        if (this.options.rowPrelight) {
-            var rc = this.getRowColumnFromEvent(e);
-            this.prelightRow(rc.row);            
+    onSlideIn: function (header) {
+        this.headerState.set(header.get('id'), 'open');
+        if (header.hasClass(this.options.headerClass + '-closed')) {
+            header.removeClass(this.options.headerClass + '-closed');
         }
+        header.addClass(this.options.headerClass + '-open');
     },
-
     /**
-     * Method: onMouseMoveColumnHeader
-     * handle the mouse moving over the column header cells.  This pre-lights
-     * the column and subsequently the column header.
-     *
+     * Method: onSlideOut
+     * Called when a group closes.
+     * 
      * Parameters:
-     * e - {Event} the browser event object
+     * header - the header that was clicked.
      */
-    onMouseMoveColumnHeader: function(e) {
-        if (this.options.columnPrelight) {
-            var rc = this.getRowColumnFromEvent(e);
-            this.prelightColumn(rc.column);
+    onSlideOut: function (header) {
+        this.headerState.set(header.get('id'), 'closed');
+        if (header.hasClass(this.options.headerClass + '-open')) {
+            header.removeClass(this.options.headerClass + '-open');
         }
+        header.addClass(this.options.headerClass + '-closed');
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Field
+ * Field plugin namespace
+ * 
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Field = {};// $Id: $
+/**
+ * Class: Jx.Plugin.Field.Validator
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Field plugin for enforcing validation when a field is not used in a form.
+ *
+ * 
+ * License: 
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ * Parts inspired by mootools-more's Form.Validator class
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Field.Validator = new Class({
+
+    Extends : Jx.Plugin,
+    name: 'Field.Validator',
+    
+    options: {
+        /**
+         * Option: validators
+         * An array that contains either a string that names the predefined
+         * validator to use with its needed options or an object that defines
+         * the options of an InputValidator (also with needed options) defined 
+         * like so:
+         * 
+         * (code)
+         * {
+         *     validatorClass: 'name:with options',    //gets applied to the field
+         *     validator: {                         //used to create the InputValidator
+         *         name: 'validatorName',
+         *         options: {
+         *             errorMsg: 'error message',
+         *             test: function(field,props){}
+         *         }
+         *     }
+         * }
+         * (end)
+         */
+        validators: [],
+        /**
+         * Option: validateOnBlur
+         * Determines whether the plugin will validate the field on blur. 
+         * Defaults to true. 
+         */
+        validateOnBlur: true,
+        /**
+         * Option: validateOnChange
+         * Determines whether the plugin will validate the field on change. 
+         * Defaults to true. 
+         */
+        validateOnChange: true
     },
-    
     /**
-     * Method: onClickGrid
-     * handle the user clicking on the grid.  This triggers an
-     * event to the model (if a cellSelected function is provided).
-     *
-     * The following is an example of a function in the model that selects
-     * a row when the cellSelected function is called and deselects any rows
-     * that are currently selected.
-     *
-     * (code)
-     * cellSelected: function(grid, row,col) { 
-     *    if (this.selectedRow != null) {
-     *        grid.selectRow(this.selectedRow, false);
-     *    }
-     *    this.selectedRow = row;
-     *    grid.selectRow(row, true);
-     * }
-     *
-     * Parameters:
-     * e - {Event} the browser event object
+     * Property: valid
+     * tells whether this field passed validation or not.
      */
-    onClickGrid: function(e) {
-        var rc = this.getRowColumnFromEvent(e);
-        
-        if (this.options.cellSelection && this.model.cellSelected) {
-            this.model.cellSelected(this, rc.row, rc.column);
+    valid: null,
+    /**
+     * Property: errors
+     * array of errors found on this field
+     */
+    errors: [],
+    /**
+     * 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.validate = this.validate.bind(this);
+        this.bound.reset = this.reset.bind(this);
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and connects it to the field
+     */
+    attach: function (field) {
+        if (!$defined(field) && !(field instanceof Jx.Field)) {
+            return;
         }
-        if (this.options.rowSelection && this.model.rowSelected) {
-            this.model.rowSelected(this, rc.row);
+        this.field = field;
+        if (this.field.options.required && !this.options.validators.contains('required')) {
+            //would have used unshift() but reading tells me it may not work in IE.
+            this.options.validators.reverse().push('required');     
+            this.options.validators.reverse();
         }
-        if (this.options.columnSelection && this.model.columnSelected) {
-            this.model.columnSelected(this, rc.column);
+        //add validation classes
+        this.options.validators.each(function (v) {
+            var t = Jx.type(v);
+            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.field.field.addClass(v.validatorClass);
+            }
+        }, this);
+        if (this.options.validateOnBlur) {
+            this.field.field.addEvent('blur', this.bound.validate);
         }
-        
+        if (this.options.validateOnChange) {
+            this.field.field.addEvent('change', this.bound.validate);
+        }
+        this.field.addEvent('reset', this.bound.reset);
     },
-    
     /**
-     * Method: onClickRowHeader
-     * handle the user clicking on the row header.  This triggers an
-     * event to the model (if a rowSelected function is provided) which
-     * can then select the row if desired.  
-     *
-     * The following is an example of a function in the model that selects
-     * a row when the rowSelected function is called and deselects any rows
-     * that are currently selected.  More complex code could be written to 
-     * allow the user to select multiple rows.
-     *
-     * (code)
-     * rowSelected: function(grid, row) {
-     *    if (this.selectedRow != null) {
-     *        grid.selectRow(this.selectedRow, false);
-     *    }
-     *    this.selectedRow = row;
-     *    grid.selectRow(row, true);
-     * }
-     * (end)
-     *
-     * Parameters:
-     * e - {Event} the browser event object
+     * APIMethod: detach
      */
-    onClickRowHeader: function(e) {
-        var rc = this.getRowColumnFromEvent(e);
-        
-        if (this.options.rowSelection && this.model.rowSelected) {
-            this.model.rowSelected(this, rc.row);
+    detach: function () {
+        if (this.field) {
+            this.field.field.removeEvent('blur', this.bound.validate);
+            this.field.field.removeEvent('change', this.bound.validate);
         }
+        this.field.removeEvent('reset', this.bound.reset);
+        this.field = null;
+        this.validators = null;
     },
     
-    /**
-     * Method: onClickColumnHeader
-     * handle the user clicking on the column header.  This triggers column
-     * selection and column (and header) styling changes and an
-     * event to the model (if a columnSelected function is provided)
-     *
-     * The following is an example of a function in the model that selects
-     * a column when the columnSelected function is called and deselects any 
-     * columns that are currently selected.  More complex code could be written
-     * to allow the user to select multiple columns.
-     *
-     * (code)
-     * colSelected: function(grid, col) {
-     *    if (this.selectedColumn != null) {
-     *        grid.selectColumn(this.selectedColumn, false);
-     *    }
-     *    this.selectedColumn = col;
-     *    grid.selectColumn(col, true);
-     * }
-     * (end)
-     *
-     * Parameters:
-     * e - {Event} the browser event object
-     */
-    onClickColumnHeader: function(e) {
-        var rc = this.getRowColumnFromEvent(e);
-        
-        if (this.options.columnSelection && this.model.columnSelected) {
-            this.model.columnSelected(this, rc.column);
+    validate: function () {
+        $clear(this.timer);
+        this.timer = this.validateField.delay(50, this);
+    },
+    
+    validateField: function () {
+        //loop through the validators
+        this.valid = true;
+        this.errors = [];
+        this.options.validators.each(function (v) {
+            var val = (Jx.type(v) === 'string') ? Form.Validator.getValidator(v) : this.validators.get(v.validator.name);
+            if (val) {
+                if (!val.test(this.field.field)) {
+                    this.valid = false;
+                    this.errors.push(val.getError(this.field.field));
+                }
+            }          
+        }, this);
+        if (!this.valid) {
+            this.field.domObj.removeClass('jxFieldValidated').addClass('jxFieldInvalid');
+            this.fireEvent('fieldValidationFailed', [this.field, this]);
+        } else {
+            this.field.domObj.removeClass('jxFieldInvalid').addClass('jxFieldValidated');
+            this.fireEvent('fieldValidationPassed', [this.field, this]);  
         }
+        return this.valid;
     },
     
+    isValid: function () {
+        return this.validateField();
+    },
+    
+    reset: function () {
+        this.valid = null;
+        this.errors = [];
+        this.field.field.removeClass('jxFieldInvalid').removeClass('jxFieldValidated');
+    },
     /**
-     * method: getRowColumnFromEvent
-     * retrieve the row and column indexes from an event click.
-     * This function is used by the grid, row header and column
-     * header to safely get these numbers.
-     *
-     * If the event isn't valid (i.e. it wasn't on a TD or TH) then
-     * the returned values will be -1, -1
-     *
-     * Parameters:
-     * e - {Event} the browser event object
-     *
-     * @return Object an object with two properties, row and column,
-     *         that contain the row and column that was clicked
+     * APIMethod: getErrors
+     * USe this method to retrieve all of the errors noted for this field.
      */
-    getRowColumnFromEvent: function(e) {
-        var td = e.target;
-        if (td.tagName != 'TD' && td.tagName != 'TH') {
-            return {row:-1,column:-1};
-        }
-        var tr = td.parentNode;
-        var col = td.cellIndex - 1; /* because of hidden spacer column */
-        var row = tr.rowIndex - 1; /* because of hidden spacer row */
-        
-        if (col == -1) { 
-            /* bug in safari returns 0 for cellIndex - only choice seems
-             * to be to loop through the row
-             */
-            for (var i=0; i<tr.childNodes.length; i++) {
-                if (tr.childNodes[i] == td) {
-                    col = i - 1;
-                    break;
-                }
-            }
-        }
-        return {row:row,column:col};
+    getErrors: function () {
+        return this.errors;
     }
-});/**
- * Class: Jx.Grid.Model
+    
+   
+});
+// $Id: $
+/**
+ * Class: Jx.Plugin.Form
+ * Form plugin namespace
+ * 
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Form = {};// $Id: $
+/**
+ * Class: Jx.Plugin.Form.Validator
+ * 
+ * Extends: <Jx.Plugin>
+ * 
+ * Form plugin for enforcing validation on the fields in a form.
  *
- * Extends: Object
- *
- * Implements: Options, Events
- *
- * A Jx.Grid.Model is the source of data for a <Jx.Grid> instance.  The
- * default implementation of the grid model works with two-dimensional
- * arrays of data and acts as a convenient base class for custom models
- * based on other sources of data.
- *
  * License: 
- * Copyright (c) 2008, DM Solutions Group Inc.
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ * Parts inspired by mootools-more's Form.Validator class
  * 
  * This file is licensed under an MIT style license
  */
-Jx.Grid.Model = new Class({
-    Family: 'Jx.Grid.Model',
-    Implements: [Events, Options],
+Jx.Plugin.Form.Validator = new Class({
+
+    Extends : Jx.Plugin,
+    name: 'Form.Validator',
+    
     options: {
-        /* Option: colHeaderHeight
-         * default 28, the height of the column header row
+        /**
+         * Option: fields
+         * This will be key/value pairs for each of the fields as shown here:
+         * {
+         *     fieldID: {
+         *          ... options for Field.Validator plugin ...
+         *     },
+         *     fieldID: {...
+         *     }
+         * }
          */
-        colHeaderHeight: 28,
-        /* Option: colHeaderHeight
-         * default 28, the height of the column header row
-         */
-        rowHeaderWidth: 28,
-        /* Option: rowHeaderWidth
-         * default 28, the width of the row header column.
-         */
-        colWidth: 50,
-        /* Option: colWidth
-         * default 50, the width of columns
-         */
-        rowHeight: 20,
-        /* Option: rowHeight
-         * default 20, the height of rows
-         */
-        rowHeaders: null,
-        /* Option: columnHeaders
-         * optional column headers, defaults to null
-         */
-        columnHeaders: null
+        fields: null,
+        
+        fieldDefaults: {
+            validateOnBlur: true,
+            validateOnChange: true
+        },
+        
+        validateOnSubmit: true,
+        
+        suspendSubmit: false
     },
-    data: null,
     /**
-     * Constructor: Jx.Grid.Model
-     * create a new grid model
-     *
-     * Parameters:
-     * data - array of data to display in the grid
-     * options - <Jx.Grid.Model.Options>
+     * Property: errorMessagess
+     * element holding
      */
-    initialize: function(data, options) {
-        this.data = data || [];
-        this.setOptions(options);
+    errorMessage: 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.validate = this.validate.bind(this);
+        this.bound.failed = this.fieldFailed.bind(this);
+        this.bound.passed = this.fieldPassed.bind(this);
     },
-    /** 
-     * Method: getColumnCount
-     * This function returns the number of columns of data in the 
-     * model as an integer value.
-     */ 
-    getColumnCount: function() { return (this.data && this.data[0]) ? this.data[0].length : 0; },
-    /* Method: getColumnHeaderHTML
-     * This function returns an HTML string to be placed in the
-     * column header for the given column index.
-     */ 
-    getColumnHeaderHTML: function(col) { 
-        return this.options.columnHeaders?this.options.columnHeaders[col]:col+1;
-     },
-     /* Method: getColumnHeaderHeight
-      * This function returns an integer which is the height of the
-      * column header row in pixels.
-      */ 
-    getColumnHeaderHeight: function() { return this.options.colHeaderHeight; },
-    /* Method: getColumnWidth
-     * This function returns an integer which is the width of the
-     * given column in pixels.
-     */ 
-    getColumnWidth: function(col) { return this.options.colWidth; },
-    /* Method: getRowHeaderHTML
-     * This function returns an HTML string to be placed in the row
-     * header for the given row index
-     */ 
-    getRowHeaderHTML: function(row) { 
-        return this.options.rowHeaders?this.options.rowHeaders[row]:row+1; 
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and connects it to the form
+     */
+    attach: function (form) {
+        if (!$defined(form) && !(form instanceof Jx.Form)) {
+            return;
+        }
+        this.form = form;
+        var plugin = this;
+        //override the isValid function in the form
+        this.form.isValid = function () {
+            return plugin.isValid();
+        };
+        
+        if (this.options.validateOnSubmit && !this.options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', this.bound.validate);
+        } else if (this.options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', function (ev) {
+                ev.stop();
+            });
+        }
+        
+        this.plugins = $H();
+        
+        //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);
+            
+        }, this);
+       
     },
-    /* Method: getRowHeaderWidth
-     * This function returns an integer which is the width of the row
-     * header column in pixels.
-     */ 
-    getRowHeaderWidth: function() { return this.options.rowHeaderWidth; },
-    /* Method: getRowHeight
-     * This function returns an integer which is the height of the
-     * given row in pixels.
-     */ 
-    getRowHeight: function(row) { return this.options.rowHeight; },
-    /* Method: getRowCount
-     * This function returns the number of rows of data in the model
-     * as an integer value.
-     */ 
-    getRowCount: function() { return this.data.length },
-    /* Method: getValueAt
-     * This function returns an HTML string which is the text to place
-     * in the cell at the given row and column.
-     */ 
-    getValueAt: function(row, col) { return (this.data && $chk(this.data[row])) ? this.data[row][col] : ''; },
-    /* Method: setColumnWidth
-     * This function is called with a column index and width in pixels
-     * when a column is resized.  This function is only required if the grid
-     * allows resizeable columns.
+    /**
+     * APIMethod: detach
      */
-    setColumnWidth: function() {},
-    /* Method: isCellEditable
-     * This function returns a boolean value to indicate if a given
-     * cell is editable by the user.
-     */ 
-    isCellEditable: function() { return false },
-    /* Method: setValueAt
-     * This function is called with the row and column of a cell and a
-     * new value for the cell.  It is mandatory to provide this function if any of
-     * the cells in the model are editable.
-     */ 
-    setValueAt: function(row, col, value) {},
-    /* Method: rowSelected
-     * This function is called by the grid to indicate that the user
-     * has selected a row by clicking on the row header.
-     */ 
-    rowSelected: function(grid, row) {
-        if (this.selectedRow != null) {
-            grid.selectRow(this.selectedRow, false);
+    detach: function() {
+        if (this.form) {
+            document.id(this.form).removeEvent('submit');
         }
-        this.selectedRow = row;
-        grid.selectRow(row, true);
-        this.fireEvent('select-row', row);
+        this.form = null;
+        this.plugins.each(function(plugin){
+            plugin.detach();
+            plugin = null;
+        },this);
+        this.plugins = null;
     },
-    /* Method: columnSelected
-     * This function is called by the grid to indicate that the user
-     * has selected a column by clicking on the column header.
-     */ 
-    columnSelected: function(grid, col) {
-        if (this.selectedCol != null) {
-            grid.selectColumn(this.selectedCol, false);
+    /**
+     * APIMethod: isValid
+     * Call this to determine whether the form validates.
+     */
+    isValid: function () {
+        return this.validate();
+    },
+    /**
+     * Method: validate
+     * Method that actually does the work of validating the fields in the form.
+     */
+    validate: function () {
+        var valid = true;
+        this.errors = $H();
+        this.plugins.each(function(plugin){
+            if (!plugin.isValid()) {
+                valid = false;
+                this.errors.set(plugin.field.id,plugin.getErrors());
+            }
+        }, this);
+        if (valid) {
+            this.fireEvent('formValidationPassed', [this.form, this]);
+        } else {
+            this.fireEvent('formValidationFailed', [this.form, this]);
         }
-        this.selectedCol = col;
-        grid.selectColumn(col, true);
-        this.fireEvent('select-column', col);
+        return valid;
     },
-    /* Method: cellSelected
-     * This function is called by the grid to indicate that the user
-     * has selected a cell by clicking on the cell in the grid.
+    /**
+     * Method: fieldFailed
+     * Refires the fieldValidationFailed event from the field validators it contains 
      */
-    cellSelected: function(grid, row,col) { 
-        grid.selectCell(row, col);
-        this.fireEvent('select-cell', [row, col]);
+    fieldFailed: function (field, validator) {
+        this.fireEvent('fieldValidationFailed', [field, validator]);
+    },
+    /**
+     * Method: fielPassed
+     * Refires the fieldValidationPassed event from the field validators it contains 
+     */
+    fieldPassed: function (field, validator) {
+        this.fireEvent('fieldValidationPassed', [field, validator]);
+    },
+    /**
+     * APIMethod: getErrors
+     * Use this method to get all of the errors from all of the fields.
+     */
+    getErrors: function () {
+        if (!$defined(this.errors)) {
+           this.validate(); 
+        }
+        return this.errors;
+    }
     
-    }
+   
 });
-// $Id: context.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: context.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu.Context
  *
@@ -16136,21 +27735,19 @@
      * <Jx.Menu>
      */
     Extends: Jx.Menu,
+    
+    parameters: ['id'],
+    
     /**
-     * Constructor: Jx.ContextMenu
+     * APIMethod: render
      * create a new context menu
-     *
-     * Parameters:
-     * id - {HTMLElement} element or id to make this the context menu
-     * for.  The menu hooks the oncontextmenu event of the element
-     * and shows itself at the mouse position where the right-click
-     * happened.
      */
-    initialize : function(id) {
+    render: function() {
+        this.id = document.id(this.options.id);
+        if (this.id) {
+            this.id.addEvent('contextmenu', this.show.bindWithEvent(this));
+        }
         this.parent();
-        if ($(id)) {
-            $(id).addEvent('contextmenu', this.show.bindWithEvent(this));
-        }
     },
     /**
      * Method: show
@@ -16160,13 +27757,13 @@
      * e - {Event} the mouse event
      */
     show : function(e) {
-        if (this.items.length ==0) {
+        if (this.list.count() ==0) {
             return;
         }
         
         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
@@ -16187,11 +27784,11 @@
 
         e.stop();
     }    
-});// $Id: menu.separator.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: menu.separator.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu.Separator
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
  * A convenience class to create a visual separator in a menu.
  *
@@ -16206,6 +27803,7 @@
  */
 Jx.Menu.Separator = new Class({
     Family: 'Jx.Menu.Separator',
+    Extends: Jx.Object,
     /**
      * Property: domObj
      * {HTMLElement} the HTML element that the separator is contained
@@ -16217,14 +27815,18 @@
      * {<Jx.Menu>, <Jx.Menu.SubMenu>} the menu that the separator is in.
      */
     owner: null,
+    options: {
+        template: "<li class='jxMenuItem'><span class='jxMenuSeparator'></span></li>"
+    },
+    classes: ['jxMenuItem'],
     /**
-     * Constructor: Jx.Menu.Separator
+     * APIMethod: render
      * Create a new instance of a menu separator
      */
-    initialize: function() {
-        this.domObj = new Element('li',{'class':'jxMenuItem'});
-        var span = new Element('span', {'class':'jxMenuSeparator','html':'&nbsp;'});
-        this.domObj.appendChild(span);
+    render: function() {
+        this.parent();
+        this.elements = this.processTemplate(this.options.template, this.classes);
+        this.domObj = this.elements.get('jxMenuItem');
     },
     /**
      * Method: setOwner
@@ -16246,7 +27848,7 @@
      * Show the menu item
      */
     show: $empty
-});// $Id: submenu.js 424 2009-05-12 12:51:44Z pagameba $
+});// $Id: submenu.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Menu.SubMenu
  *
@@ -16272,7 +27874,6 @@
 Jx.Menu.SubMenu = new Class({
     Family: 'Jx.Menu.SubMenu',
     Extends: Jx.Menu.Item,
-    Implements: [Jx.AutoPosition, Jx.Chrome],
     /**
      * Property: subDomObj
      * {HTMLElement} the HTML container for the sub menu.
@@ -16290,30 +27891,32 @@
      */
     visibleItem: null,
     /**
-     * Property: items
-     * {Array} the menu items that are in this sub menu.
+     * Property: list
+     * {<Jx.List>} a list to manage menu items
      */
-    items: null,
+    list: null,
+    options: {
+        template: '<li class="jxMenuItemContainer"><a class="jxMenuItem jxButtonSubMenu"><span class="jxMenuItemContent"><img class="jxMenuItemIcon" src="'+Jx.aPixel.src+'"><span class="jxMenuItemLabel"></span></span></a></li>',
+        position: {
+            horizontal: ['right left', 'left right'],
+            vertical: ['top top']
+        }
+    },
+    
+    classes: ['jxMenuItemContainer', 'jxMenuItem','jxMenuItemIcon','jxMenuItemLabel'],
+    
     /**
-     * Constructor: Jx.SubMenu
+     * APIMethod: render
      * Create a new instance of Jx.SubMenu
-     *
-     * Parameters:
-     * options - see <Jx.Button.Options>
      */
-    initialize: function(options) { 
+    render: function() { 
+        this.parent();
         this.open = false;
-        this.items = [];
-        this.parent(options);
-        this.domA.addClass('jxButtonSubMenu');
         
-        this.contentContainer = new Element('div', {
-            'class': 'jxMenuContainer'
+        this.menu = new Jx.Menu(null, {
+            position: this.options.position
         });
-        this.subDomObj = new Element('ul', {
-            'class':'jxSubMenu'
-        });
-        this.contentContainer.adopt(this.subDomObj);
+        this.menu.domObj = this.domObj;
     },
     /**
      * Method: setOwner
@@ -16330,29 +27933,11 @@
      * Show the sub menu
      */
     show: function() {
-        if (this.open || this.items.length == 0) {
+        if (this.open || this.menu.list.count() == 0) {
             return;
         }
-        
-        this.contentContainer.setStyle('visibility','hidden');
-        this.contentContainer.setStyle('display','block');
-        $(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.domObj, {
-            horizontal: ['right left', 'left right'],
-            vertical: ['top top'],
-            offsets: this.chromeOffsets
-        });
-        
+        this.menu.show();
         this.open = true;
-        this.contentContainer.setStyle('visibility','');
-        
         this.setActive(true);
     },
     
@@ -16362,14 +27947,9 @@
             this.visibleItem.eventInMenu(e)) {
             return true;
         }
-        return $(e.target).descendantOf(this.domObj) ||
-               $(e.target).descendantOf(this.subDomObj) ||
-               this.items.some(
-                   function(item) {
-                       return item instanceof Jx.Menu.SubMenu && 
-                              item.eventInMenu(e);
-                   }
-               );
+        return document.id(e.target).descendantOf(this.domObj) ||
+               this.menu.eventInMenu(e);
+               document.id(e.target).descendantOf(this.menusubDomObj);
     },
     
     /**
@@ -16381,8 +27961,7 @@
             return;
         }
         this.open = false;
-        this.items.each(function(item){item.hide();});
-        this.contentContainer.setStyle('display','none');
+        this.menu.hide();
         this.visibleItem = null;
     },
     /**
@@ -16393,52 +27972,32 @@
      * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
      * can be added by passing multiple arguments to this function.
      */
-    add : function() { /* menu */
-        var that = this;
-        $A(arguments).each(function(item){
-            that.items.push(item);
-            item.setOwner(that);
-            that.subDomObj.adopt(item.domObj);
-        });
+    add: function(item, position) {
+        this.menu.add(item, position);
         return this;
     },
     /**
-     * Method: insertBefore
-     * Insert a menu item before another menu item.
+     * Method: remove
+     * Remove a menu item from the menu
      *
      * Parameters:
-     * newItem - {<Jx.MenuItem>} the menu item to insert
-     * targetItem - {<Jx.MenuItem>} the menu item to insert before
+     * item - {<Jx.MenuItem>} the menu item to remove
      */
-    insertBefore: function(newItem, targetItem) {
-        var bInserted = false;
-        for (var i=0; i<this.items.length; i++) {
-            if (this.items[i] == targetItem) {
-                this.items.splice(i, 0, newItem);
-                this.subDomObj.insertBefore(newItem.domObj, targetItem.domObj);
-                bInserted = true;
-                break;
-            }
-        }
-        if (!bInserted) {
-            this.add(newItem);
-        }
+    remove: function(item) {
+        this.menu.remove(item);
+        return this;
     },
     /**
-     * Method: remove
-     * Remove a single menu item from the menu.
+     * Method: replace
+     * Replace a menu item with another menu item
      *
      * Parameters:
-     * item - {<Jx.MenuItem} the menu item to remove.
+     * what - {<Jx.MenuItem>} the menu item to replace
+     * withWhat - {<Jx.MenuItem>} the menu item to replace it with
      */
-    remove: function(item) {
-        for (var i=0; i<this.items.length; i++) {
-            if (this.items[i] == item) {
-                this.items.splice(i,1);
-                this.subDomObj.removeChild(item.domObj);
-                break;
-            }
-        }
+    replace: function(item, withItem) {
+        this.menu.replace(item, withItem);
+        return this;
     },
     /**
      * Method: deactivate
@@ -16497,11 +28056,11 @@
             this.visibleItem.show();
         }
     }
-});// $Id: snap.js 484 2009-07-17 20:06:27Z pagameba $
+});// $Id: snap.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.Splitter.Snap
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
  * A helper class to create an element that can snap a split panel open or
  * closed.
@@ -16517,6 +28076,7 @@
  */
 Jx.Splitter.Snap = new Class({
     Family: 'Jx.Splitter.Snap',
+    Extends: Jx.Object,
     /**
      * Property: snap
      * {HTMLElement} the DOM element of the snap (the thing that gets
@@ -16540,22 +28100,26 @@
      */
     layout: 'vertical',
     /**
-     * Constructor: Jx.Splitter.Snap
-     * Create a new Jx.Splitter.Snap
-     *
      * Parameters:
      * snap - {HTMLElement} the clickable thing that snaps the element
      *           open and closed
      * element - {HTMLElement} the element that gets controlled by the snap
      * splitter - {<Jx.Splitter>} the splitter that this all happens inside of.
      */
-    initialize: function(snap, element, splitter, events) {
-        this.snap = snap;
-        this.element = element;
-        var jxl = element.retrieve('jxLayout');
+    parameters: ['snap','element','splitter','events'],
+    
+    /**
+     * APIMethod: init
+     * Create a new Jx.Splitter.Snap
+     */
+    init: function() {
+        this.snap = this.options.snap;
+        this.element = this.options.element;
+        this.splitter = this.options.splitter;
+        this.events = this.options.events;
+        var jxl = this.element.retrieve('jxLayout');
         jxl.addEvent('sizeChange', this.sizeChange.bind(this));
-        this.splitter = splitter;
-        this.layout = splitter.options.layout; 
+        this.layout = this.splitter.options.layout; 
         var jxo = jxl.options;
         var size = this.element.getContentBoxSize();
         if (this.layout == 'vertical') {
@@ -16565,8 +28129,8 @@
             this.originalSize = size.width;
             this.minimumSize = jxo.minWidth ? jxo.minWidth : 0;
         }
-        events.each(function(eventName) {
-            snap.addEvent(eventName, this.toggleElement.bind(this));
+        this.events.each(function(eventName) {
+            this.snap.addEvent(eventName, this.toggleElement.bind(this));
         }, this);
     },
     
@@ -16575,19 +28139,20 @@
      * Snap the element open or closed.
      */
     toggleElement: function() {
+        var size = this.element.getContentBoxSize();
         var newSize = {};
         if (this.layout == 'vertical') {
-            if (this.element.clientHeight <= this.minimumSize) {
+            if (size.height == this.minimumSize) {
                 newSize.height = this.originalSize;
             } else {
-                this.originalSize = this.element.clientHeight;
+                this.originalSize = size.height;
                 newSize.height = this.minimumSize;
             }
         } else {
-            if (this.element.clientWidth <= this.minimumSize) {
+            if (size.width == this.minimumSize) {
                 newSize.width = this.originalSize;
             } else {
-                this.originalSize = this.element.clientWidth;
+                this.originalSize = size.width;
                 newSize.width = this.minimumSize;
             }
         }
@@ -16603,7 +28168,7 @@
     sizeChange: function() {
         var size = this.element.getContentBoxSize();
         if (this.layout == 'vertical') {
-            if (this.element.clientHeight == this.minimumSize) {
+            if (size.height == this.minimumSize) {
                 this.snap.addClass('jxSnapClosed');
                 this.snap.removeClass('jxSnapOpened');
             } else {
@@ -16611,7 +28176,7 @@
                 this.snap.removeClass('jxSnapClosed');
             }
         } else {
-            if (this.element.clientWidth == this.minimumSize) {
+            if (size.width == this.minimumSize) {
                 this.snap.addClass('jxSnapClosed');
                 this.snap.removeClass('jxSnapOpened');
             } else {
@@ -16620,269 +28185,12 @@
             }
         }
     }
-});// $Id: toolbar.js 451 2009-05-31 21:21:30Z pagameba $
+});// $Id: tabset.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
- * Class: Jx.Toolbar
- *
- * Extends: Object
- *
- * Implements: Options, Events
- *
- * A toolbar is a container object that contains other objects such as
- * buttons.  The toolbar organizes the objects it contains automatically,
- * wrapping them as necessary.  Multiple toolbars may be placed within
- * the same containing object.
- *
- * Jx.Toolbar includes CSS classes for styling the appearance of a
- * toolbar to be similar to traditional desktop application toolbars.
- *
- * There is one special object, Jx.ToolbarSeparator, that provides
- * a visual separation between objects in a toolbar.
- *
- * While a toolbar is generally a *dumb* container, it serves a special
- * purpose for menus by providing some infrastructure so that menus can behave
- * properly.
- *
- * In general, almost anything can be placed in a Toolbar, and mixed with 
- * anything else.
- *
- * Example:
- * The following example shows how to create a Jx.Toolbar instance and place
- * two objects in it.
- *
- * (code)
- * //myToolbarContainer is the id of a <div> in the HTML page.
- * function myFunction() {}
- * var myToolbar = new Jx.Toolbar('myToolbarContainer');
- * 
- * var myButton = new Jx.Button(buttonOptions);
- *
- * var myElement = document.createElement('select');
- *
- * myToolbar.add(myButton, new Jx.ToolbarSeparator(), myElement);
- * (end)
- *
- * Events:
- * add - fired when one or more buttons are added to a toolbar
- * remove - fired when on eor more buttons are removed from a toolbar
- *
- * Implements: 
- * Options
- *
- * License: 
- * Copyright (c) 2008, DM Solutions Group Inc.
- * 
- * This file is licensed under an MIT style license
- */
-Jx.Toolbar = new Class({
-    Family: 'Jx.Toolbar',
-    Implements: [Options,Events],
-    /**
-     * Property: items
-     * {Array} an array of the things in the toolbar.
-     */
-    items : null,
-    /**
-     * Property: domObj
-     * {HTMLElement} the HTML element that the toolbar lives in
-     */
-    domObj : null,
-    /**
-     * Property: isActive
-     * When a toolbar contains <Jx.Menu> instances, they want to know
-     * if any menu in the toolbar is active and this is how they
-     * find out.
-     */
-    isActive : false,
-    options: {
-        type: 'Toolbar',
-        /* Option: position
-         * the position of this toolbar in the container.  The position
-         * affects some items in the toolbar, such as menus and flyouts, which
-         * need to open in a manner sensitive to the position.  May be one of
-         * 'top', 'right', 'bottom' or 'left'.  Default is 'top'.
-         */
-        position: 'top',
-        /* Option: parent
-         * a DOM element to add this toolbar to
-         */
-        parent: null,
-        /* Option: autoSize
-         * if true, the toolbar will attempt to set its size based on the
-         * things it contains.  Default is false.
-         */
-        autoSize: false,
-        /* Option: scroll
-         * if true, the toolbar may scroll if the contents are wider than
-         * the size of the toolbar
-         */
-        scroll: true
-    },
-    /**
-     * Constructor: Jx.Toolbar
-     * Create a new instance of Jx.Toolbar.
-     *
-     * Parameters:
-     * options - <Jx.Toolbar.Options>
-     */
-    initialize : function(options) {
-        this.setOptions(options);
-        this.items = [];
-        
-        this.domObj = new Element('ul', {
-            id: this.options.id,
-            'class':'jx'+this.options.type
-        });
-        
-        if (this.options.parent) {
-            this.addTo(this.options.parent);
-        }
-        this.deactivateWatcher = this.deactivate.bindWithEvent(this);
-        if (this.options.items) {
-            this.add(this.options.items);
-        }
-    },
-    
-    /**
-     * Method: addTo
-     * add this toolbar to a DOM element automatically creating a toolbar
-     * container if necessary
-     *
-     * Parameters:
-     * parent - the DOM element or toolbar container to add this toolbar to.
-     */
-    addTo: function(parent) {
-        var tbc = $(parent).retrieve('jxBarContainer');
-        if (!tbc) {
-            tbc = new Jx.Toolbar.Container({
-                parent: parent, 
-                position: this.options.position, 
-                autoSize: this.options.autoSize,
-                scroll: this.options.scroll
-            });
-        }
-        tbc.add(this);
-        return this;
-    },
-    
-    /**
-     * Method: add
-     * Add an item to the toolbar.  If the item being added is a Jx component
-     * with a domObj property, the domObj is added.  If the item being added
-     * is an LI element, then it is given a CSS class of *jxToolItem*.
-     * Otherwise, the thing is wrapped in a <Jx.ToolbarItem>.
-     *
-     * Parameters:
-     * thing - {Object} the thing to add.  More than one thing can be added
-     * by passing multiple arguments.
-     */
-    add: function( ) {
-        $A(arguments).flatten().each(function(thing) {
-            if (thing.domObj) {
-                thing = thing.domObj;
-            }
-            if (thing.tagName == 'LI') {
-                if (!thing.hasClass('jxToolItem')) {
-                    thing.addClass('jxToolItem');
-                }
-                this.domObj.appendChild(thing);
-            } else {
-                var item = new Jx.Toolbar.Item(thing);
-                this.domObj.appendChild(item.domObj);
-            }            
-        }, this);
-
-        if (arguments.length > 0) {
-            this.fireEvent('add', this);
-        }
-        return this;
-    },
-    /**
-     * Method: remove
-     * remove an item from a toolbar.  If the item is not in this toolbar
-     * nothing happens
-     *
-     * Parameters:
-     * item - {Object} the object to remove
-     *
-     * Returns:
-     * {Object} the item that was removed, or null if the item was not
-     * removed.
-     */
-    remove: function(item) {
-        if (item.domObj) {
-            item = item.domObj;
-        }
-        var li = item.findElement('LI');
-        if (li && li.parentNode == this.domObj) {
-            item.dispose();
-            li.dispose();
-            this.fireEvent('remove', this);
-        } else {
-            return null;
-        }
-    },
-    /**
-     * Method: deactivate
-     * Deactivate the Toolbar (when it is acting as a menu bar).
-     */
-    deactivate: function() {
-        this.items.each(function(o){o.hide();});
-        this.setActive(false);
-    },
-    /**
-     * Method: isActive
-     * Indicate if the toolbar is currently active (as a menu bar)
-     *
-     * Returns:
-     * {Boolean}
-     */
-    isActive: function() { 
-        return this.isActive; 
-    },
-    /**
-     * Method: setActive
-     * Set the active state of the toolbar (for menus)
-     *
-     * Parameters: 
-     * b - {Boolean} the new state
-     */
-    setActive: function(b) { 
-        this.isActive = b;
-        if (this.isActive) {
-            document.addEvent('click', this.deactivateWatcher);
-        } else {
-            document.removeEvent('click', this.deactivateWatcher);
-        }
-    },
-    /**
-     * Method: setVisibleItem
-     * For menus, they want to know which menu is currently open.
-     *
-     * Parameters:
-     * obj - {<Jx.Menu>} the menu that just opened.
-     */
-    setVisibleItem: function(obj) {
-        if (this.visibleItem && this.visibleItem.hide && this.visibleItem != obj) {
-            this.visibleItem.hide();
-        }
-        this.visibleItem = obj;
-        if (this.isActive()) {
-            this.visibleItem.show();
-        }
-    },
-    showItem: function(item) {
-        this.fireEvent('show', item);
-    }
-});
-// $Id: tabset.js 424 2009-05-12 12:51:44Z pagameba $
-/**
  * Class: Jx.TabSet
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
- * Implements: Options, Events
- *
  * A TabSet manages a set of <Jx.Button.Tab> content areas by ensuring that only one
  * of the content areas is visible (i.e. the active tab).  TabSet does not
  * manage the actual tabs.  The instances of <Jx.Button.Tab> that are to be managed
@@ -16914,7 +28222,7 @@
  */
 Jx.TabSet = new Class({
     Family: 'Jx.TabSet',
-    Implements: [Options,Events],
+    Extends: Jx.Object,
     /**
      * Property: tabs
      * {Array} array of tabs that are managed by this tab set
@@ -16927,20 +28235,22 @@
      */
     domObj : null,
     /**
-     * Constructor: Jx.TabSet
-     * Create a new instance of <Jx.TabSet> within a specific element of
-     * the DOM.
-     *
      * Parameters:
      * domObj - {HTMLElement} an element or id of an element to put the
      * content of the tabs into.
      * options - an options object, only event handlers are supported
      * as options at this time.
      */
-    initialize: function(domObj, options) {
-        this.setOptions(options);
+    parameters: ['domObj','options'],
+    
+    /**
+     * APIMethod: init
+     * Create a new instance of <Jx.TabSet> within a specific element of
+     * the DOM.
+     */
+    init: function() {
         this.tabs = [];
-        this.domObj = $(domObj);
+        this.domObj = document.id(this.options.domObj);
         if (!this.domObj.hasClass('jxTabSetContainer')) {
             this.domObj.addClass('jxTabSetContainer');
         }
@@ -17021,14 +28331,12 @@
 
 
 
-// $Id: tabbox.js 426 2009-05-12 15:29:00Z pagameba $
+// $Id: tabbox.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
  * Class: Jx.TabBox
  * 
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events, <Jx.Addable>
- *
  * A convenience class to handle the common case of a single toolbar
  * directly attached to the content area of the tabs.  It manages both a
  * <Jx.Toolbar> and a <Jx.TabSet> so that you don't have to.  If you are using
@@ -17052,7 +28360,7 @@
  */
 Jx.TabBox = new Class({
     Family: 'Jx.TabBox',
-    Implements: [Options, Events, Jx.Addable],
+    Extends: Jx.Widget,
     options: {
         /* Option: parent
          * a DOM element to add the tab box to
@@ -17091,14 +28399,11 @@
      */
     tabSet: null,
     /**
-     * Constructor: Jx.TabBox
+     * APIMethod: render
      * Create a new instance of a TabBox.
-     *
-     * Parameters:
-     * options - <Jx.TabBox.Options>
      */
-    initialize : function(options) {
-        this.setOptions(options);
+    render : function() {
+        this.parent();
         this.tabBar = new Jx.Toolbar({
             type: 'TabBar', 
             position: this.options.position,
@@ -17177,400 +28482,11 @@
         this.tabSet.remove(tab);
     }
 });
-// $Id: container.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: toolbar.separator.js 524 2009-09-18 05:40:16Z jonlb at comcast.net $
 /**
- * Class: Jx.Toolbar.Container
- *
- * Extends: Object
- *
- * Implements: Options, Events, <Jx.Addable>
- *
- * A toolbar container contains toolbars.  A single toolbar container fills
- * the available space horizontally.  Toolbars placed in a toolbar container
- * do not wrap when they exceed the available space.
- *
- * Events:
- * add - fired when one or more toolbars are added to a container
- * remove - fired when one or more toolbars are removed from a container
- *
- * Implements: 
- * Options
- * Events
- * {<Jx.Addable>}
- *
- * License: 
- * Copyright (c) 2008, DM Solutions Group Inc.
- * 
- * This file is licensed under an MIT style license
- */
-Jx.Toolbar.Container = new Class({
-    Family: 'Jx.Toolbar.Container',
-    Implements: [Options,Events, Jx.Addable],
-    /**
-     * Property: domObj
-     * {HTMLElement} the HTML element that the container lives in
-     */
-    domObj : null,
-    options: {
-        /* Option: parent
-         * a DOM element to add this to
-         */
-        parent: null,
-        /* Option: position
-         * the position of the toolbar container in its parent, one of 'top',
-         * 'right', 'bottom', or 'left'.  Default is 'top'
-         */
-        position: 'top',
-        /* Option: autoSize
-         * automatically size the toolbar container to fill its container.
-         * Default is false
-         */
-        autoSize: false,
-        /* Option: scroll
-         * Control whether the user can scroll of the content of the
-         * container if the content exceeds the size of the container.  
-         * Default is true.
-         */
-        scroll: true
-    },
-    /**
-     * Constructor: Jx.Toolbar.Container
-     * Create a new instance of Jx.Toolbar.Container
-     *
-     * Parameters:
-     * options - <Jx.Toolbar.Options>
-     */
-    initialize : function(options) {
-        this.setOptions(options);
-        
-        var d = $(this.options.parent);
-        this.domObj = d || new Element('div');
-        this.domObj.addClass('jxBarContainer');
-        
-        if (this.options.scroll) {
-            this.scroller = new Element('div', {'class':'jxBarScroller'});
-            this.domObj.adopt(this.scroller);
-        }
-
-        /* this allows toolbars to add themselves to this bar container
-         * once it already exists without requiring an explicit reference
-         * to the toolbar container
-         */
-        this.domObj.store('jxBarContainer', this);
-        
-        if (['top','right','bottom','left'].contains(this.options.position)) {
-            this.domObj.addClass('jxBar' +
-                           this.options.position.capitalize());            
-        } else {
-            this.domObj.addClass('jxBarTop');
-            this.options.position = 'top';
-        }
-
-        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));
-            
-            //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');
-            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)
-            });
-            
-            this.scrollRight = new Jx.Button({
-                image: Jx.aPixel.src
-            }).addTo(this.domObj);
-            this.scrollRight.domObj.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)
-            });         
-            
-        } else {
-            this.options.scroll = false;
-        }
-
-        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();
-        }
-    },
-    
-    measure: function() {
-        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);
-                    } else {
-                        this.scroller.setStyle('left',this.scrollWidth);
-                    }
-                }
-            } 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);
-                }
-            }
-        }            
-    },
-    
-    /**
-     * Method: add
-     * Add a toolbar to the container.
-     *
-     * Parameters:
-     * toolbar - {Object} the toolbar to add.  More than one toolbar
-     *    can be added by passing multiple arguments.
-     */
-    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('show', this.scrollIntoView.bind(this));                
-            }
-            if (this.scroller) {
-                this.scroller.adopt(thing.domObj);
-            } else {
-                this.domObj.adopt(thing.domObj);
-            }
-            this.domObj.addClass('jx'+thing.options.type+this.options.position.capitalize());
-        }, this);
-        if (this.options.scroll) {
-            this.update();            
-        }
-        if (arguments.length > 0) {
-            this.fireEvent('add', this);
-        }
-        return this;
-    },
-    /**
-     * Method: remove
-     * remove an item from a toolbar.  If the item is not in this toolbar
-     * nothing happens
-     *
-     * Parameters:
-     * item - {Object} the object to remove
-     *
-     * Returns:
-     * {Object} the item that was removed, or null if the item was not
-     * removed.
-     */
-    remove: function(item) {
-        
-    },
-    /**
-     * Method: scrollIntoView
-     * scrolls an item in one of the toolbars into the currently visible
-     * area of the container if it is not already fully visible
-     *
-     * Parameters:
-     * item - the item to scroll.
-     */
-    scrollIntoView: function(item) {
-        var width = this.domObj.getSize().x;
-        var coords = item.domObj.getCoordinates(this.scroller);
-        
-        //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.
-        
-        //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;
-        } else {
-            //otherwise, convert to int
-            l = l.toInt();
-        }
-        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;
-            }
-        } 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;
-            }
-        }
-                
-        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);
-            }
-        }
-    }
-});
-// $Id: toolbar.item.js 424 2009-05-12 12:51:44Z pagameba $
-/**
- * Class: Jx.Toolbar.Item
- * 
- * Extends: Object
- *
- * Implements: Options
- *
- * A helper class to provide a container for something to go into 
- * a <Jx.Toolbar>.
- *
- * License: 
- * Copyright (c) 2008, DM Solutions Group Inc.
- * 
- * This file is licensed under an MIT style license
- */
-Jx.Toolbar.Item = new Class( {
-    Family: 'Jx.Toolbar.Item',
-    Implements: [Options],
-    options: {
-        /* Option: active
-         * is this item active or not?  Default is true.
-         */
-        active: true
-    },
-    /**
-     * Property: domObj
-     * {HTMLElement} an element to contain the thing to be placed in the
-     * toolbar.
-     */
-    domObj: null,
-    /**
-     * Constructor: Jx.Toolbar.Item
-     * Create a new instance of Jx.Toolbar.Item.
-     *
-     * Parameters:
-     * jxThing - {Object} the thing to be contained.
-     */
-    initialize : function( jxThing ) {
-        this.al = [];
-        this.domObj = new Element('li', {'class':'jxToolItem'});
-        if (jxThing) {
-            if (jxThing.domObj) {
-                this.domObj.appendChild(jxThing.domObj);
-                if (jxThing instanceof Jx.Button.Tab) {
-                    this.domObj.addClass('jxTabItem');
-                }
-            } else {
-                this.domObj.appendChild(jxThing);
-                if (jxThing.hasClass('jxTab')) {
-                    this.domObj.addClass('jxTabItem');
-                }
-            }
-        }
-    }
-});// $Id: toolbar.separator.js 424 2009-05-12 12:51:44Z pagameba $
-/**
  * Class: Jx.Toolbar.Separator
  *
- * Extends: Object
+ * Extends: <Jx.Object>
  *
  * A helper class that represents a visual separator in a <Jx.Toolbar>
  *
@@ -17585,32 +28501,26 @@
  */
 Jx.Toolbar.Separator = new Class({
     Family: 'Jx.Toolbar.Separator',
+    Extends: Jx.Widget,
     /**
-     * Property: domObj
-     * {HTMLElement} The DOM element that goes in the <Jx.Toolbar>
-     */
-    domObj: null,
-    /**
-     * Constructor: Jx.Toolbar.Separator
+     * APIMethod: render
      * Create a new Jx.Toolbar.Separator
      */
-    initialize: function() {
+    render: function() {
         this.domObj = new Element('li', {'class':'jxToolItem'});
         this.domSpan = new Element('span', {'class':'jxBarSeparator'});
         this.domObj.appendChild(this.domSpan);
     }
 });
-// $Id: treeitem.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: treeitem.js 581 2009-10-29 20:59:48Z pagameba $
 /**
- * Class: Jx.TreeItem 
+ * Class: Jx.TreeItem
  *
- * Extends: Object
+ * Extends: <Jx.Widget>
  *
- * Implements: Options, Events
- *
  * An item in a tree.  An item is a leaf node that has no children.
  *
- * Jx.TreeItem supports selection via the click event.  The application 
+ * Jx.TreeItem supports selection via the click event.  The application
  * is responsible for changing the style of the selected item in the tree
  * and for tracking selection if that is important.
  *
@@ -17625,14 +28535,15 @@
  * Events - MooTools Class.Extras
  * Options - MooTools Class.Extras
  *
- * License: 
+ * License:
  * Copyright (c) 2008, DM Solutions Group Inc.
- * 
+ *
  * This file is licensed under an MIT style license
  */
 Jx.TreeItem = new Class ({
     Family: 'Jx.TreeItem',
-    Implements: [Options,Events],
+    Extends: Jx.Widget,
+    selection: null,
     /**
      * Property: domObj
      * {HTMLElement} a reference to the HTML element that is the TreeItem
@@ -17647,7 +28558,7 @@
     options: {
         /* Option: label
          * {String} the label to display for the TreeItem
-         */        
+         */
         label: '',
         /* Option: data
          * {Object} any arbitrary data to be associated with the TreeItem
@@ -17659,11 +28570,10 @@
          */
         contextMenu: null,
         /* Option: enabled
-         * {Boolean} the initial state of the TreeItem.  If the 
+         * {Boolean} the initial state of the TreeItem.  If the
          * TreeItem is not enabled, it cannot be clicked.
          */
         enabled: true,
-        type: 'Item',
         /* Option: image
          * {String} URL to an image to use as the icon next to the
          * label of this TreeItem
@@ -17673,108 +28583,96 @@
          * {String} CSS class to apply to the image, useful for using CSS
          * sprites
          */
-        imageClass: ''
+        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>'
     },
+    classes: ['jxTreeContainer', 'jxTreeItem', 'jxTreeImage', 'jxTreeIcon','jxTreeLabel'],
+    
     /**
-     * Constructor: Jx.TreeItem
+     * APIMethod: render
      * Create a new instance of Jx.TreeItem with the associated options
-     *
-     * Parameters:
-     * options - <Jx.TreeItem.Options>
      */
-    initialize : function( options ) {
-        this.setOptions(options);
+    render : function() {
+        this.parent();
+        this.elements = this.processTemplate(this.options.template, this.classes);
 
-        this.domObj = new Element('li', {'class':'jxTree'+this.options.type});
-        if (this.options.id) {
-            this.domObj.id = this.options.id;
+        this.domObj = this.elements.get('jxTreeContainer');
+        this.domObj.store('jxTreeItem', this);
+        var domA = this.elements.get('jxTreeItem');
+        var domImg = this.elements.get('jxTreeIcon');
+        var domLabel = this.elements.get('jxTreeLabel');
+
+        if (this.domObj) {
+            if (this.options.id) {
+                this.domObj.id = this.options.id;
+            }
+            this.domObj.store('jxTreeItem', this);
+            if (!this.options.enabled) {
+                this.domObj.addClass('jxDisabled');
+            }
         }
-      
-        this.domNode = new Element('img',{
-            'class': 'jxTreeImage', 
-            src: Jx.aPixel.src,
-            alt: '',
-            title: ''
-        });
-        this.domObj.appendChild(this.domNode);
-        
-        this.domLabel = (this.options.draw) ? 
-            this.options.draw.apply(this) : 
-            this.draw();
 
-        this.domObj.appendChild(this.domLabel);
-        this.domObj.store('jxTreeItem', this);
-        
-        if (!this.options.enabled) {
-            this.domObj.addClass('jxDisabled');
-        }
-    },
-    draw: function() {
-        var domImg = new Element('img',{
-            'class':'jxTreeIcon', 
-            src: Jx.aPixel.src,
-            alt: '',
-            title: ''
-        });
-        if (this.options.image) {
+        if (this.options.image && domImg) {
             domImg.setStyle('backgroundImage', 'url('+this.options.image+')');
+            if (this.options.imageClass) {
+                domImg.addClass(this.options.imageClass);
+            }
+            
         }
-        if (this.options.imageClass) {
-            domImg.addClass(this.options.imageClass);
+
+        if (this.options.label && domLabel) {
+            domLabel.set('html',this.options.label);
         }
-        // the clickable part of the button
-        var hasFocus;
-        var mouseDown;
-        
-        var domA = new Element('a',{
-            href:'javascript:void(0)',
-            html: this.options.label
-        });
-        domA.addEvents({
-            click: this.selected.bind(this),
-            dblclick: this.selected.bind(this),
-            drag: function(e) {e.stop();},
-            contextmenu: function(e) { e.stop(); },
-            mousedown: (function(e) {
-               domA.addClass('jxTreeItemPressed');
-               hasFocus = true;
-               mouseDown = true;
-               domA.focus();
-               if (e.rightClick && this.options.contextMenu) {
-                   this.options.contextMenu.show(e);
-               }
-            }).bind(this),
-            mouseup: function(e) {
-                domA.removeClass('jxTreeItemPressed');
-                mouseDown = false;
-            },
-            mouseleave: function(e) {
-                domA.removeClass('jxTreeItemPressed');
-            },
-            mouseenter: function(e) {
-                if (hasFocus && mouseDown) {
-                    domA.addClass('jxTreeItemPressed');
-                }
-            },
-            keydown: function(e) {
-                if (e.key == 'enter') {
-                    domA.addClass('jxTreeItemPressed');
-                }
-            },
-            keyup: function(e) {
-                if (e.key == 'enter') {
+
+        if (domA) {
+            var hasFocus;
+            var mouseDown;
+            domA.addEvents({
+                click: this.click.bind(this),
+                dblclick: this.dblclick.bind(this),
+                drag: function(e) { e.stop(); },
+                contextmenu: function(e) { e.stop(); },
+                mousedown: (function(e) {
+                   domA.addClass('jxTreeItemPressed');
+                   hasFocus = true;
+                   mouseDown = true;
+                   domA.focus();
+                   if (e.rightClick && this.options.contextMenu) {
+                       this.options.contextMenu.show(e);
+                   }
+                }).bind(this),
+                mouseup: function(e) {
                     domA.removeClass('jxTreeItemPressed');
-                }
-            },
-            blur: function() { hasFocus = false; }
-        });
-        domA.appendChild(domImg);
-        if (typeof Drag != 'undefined') {
-            new Drag(domA, {
-                onStart: function() {this.stop();}
+                    mouseDown = false;
+                },
+                mouseleave: function(e) {
+                    domA.removeClass('jxTreeItemPressed');
+                },
+                mouseenter: function(e) {
+                    if (hasFocus && mouseDown) {
+                        domA.addClass('jxTreeItemPressed');
+                    }
+                },
+                keydown: function(e) {
+                    if (e.key == 'enter') {
+                        domA.addClass('jxTreeItemPressed');
+                    }
+                },
+                keyup: function(e) {
+                    if (e.key == 'enter') {
+                        domA.removeClass('jxTreeItemPressed');
+                    }
+                },
+                blur: function() { hasFocus = false; }
             });
+            domA.appendChild(domImg);
+            if (typeof Drag != 'undefined') {
+                new Drag(domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
         }
-        return domA;
     },
     /**
      * Method: finalize
@@ -17785,67 +28683,59 @@
      * Method: finalizeItem
      * Clean up the TreeItem and remove all DOM references
      */
-    finalizeItem: function() {  
+    finalizeItem: function() {
         if (!this.domObj) {
             return;
         }
-        //this.domA.removeEvents();
         this.options = null;
         this.domObj.dispose();
         this.domObj = null;
         this.owner = null;
     },
     /**
-     * Method: clone
-     * Create a clone of the TreeItem
-     * 
-     * Returns: 
-     * {<Jx.TreeItem>} a copy of the TreeItem
-     */
-    clone : function() {
-        return new Jx.TreeItem(this.options);
-    },
-    /**
      * Method: update
      * Update the CSS of the TreeItem's DOM element in case it has changed
      * position
      *
      * Parameters:
-     * shouldDescend - {Boolean} propagate changes to child nodes?
+     * isLast - {Boolean} is the item the last one or not?
      */
-    update : function(shouldDescend) {
-        var isLast = (arguments.length > 1) ? arguments[1] : 
-                     (this.owner && this.owner.isLastNode(this));
+    update : function(isLast) {
         if (isLast) {
-            this.domObj.removeClass('jxTree'+this.options.type);
-            this.domObj.addClass('jxTree'+this.options.type+'Last');
+            this.domObj.addClass(this.options.lastLeafClass);
         } else {
-            this.domObj.removeClass('jxTree'+this.options.type+'Last');
-            this.domObj.addClass('jxTree'+this.options.type);
+            this.domObj.removeClass(this.options.lastLeafClass);
         }
     },
+    click: function() {
+        this.fireEvent('click', this);
+        this.select();
+    },
+    dblclick: function() {
+        this.fireEvent('dblclick', this);
+        this.select();
+    },
     /**
-     * Method: selected
-     * Called when the DOM element for the TreeItem is clicked, the
-     * node is selected.
-     *
-     * Parameters:
-     * e - {Event} the DOM event
+     * Method: select
+     * Select a tree node.
      */
-    selected : function(e) {
-        this.fireEvent('click', this);
+    select: function() {
+        if (this.selection) {
+            console.log('select: '+this.options.label);
+            this.selection.select(document.id(this));
+        }
     },
     /**
      * Method: getName
      * Get the label associated with a TreeItem
      *
-     * Returns: 
+     * Returns:
      * {String} the name
      */
     getName : function() { return this.options.label; },
     /**
      * Method: propertyChanged
-     * A property of an object has changed, synchronize the state of the 
+     * A property of an object has changed, synchronize the state of the
      * TreeItem with the state of the object
      *
      * Parameters:
@@ -17858,9 +28748,12 @@
         } else {
             this.domObj.addClass('jxDisabled');
         }
+    },
+    setSelection: function(selection){
+        this.selection = selection;
     }
 });
-// $Id: treefolder.js 424 2009-05-12 12:51:44Z pagameba $
+// $Id: treefolder.js 581 2009-10-29 20:59:48Z pagameba $
 /**
  * Class: Jx.TreeFolder
  * 
@@ -17885,411 +28778,1453 @@
     Family: 'Jx.TreeFolder',
     Extends: Jx.TreeItem,
     /**
-     * Property: subDomObj
-     * {HTMLElement} an HTML container for the things inside the folder
+     * Property: tree
+     * {<Jx.Tree>} a Jx.Tree instance for managing the folder contents
      */
-    subDomObj : null,
-    /**
-     * Property: nodes
-     * {Array} an array of references to the javascript objects that are
-     * children of this folder
-     */
-    nodes : null,
+    tree : null,
 
     options: {
         /* Option: open
          * is the folder open?  false by default.
          */
-        open : false
+        open: false,
+        /* folders will share a selection with the tree they are in */
+        select: false, 
+        template: '<li class="jxTreeContainer jxTreeBranch"><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><ul class="jxTree"></ul></li>'
     },
+    classes: ['jxTreeContainer','jxTreeImage','jxTreeItem','jxTreeIcon','jxTreeLabel','jxTree'],
     /**
-     * Constructor: Jx.TreeFolder
+     * APIMethod: render
      * Create a new instance of Jx.TreeFolder
-     *
-     * Parameters:
-     * options - <Jx.TreeFolder.Options> and <Jx.TreeItem.Options>
      */
-    initialize : function( options ) {
-        this.parent($merge(options,{type:'Branch'}));
-
-        $(this.domNode).addEvent('click', this.clicked.bindWithEvent(this));
-        this.addEvent('click', this.clicked.bindWithEvent(this));
+    render : function() {
+        this.parent();
+        this.domObj.store('jxTreeFolder', this);
+        this.addEvents({
+            click: this.toggle.bind(this),
+            dblclick: this.toggle.bind(this)
+        })
+        
+        var node = this.elements.get('jxTreeImage');
+        if (node) {
+            document.id(node).addEvent('click', this.toggle.bind(this));
+        }
                 
-        this.nodes = [];
-        this.subDomObj = new Element('ul', {'class':'jxTree'});
-        this.domObj.appendChild(this.subDomObj);
+        this.tree = new Jx.Tree({
+            template: this.options.template,
+            onAdd: function(item) {
+                this.update();
+                this.fireEvent('add', item);
+            }.bind(this),
+            onRemove: function(item) {
+                this.update();
+                this.fireEvent('remove', item);
+            }.bind(this)
+        }, this.elements.get('jxTree'));
         if (this.options.open) {
             this.expand();
         } else {
             this.collapse();
         }
+        
+        this.addEvent('postDestroy',function() {
+            this.tree.destroy();
+        }.bind(this));
     },
+    
+    add: function(item, position) {
+        this.tree.add(item, position);
+        return this;
+    },
+    remove: function(item) {
+        this.tree.remove(item);
+        return this;
+    },
+    replace: function(item, withItem) {
+        this.tree.replace(item, withItem);
+        return this;
+    },
     /**
-     * Method: finalize
-     * Clean up a TreeFolder.
+     * Method: update
+     * Update the CSS of the TreeFolder's DOM element in case it has changed
+     * position.
+     *
+     * Parameters:
+     * shouldDescend - {Boolean} propagate changes to child nodes?
+     * isLast - {Boolean} is this the last item in the list?
      */
-    finalize: function() {
-        this.finalizeFolder();
-        this.finalizeItem();
-        this.subDomObj.dispose();
-        this.subDomObj = null;
+    update: function(shouldDescend,isLast) {
+        /* avoid update if not attached to tree yet */
+        if (!this.domObj.parentNode) return;
+        
+        if (!$defined(isLast)) {
+            isLast = this.domObj.hasClass('jxTreeBranchLastOpen') ||
+                     this.domObj.hasClass('jxTreeBranchLastClosed');
+        }
+
+        ['jxTreeBranchOpen','jxTreeBranchLastOpen','jxTreeBranchClosed',
+        'jxTreeBranchLastClosed'].each(function(c){
+            this.removeClass(c);
+        }.bind(this.domObj));
+        var c = 'jxTreeBranch';
+        c += isLast ? 'Last' : '';
+        c += this.options.open ? 'Open' : 'Closed';
+        this.domObj.addClass(c);
+
+        this.tree.update(shouldDescend, isLast);
     },
+    toggle: function() {
+        if (this.options.open) {
+            this.collapse();
+        } else {
+            this.expand();
+        }
+    },
     /**
-     * Method: finalizeFolder
-     * Internal method to clean up folder-related stuff.
+     * Method: expand
+     * Expands the folder
      */
-    finalizeFolder: function() {
-        this.domObj.childNodes[0].removeEvents();
-        for (var i=this.nodes.length-1; i>=0; i--) {
-            this.nodes[i].finalize();
-            this.nodes.pop();
+    expand : function() {
+        this.options.open = true;
+        document.id(this.tree).setStyle('display', 'block');
+        this.update(true);
+        this.fireEvent('disclosed', this);    
+    },
+    /**
+     * Method: collapse
+     * Collapses the folder
+     */
+    collapse : function() {
+        this.options.open = false;
+        document.id(this.tree).setStyle('display', 'none');
+        this.update(true);
+        this.fireEvent('disclosed', this);
+    },
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return this;
+        } else {
+            return this.tree.findChild(path);
         }
+    }
+});// $Id: tree.js 581 2009-10-29 20:59:48Z pagameba $
+/**
+ * Class: Jx.Tree
+ *
+ * Extends: Jx.TreeFolder
+ *
+ * Jx.Tree displays hierarchical data in a tree structure of folders and nodes.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Extends: <Jx.Widget>
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Tree = new Class({
+    Family: 'Jx.Tree',
+    Extends: Jx.Widget,
+    parameters: ['options','container', 'selection'],
+    selection: null,
+    ownsSelection: false,
+    isOpen: true,
+    list: null,
+    domObj: null,
+    options: {
+        /* APIProperty: select
+         * {Boolean} are items in the tree selectable?  See <Jx.Selection>
+         * for other options relating to selections that can be set here.
+         */
+        select: true,
+        template: '<ul class="jxTreeRoot"></ul>'
+    },
+    classes: ['jxTreeRoot'],
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Tree
+     */
+    render: function() {
+        this.parent();
         
+        if (this.options.selection) {
+            this.selection = this.options.selection;
+        } else if (this.options.select) {
+            this.selection = new Jx.Selection(this.options);
+            this.ownsSelection = true;
+        }
+
+        if (this.selection && this.ownsSelection) {
+            this.selection.addEvent('select', function(item) {
+                this.fireEvent('select', item.retrieve('jxTreeItem'));
+            }.bind(this));
+        }
+        if ($defined(this.options.container) && 
+            document.id(this.options.container)) {
+            this.domObj = this.options.container;
+        } else {
+            this.elements = this.processTemplate(this.options.template, this.classes);
+            this.domObj = this.elements.get('jxTreeRoot');
+        }
+        this.list = new Jx.List(this.domObj, {
+                hover: true,
+                press: true,
+                select: true,
+                onAdd: function(item) {this.update();}.bind(this),
+                onRemove: function(item) {this.update();}.bind(this)
+            }, this.selection);
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
     },
     
+    add: function(item, position) {
+        item.addEvents({
+            add: function(what) { this.fireEvent('add', what).bind(this); },
+            remove: function(what) { this.fireEvent('remove', what).bind(this); },
+            disclose: function(what) { this.fireEvent('disclose', what).bind(this); }
+        })
+        item.setSelection(this.selection);
+        this.list.add(item, position);
+        return this;
+    },
+    remove: function(item) {
+        item.removeEvents('add');
+        item.removeEvents('remove');
+        item.removeEvents('disclose');
+        this.list.remove(item);
+        item.setSelection(null);
+        return this;
+    },
+    replace: function(item, withItem) {
+        this.list.replace(item, withItem);
+        withItem.setSelection(this.selection);
+        item.setSelection(null);
+        return this;
+    },
+    
     /**
-     * Method: clone
-     * Create a clone of the TreeFolder
-     * 
-     * Returns: 
-     * {<Jx.TreeFolder>} a copy of the TreeFolder
+     * Method: cleanup
+     * Clean up a Jx.Tree instance
      */
-    clone : function() {
-        var node = new Jx.TreeFolder(this.options);
-        this.nodes.each(function(n){node.append(n.clone());});
-        return node;
+    cleanup: function() {
+        if (this.ownsSelection) {
+            this.selection.destroy();
+        }
+        this.list.destroy();
+        this.domObj.dispose();
     },
     /**
-     * Method: isLastNode
-     * Indicates if a node is the last thing in the folder.
+     * Method: update
+     * Update the CSS of the Tree's DOM element in case it has changed
+     * position
      *
      * Parameters:
-     * node - {Jx.TreeItem} the node to check
+     * shouldDescend - {Boolean} propagate changes to child nodes?
+     */
+    update: function(shouldDescend, isLast) {
+        
+        if ($defined(isLast)) {
+            if (isLast) {
+                this.domObj.removeClass('jxTreeNest');
+            } else {
+                this.domObj.addClass('jxTreeNest');
+            }
+        }
+        var last = this.list.count() - 1;
+        this.list.each(function(item, idx){
+            var lastItem = idx == last;
+            if (item.retrieve('jxTreeFolder')) {
+                item.retrieve('jxTreeFolder').update(shouldDescend, lastItem);
+            }
+            if (item.retrieve('jxTreeItem')) {
+                item.retrieve('jxTreeItem').update(lastItem);
+            }
+        });
+    },
+    
+    /**
+     * Method: findChild
+     * Get a reference to a child node by recursively searching the tree
+     * 
+     * Parameters:
+     * path - {Array} an array of labels of nodes to search for
      *
      * Returns:
-     *
-     * {Boolean}
+     * {Object} the node or null if the path was not found
      */
-    isLastNode : function(node) {
-        if (this.nodes.length == 0) {
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
             return false;
-        } else {
-            return this.nodes[this.nodes.length-1] == node;
         }
+        //path has more than one thing in it, find a folder and descend into it    
+        var name = path.shift();
+        var result = false;
+        this.list.items().some(function(item) {
+            var treeItem = item.retrieve('jxTreeItem');
+            if (treeItem && treeItem.getName() == name) {
+                if (path.length > 0) {
+                    var folder = item.retrieve('jxTreeFolder');
+                    if (folder) {
+                        result = folder.findChild(path)
+                    }
+                } else {
+                    result = treeItem;
+                }
+            }
+            return result;
+        });
+        return result;
+    }
+});
+
+
+/**
+ * Class: Jx.Slider
+ * This class wraps the mootools-more slider class to make it more Jx.Slider
+ *  
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slider = new Class({
+    
+    Extends: Jx.Widget,
+    
+    options: {
+        /**
+         * Option: template
+         * The template used to render the slider
+         */
+        template: '<div class="jxSliderContainer"><div class="jxSliderKnob"></div></div>',
+        /**
+         * Option: max
+         * The maximum value the slider should have
+         */
+        max: 100,
+        /**
+         * Option: min
+         * The minimum value the slider should ever have
+         */
+        min: 0,
+        /**
+         * Option: step
+         * The distance between adjacent steps. For example, the default (1) 
+         * with min of 0 and max of 100, provides 100 steps between the min 
+         * and max values  
+         */
+        step: 1,
+        /**
+         * Option: mode
+         * Whether this is a vertical or horizontal slider
+         */
+        mode: 'horizontal',
+        /**
+         * Option: wheel
+         * Whether the slider reacts to the scroll wheel.
+         */
+        wheel: true,
+        /**
+         * Option: snap
+         * whether to snap to each step
+         */
+        snap: true,
+        /**
+         * Option: startAt
+         * The value, or step, to put the slider at initially
+         */
+        startAt: 0,
+        /**
+         * Option: offset
+         * 
+         */
+        offset: 0,
+        onChange: $empty,
+        onComplete: $empty
     },
+    
+    slider: null,
+    knob: null,
+    sliderOpts: null,
     /**
-     * Method: update
-     * Update the CSS of the TreeFolder's DOM element in case it has changed
-     * position.
-     *
-     * Parameters:
-     * shouldDescend - {Boolean} propagate changes to child nodes?
+     * APIMethod: render
+     * Create the slider but does not start it up due to issues with it 
+     * having to be visible before it will work properly.
      */
-    update : function(shouldDescend) {
-        /* avoid update if not attached to tree yet */
-        if (!this.parent) return;
-        var isLast = false;
-        if (arguments.length > 1) {
-            isLast = arguments[1];
-        } else {
-            isLast = (this.owner && this.owner.isLastNode(this));
+    render: function () {
+        this.parent();
+        
+        var els = this.processTemplate(this.options.template, ['jxSliderContainer', 'jxSliderKnob']);
+        
+        if (!els.has('jxSliderContainer')) {
+            return;
         }
         
-        var c = 'jxTree'+this.options.type;
-        c += isLast ? 'Last' : '';
-        c += this.options.open ? 'Open' : 'Closed';
-        this.domObj.className = c;
+        this.domObj = els.get('jxSliderContainer');
+        this.knob = els.get('jxSliderKnob');
         
-        if (isLast) {
-            this.subDomObj.className = 'jxTree';
-        } else {
-            this.subDomObj.className = 'jxTree jxTreeNest';
+        this.sliderOpts = {
+            range: [this.options.min, this.options.max],
+            snap: this.options.snap,
+            mode: this.options.mode,
+            wheel: this.options.wheel,
+            steps: (this.options.max - this.options.min) / this.options.step,
+            offset: this.options.offset,
+            onChange: this.change.bind(this),
+            onComplete: this.complete.bind(this)
+        };
+        
+    },
+    /**
+     * Method: change
+     * Called when the slider moves
+     */
+    change: function (step) {
+        this.fireEvent('change', [step, this]);
+    },
+    /**
+     * Method: complete
+     * Called when the slider stops movingand the mouse button is released.
+     */
+    complete: function (step) {
+        this.fireEvent('complete', [step, this]);
+    },
+    /**
+     * APIMethod: start
+     * Call this method after the slider has been rendered in the DOM to start
+     * it up and position the slider at the startAt poisition. 
+     */
+    start: function () {
+        if (!$defined(this.slider)) {
+            this.slider = new Slider(this.domObj, this.knob, this.sliderOpts);
         }
+        this.slider.set(this.options.startAt);
+    }
+}); // $Id: $
+/**
+ * Class: Jx.Formatter
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class used for specific implementations to coerce data into specific formats
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter = new Class({
+    
+    Extends: Jx.Object,
+    
+    /**
+     * APIMethod: format
+     * Empty method that must be overridden by subclasses to provide
+     * the needed formatting functionality.
+     */
+    format: $empty
+});// $Id: $
+/**
+ * Class: Jx.Formatter.Number
+ * 
+ * Extends: <Jx.Formatter>
+ * 
+ * This class formats numbers. You can have it do the following
+ * 
+ * o replace the decimal separator
+ * o use/add a thousands separator
+ * o change the precision (number of decimal places)
+ * o format negative numbers with parenthesis
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Number = new Class({
+    
+    Extends: Jx.Formatter,
+    
+    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
+         */
+        precision: 2,
+        /**
+         * Option: useParens
+         * Whether negative numbers should be done with parenthesis
+         */
+        useParens: true,
+        /**
+         * Option: useThousands
+         * Whether to use the thousands separator
+         */
+        useThousands: true
+    },
+    /**
+     * APIMethod: format
+     * Formats the provided number
+     * 
+     * Parameters:
+     * value - the raw number to format
+     */
+    format : function (value) {
+            //first set the decimal
+        if (Jx.type(value) === 'string') {
+                //remove commas from the string
+            var p = value.split(',');
+            value = p.join('');
+            value = value.toFloat();
+        }
+        value = value.toFixed(this.options.precision);
         
-        if (this.nodes && shouldDescend) {
-            var that = this;
-            this.nodes.each(function(n,i){
-                n.update(false, i==that.nodes.length-1);
-            });
+        //split on the decimalSeparator
+        var parts = value.split('.');
+        var dec = true;
+        if (parts.length === 1) {
+            dec = false;
         }
+        //check for negative
+        var neg = false;
+        var main;
+        var ret = '';
+        if (parts[0].contains('-')) {
+            neg = true;
+            main = parts[0].substring(1, parts[0].length);
+        } else {
+            main = parts[0];
+        }
+    
+        if (this.options.useThousands) {
+            var l = main.length;
+            var left = l % 3;
+            var j = 0;
+            for (var i = 0; i < l; i++) {
+                ret = ret + main.charAt(i);
+                if (i === left - 1 && i !== l - 1) {
+                    ret = ret + this.options.thousandsSeparator;
+                } else if (i >= left) {
+                    j++;
+                    if (j === 3 && i !== l - 1) {
+                        ret = ret + this.options.thousandsSeparator;
+                        j = 0;
+                    }
+                }
+    
+            }
+        } else {
+            ret = parts[0];
+        }
+    
+        if (dec) {
+            ret = ret + this.options.decimalSeparator + parts[1];
+        }
+        if (neg && this.options.useParens) {
+            ret = "(" + ret + ")";
+        } else if (neg && !this.options.useParens) {
+            ret = "-" + ret;
+        }
+    
+        return ret;
+    }
+});// $Id: $
+/**
+ * Class: Jx.Formatter.Currency
+ * 
+ * Extends: <Jx.Formatter.Number>
+ * 
+ * This class formats numbers as US currency. It actually
+ * runs the value through Jx.Formatter.Number first and then 
+ * updates the returned value as currency.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Currency = new Class({
+    
+    Extends: Jx.Formatter.Number,
+    
+    options: {
+        /**
+         * Option: sign
+         * The sign to use for this currency. Defaults to
+         * the US '$'.
+         */
+        sign: "$"
     },
     /**
-     * Method: append
-     * append a node at the end of the sub-tree
-     *
+     * APIMethod: format
+     * Takes a number and formats it as currency.
+     * 
      * Parameters:
-     * node - {Object} the node to append.
+     * value - the number to format
      */
-    append : function( node ) {
-        node.owner = this;
-        this.nodes.push(node);
-        this.subDomObj.appendChild( node.domObj );
-        this.update(true);
-        return this;
+    format: function (value) {
+
+        this.options.precision = 2;
+
+        value = this.parent(value);
+
+        //check for negative
+        var neg = false;
+        if (value.contains('(') || value.contains('-')) {
+            neg = true;
+        }
+    
+        var ret;
+        if (neg && !this.options.useParens) {
+            ret = "-" + this.options.sign + value.substring(1, value.length);
+        } else {
+            ret = this.options.sign + value;
+        }
+    
+        return ret;
+    }
+});// $Id: $
+/**
+ * Class: Jx.Formatter.Date
+ * 
+ * Extends: <Jx.Formatter>
+ * 
+ * This class formats dates using the mootools-more's 
+ * Date extensions. See the -more docs for details of
+ * supported formats for parsing and formatting.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Date = new Class({
+    
+    Extends: Jx.Formatter,
+    
+    options: {
+        /**
+         * Option: format
+         * The format to use. See the mootools-more Date
+         * extension documentation for details on supported 
+         * formats
+         */
+        format: '%B %d, %Y'
     },
     /**
-     * Method: insert
-     * insert a node after refNode.  If refNode is null, insert at beginning
-     *
+     * APIMethod: format
+     * Does the work of formatting dates
+     * 
      * Parameters:
-     * node - {Object} the node to insert
-     * refNode - {Object} the node to insert before
+     * value - the text to format
      */
-    insert : function( node, refNode ) {
-        node.owner = this;
-        //if refNode is not supplied, insert at the beginning.
-        if (!refNode) {
-            this.nodes.unshift(node);
-            //sanity check to make sure there is actually something there
-            if (this.subDomObj.childNodes.length ==0) {
-                this.subDomObj.appendChild(node.domObj);
+    format: function (value) {
+        var d = Date.parse(value);
+        return d.format(this.options.format);
+    }
+});// $Id: $
+/**
+ * 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)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Boolean = new Class({
+    
+    Extends: Jx.Formatter,
+    
+    options: {
+        /**
+         * Option: true
+         * The text to display for true values
+         */
+        'true': 'Yes',
+        /**
+         * Option: false
+         * The text to display for false values
+         */
+        'false': 'No'
+    },
+    /**
+     * APIMethod: format
+     * Takes a value, determines boolean equivalent and 
+     * displays the appropriate text value.
+     * 
+     * Parameters:
+     * value - the text to format
+     */
+    format : function (value) {
+        var b = false;
+        var t = Jx.type(value);
+        switch (t) {
+        case 'string':
+            if (value === 'true') {
+                b = true;
+            }
+            break;
+        case 'number':
+            if (value !== 0) {
+                b = true;
+            }
+            break;
+        case 'boolean':
+            b = value;
+            break;
+        default:
+            b = true;
+        }
+        return b ? this.options['true'] : this.options['false'];
+    }
+    
+});// $Id: $
+/**
+ * Class: Jx.Formatter.Phone
+ * 
+ * Extends: <Jx.Formatter>
+ * 
+ * Formats data as phone numbers. Currently only US-style phone numbers
+ * are supported. 
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Phone = new Class({
+    
+    Extends: Jx.Formatter,
+    
+    options: {
+        /**
+         * Option: useParens
+         * Whether to use parenthesis () around the area code.
+         * Defaults to true
+         */
+        useParens: true,
+        /**
+         * Option: separator
+         * The character to use as a separator in the phone number. 
+         * Defaults to a dash '-'.
+         */
+        separator: "-"
+    },
+    /**
+     * APIMethod: format
+     * Format the input as a phone number. This will strip all non-numeric
+     * characters and apply the current default formatting
+     * 
+     * Parameters:
+     * value - the text to format
+     */
+    format : function (value) {
+        //first strip any non-numeric characters
+        var sep = this.options.separator;
+        var v = '' + value;
+        v = v.replace(/[^0-9]/g, '');
+    
+        //now check the length. For right now, we only do US phone numbers
+        var ret = '';
+        if (v.length === 11) {
+            //do everything including the leading 1
+            ret = v.charAt(0);
+            v = v.substring(1);
+        }
+        if (v.length === 10) {
+            //do the area code
+            if (this.options.useParens) {
+                ret = ret + "(" + v.substring(0, 3) + ")";
             } else {
-                this.subDomObj.insertBefore(node.domObj, this.subDomObj.childNodes[0]);                
+                ret = ret + sep + v.substring(0, 3) + sep;
             }
-        } else {
-            //walk all nodes looking for the ref node.  Track if it actually
-            //happens so we can append if it fails.
-            var b = false;
-            for(var i=0;i<this.nodes.length;i++) {
-                if (this.nodes[i] == refNode) {
-                    //increment to append after ref node.  If this pushes us
-                    //past the end, it'll get appended below anyway
-                    i = i + 1;
-                    if (i < this.nodes.length) {
-                        this.nodes.splice(i, 0, node);
-                        this.subDomObj.insertBefore(node.domObj, this.subDomObj.childNodes[i]);
-                        b = true;
-                        break;
-                    }
-                }
+            v = v.substring(3);
+        }
+        //do the rest of the number
+        ret = ret + v.substring(0, 3) + sep + v.substring(3);
+        return ret;
+    }
+});// $Id: $
+/**
+ * Class: Jx.Fieldset
+ * 
+ * Extends: <Jx.Widget>
+ * 
+ * This class represents a fieldset. It can be used to group fields together.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ * 
+ */
+Jx.Fieldset = new Class({
+	
+    Extends : Jx.Widget,
+
+    options : {
+        /**
+         * Option: legend
+         * The text for the legend of a fieldset. Default is null
+         * or no legend.
+         */
+        legend : null,
+        /**
+         * Option: id
+         * The id to assign to this element
+         */
+        id : null,
+        /**
+         * Option: fieldsetClass
+         * A CSS class to assign to the fieldset. Useful for custom styling of 
+         * the element
+         */
+        fieldsetClass : null,
+        /**
+         * Option: legendClass
+         * A CSS class to assign to the legend. Useful for custom styling of 
+         * the element
+         */
+        legendClass : null,
+        /**
+         * Option: template
+         * a template for how this element should be rendered
+         */
+        template : '<fieldset class="jxFieldset"><legend><span class="jxFieldsetLegend"></span></legend></fieldset>',
+        /**
+         * Option: form
+         * The <Jx.Form> that this fieldset should be added to
+         */
+        form : null
+    },
+    /**
+     * Property: legend
+     * a holder for the legend Element
+     */
+    legend : null,
+    
+    /**
+     * APIMethod: render
+     * Creates a fieldset.
+     */
+    render : function () {
+        this.parent();
+    
+        this.id = this.options.id;
+    
+        if ($defined(this.options.form)
+                && this.options.form instanceof Jx.Form) {
+            this.form = this.options.form;
+        }
+    
+        var els = this.processTemplate(this.options.template, ['jxFieldset', 'jxFieldsetLegend']);
+    
+        //FIELDSET
+        if (els.has('jxFieldset')) {
+            this.domObj = els.get('jxFieldset');
+            if ($defined(this.options.id)) {
+                this.domObj.set('id', this.options.id);
             }
-            //if the node wasn't inserted, it is because refNode didn't exist
-            //and so the fallback is to just append the node.
-            if (!b) {
-                this.nodes.push(node); 
-                this.subDomObj.appendChild(node.domObj); 
+            if ($defined(this.options.fieldsetClass)) {
+                this.domObj.addClass(this.options.fieldsetClass);
             }
         }
-        this.update(true);
-        return this;
+    
+        if (els.has('jxFieldsetLegend')) {
+            this.legend = els.get('jxFieldsetLegend');
+            if ($defined(this.options.legend)) {
+                this.legend.set('html', this.options.legend);
+                if ($defined(this.options.legendClass)) {
+                    this.legend.addClass(this.options.legendClass);
+                }
+            } else {
+                this.legend.destroy();
+            }
+        }
     },
     /**
-     * Method: remove
-     * remove the specified node from the tree
-     *
+     * APIMethod: add
+     * Adds fields to this fieldset
+     * 
      * Parameters:
-     * node - {Object} the node to remove
+     * pass as many fields to this method as you like. They should be 
+     * <Jx.Field> objects
      */
-    remove : function(node) {
-        node.owner = null;
-        for(var i=0;i<this.nodes.length;i++) {
-            if (this.nodes[i] == node) {
-                this.nodes.splice(i, 1);
-                this.subDomObj.removeChild(this.subDomObj.childNodes[i]);
-                break;
+    add : function () {
+        var field;
+        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)) {
+                field.form = this.form;
+                this.form.addField(field);
             }
+            this.domObj.grab(field);
         }
-        this.update(true);
         return this;
+    }
+});
+// $Id: $
+/**
+ * Class: Jx.Field.Check
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a radio input field.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ * 
+ */
+Jx.Field.Checkbox = new Class({
+    
+    Extends : Jx.Field,
+
+    options : {
+        /**
+         * Option: template
+         * The template used for rendering this field
+         */
+        template : '<input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span>',
+        /**
+         * Option: checked
+         * Whether this field is checked or not
+         */
+        checked : false,
+        
+        labelSeparator: ''
     },
     /**
-     * Method: replace
-     * Replace a node with another node
-     *
-     * Parameters:
-     * newNode - {Object} the node to put into the tree
-     * refNode - {Object} the node to replace
-     *
-     * Returns:
-     * {Boolean} true if the replacement was successful.
+     * Property: type
+     * The type of this field
      */
-    replace: function( newNode, refNode ) {
-        //walk all nodes looking for the ref node. 
-        var b = false;
-        for(var i=0;i<this.nodes.length;i++) {
-            if (this.nodes[i] == refNode) {
-                if (i < this.nodes.length) {
-                    newNode.owner = this;
-                    this.nodes.splice(i, 1, newNode);
-                    this.subDomObj.replaceChild(newNode.domObj, refNode.domObj);
-                    return true;
+    type : 'Check',
+    
+    /**
+     * APIMethod: render 
+     * Creates a checkbox input field.
+    */
+    render : function () {
+        this.parent();
+    
+        if ($defined(this.options.checked) && this.options.checked) {
+            if (Browser.Engine.trident) {
+                var parent = this.field.getParent();
+                var sibling;
+                if (parent) {
+                    sibling = this.field.getPrevious();
                 }
+                this.field.setStyle('visibility','hidden');
+                this.field.inject($(document.body));
+                this.field.checked = true;
+                this.field.defaultChecked = true;
+                this.field.dispose();
+                this.field.setStyle('visibility','visible');
+                if (sibling) {
+                    this.field.inject(sibling, 'after');
+                } else if (parent) {
+                    this.field.inject(parent, 'top');
+                }
+            } else {
+                this.field.set("checked", "checked");
+                this.field.set("defaultChecked", "checked");
             }
         }
-        return false;
     },
     
     /**
-     * Method: clicked
-     * handle the user clicking on this folder by expanding or
-     * collapsing it.
-     *
+     * APIMethod: setValue 
+     * Sets the value property of the field
+     * 
      * Parameters: 
-     * e - {Event} the event object
+     * v - The value to set the field to, "checked" if it should be checked.
      */
-    clicked : function(e) {
-        if (this.options.open) {
-            this.collapse();
+    setValue : function (v) {
+        if (v === 'checked') {
+            this.field.set('checked', "checked");
         } else {
-            this.expand();
+            this.field.erase('checked');
         }
     },
+    
     /**
-     * Method: expand
-     * Expands the folder
+     * APIMethod: getValue 
+     * Returns the current value of the field. The field must be
+     * "checked" in order to return a value. Otherwise it returns null.
      */
-    expand : function() {
-        this.options.open = true;
-        this.subDomObj.setStyle('display', 'block');
-        this.update(true);
-        this.fireEvent('disclosed', this);    
+    getValue : function () {
+        if (this.field.get("checked")) {
+            return this.field.get("value");
+        } else {
+            return null;
+        }
     },
+    
     /**
-     * Method: collapse
-     * Collapses the folder
+     * APIMethod: reset
+     * Sets the field back to the value passed in the original
+     * options. no IE hack is implemented because the field should
+     * already be in the DOM when this is called.
      */
-    collapse : function() {
-        this.options.open = false;
-        this.subDomObj.setStyle('display', 'none');
-        this.update(true);
-        this.fireEvent('disclosed', this);
+    reset : function () {
+        if (this.options.checked) {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
+        }
+    }
+    
+});
+// $Id: $
+/**
+ * Class: Jx.Field.Radio
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a radio input field.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Radio = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: template
+         * The template used to create this field
+         */
+        template: '<input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span>',
+        /**
+         * Option: checked
+         * whether this radio button is checked or not
+         */
+        checked: false,
+
+        labelSeparator: ''
     },
     /**
-     * Method: findChild
-     * Get a reference to a child node by recursively searching the tree
+     * Property: type
+     * What kind of field this is
+     */
+    type: 'Radio',
+    
+    /**
+     * APIMethod: render
+     * Creates a radiobutton input field.
+     */
+    render: function () {
+        this.parent();
+        
+        if ($defined(this.options.checked) && this.options.checked) {
+            if (Browser.Engine.trident) {
+                var parent = this.field.getParent();
+                var sibling;
+                if (parent) {
+                    sibling = this.field.getPrevious();
+                }
+                this.field.setStyle('visibility','hidden');
+                this.field.inject($(document.body));
+                this.field.checked = true;
+                this.field.defaultChecked = true;
+                this.field.dispose();
+                this.field.setStyle('visibility','visible');
+                if (sibling) {
+                    this.field.inject(sibling, 'after');
+                } else if (parent) {
+                    this.field.inject(parent, 'top');
+                }
+            } else {
+                this.field.set("checked", "checked");
+                this.field.set("defaultChecked", "checked");
+            }
+        }
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
      * 
      * Parameters:
-     * path - {Array} an array of labels of nodes to search for
-     *
-     * Returns:
-     * {Object} the node or null if the path was not found
+     * v - The value to set the field to, "checked" it should be checked.
      */
-    findChild : function(path) {
-        //path is empty - we are asking for this node
-        if (path.length == 0)
-            return this;
-        
-        //path has only one thing in it - looking for something in this folder
-        if (path.length == 1)
-        {
-            for (var i=0; i<this.nodes.length; i++)
-            {
-                if (this.nodes[i].getName() == path[0])
-                    return this.nodes[i];
-            }
+    setValue: function (v) {
+        if (v === 'checked') {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
+        } 
+    },
+    
+    /**
+     * APIMethod: getValue
+     * Returns the current value of the field. The field must be "checked" 
+     * in order to return a value. Otherwise it returns null.
+     */
+    getValue: function () {
+        if (this.field.get("checked")) {
+            return this.field.get("value");
+        } else {
             return null;
         }
-        //path has more than one thing in it, find a folder and descend into it    
-        var childName = path.shift();
-        for (var i=0; i<this.nodes.length; i++)
-        {
-            if (this.nodes[i].getName() == childName && this.nodes[i].findChild)
-                return this.nodes[i].findChild(path);
+    },
+    
+    /**
+     * Method: reset
+     * Sets the field back to the value passed in the original
+     * options
+     */
+    reset: function () {
+        if (this.options.checked) {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
         }
-        return null;
     }
-});// $Id: tree.js 424 2009-05-12 12:51:44Z pagameba $
+    
+});
+
+
+
+
+// $Id: $
 /**
- * Class: Jx.Tree
- *
- * Extends: Jx.TreeFolder
- *
- * Implements: <Jx.Addable>
- *
- * Jx.Tree displays hierarchical data in a tree structure of folders and nodes.
- *
+ * Class: Jx.Field.Select
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a form select field.
+ * 
+ * These fields are rendered as below.
+ * 
+ * (code)
+ * <div id='' class=''>
+ *    <label for=''>A label for the field</label>
+ *    <select id='' name=''>
+ *      <option value='' selected=''>text</option>
+ *    </select>
+ * </div>
+ * (end)
+ * 
  * Example:
  * (code)
  * (end)
  *
- * Extends: <Jx.TreeFolder>
- *
  * License: 
- * Copyright (c) 2008, DM Solutions Group Inc.
+ * Copyright (c) 2009, Jon Bomgardner.
  * 
  * This file is licensed under an MIT style license
+ * 
  */
-Jx.Tree = new Class({
-    Extends: Jx.TreeFolder,
-    Implements: [Jx.Addable],
-    Family: 'Jx.Tree',
+
+Jx.Field.Select = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: comboOpts
+         * Optional, defaults to null. if not null, this should be an array of objects 
+         * formated like [{value:'', selected: true|false, text:''},...]
+         */
+        comboOpts: null,
+        /**
+         * Option: optGroups
+         * Optional, defaults to null. if not null this should be an array of objects
+         * defining option groups for this select. The comboOpts and optGroups options
+         * are mutually exclusive. optGroups will always be shown if defined.
+         * 
+         * define them like [{name: '', options: [{value:'', selected: '', text: ''}...]},...]
+         */
+        optGroups: null,
+        /**
+         * Option: template
+         * The template for creating this select input
+         */
+        template: '<label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span>'
+    },
     /**
-     * Constructor: Jx.Tree
-     * Create a new instance of Jx.Tree
-     *
-     * Parameters:
-     * options: options for <Jx.Addable>
+     * Property: type
+     * Indictes this type of field.
      */
-    initialize : function( options ) {
-        this.parent(options);
-        this.subDomObj = new Element('ul',{
-            'class':'jxTreeRoot'
-        });
+    type: 'Select',
+    
+    /**
+     * APIMethod: render
+     * Creates a select field.
+     */
+    render: function () {
+        this.parent();
         
-        this.nodes = [];
-        this.isOpen = true;
-        
-        this.addable = this.subDomObj;
-        
-        if (this.options.parent) {
-            this.addTo(this.options.parent);
+        if ($defined(this.options.optGroups)) {
+            this.options.optGroups.each(function(group){
+                var gr = new Element('optGroup');
+                gr.set('label',group.name);
+                group.options.each(function(option){
+                    var opt = new Element('option', {
+                        'value': option.value,
+                        'html': option.text
+                    });
+                    if ($defined(option.selected) && option.selected) {
+                        opt.set("selected", "selected");
+                    }
+                    gr.grab(opt);
+                },this);
+                this.field.grab(gr);
+            },this);
+        } else if ($defined(this.options.comboOpts)) {
+            this.options.comboOpts.each(function (item) {
+                var opt = new Element('option', {
+                    'value': item.value,
+                    'html': item.text
+                });
+                if ($defined(item.selected) && item.selected) {
+                    opt.set("selected", "selected");
+                }
+                this.field.grab(opt);
+            }, this);
         }
     },
     
     /**
-     * Method: finalize
-     * Clean up a Jx.Tree instance
+     * Method: setValue
+     * Sets the value property of the field
+     * 
+     * Parameters:
+     * v - The value to set the field to.
      */
-    finalize: function() { 
-        this.clear(); 
-        this.subDomObj.parentNode.removeChild(this.subDomObj); 
+    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);
     },
+    
     /**
-     * Method: clear
-     * Clear the tree of all child nodes
+     * Method: getValue
+     * Returns the current value of the field.
      */
-    clear: function() {
-        for (var i=this.nodes.length-1; i>=0; i--) {
-            this.subDomObj.removeChild(this.nodes[i].domObj);
-            this.nodes[i].finalize();
-            this.nodes.pop();
+    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");
         }
+        return ret;
+    }
+});// $Id: $
+/**
+ * Class: Jx.Field.Textarea
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a textarea field.
+ * 
+ * These fields are rendered as below.
+ * 
+ * (code)
+ * <div id='' class=''>
+ *    <label for=''>A label for the field</label>
+ *    <textarea id='' name='' rows='' cols=''>
+ *      value/ext
+ *    </textarea>
+ * </div>
+ * (end)
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ * 
+ */
+Jx.Field.Textarea = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: rows
+         * the number of rows to show
+         */
+        rows: null,
+        /**
+         * Option: columns
+         * the number of columns to show
+         */
+        columns: null,
+        /**
+         * Option: template
+         * the template used to render this field
+         */
+        template: '<label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span>'
     },
     /**
-     * Method: update
-     * Update the CSS of the Tree's DOM element in case it has changed
-     * position
-     *
-     * Parameters:
-     * shouldDescend - {Boolean} propagate changes to child nodes?
+     * Property: type
+     * The type of field this is.
      */
-    update: function(shouldDescend) {
-        var bLast = true;
-        if (this.subDomObj)
-        {
-            if (bLast) {
-                this.subDomObj.removeClass('jxTreeNest');
-            } else {
-                this.subDomObj.addClass('jxTreeNest');
-            }
+    type: 'Textarea',
+    /**
+     * Property: errorClass
+     * The class applied to error elements
+     */
+    errorClass: 'jxFormErrorTextarea',
+    
+    /**
+     * APIMethod: render
+     * Creates the input.
+    */
+    render: function () {
+        this.parent();
+                
+        if ($defined(this.options.rows)) {
+            this.field.set('rows', this.options.rows);
         }
-        if (this.nodes && shouldDescend) {
-            this.nodes.each(function(n){n.update(false);});
+        if ($defined(this.options.columns)) {
+            this.field.set('cols', this.options.columns);
         }
+        
+        //TODO: Do we need to use OverText here as well??
+        
+    }
+});/**
+ * Class: Jx.Field.Button
+ * 
+ * Extends: <Jx.Field>
+ * 
+ * This class represents a button.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, DM Solutions Group
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Button = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        /**
+         * Option: buttonOptions
+         */
+        buttonOptions: {},
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span>'
     },
     /**
-     * Method: append
-     * Append a node at the end of the sub-tree
-     * 
-     * Parameters:
-     * node - {Object} the node to append.
+     * Property: type
+     * The type of this field
      */
-    append: function( node ) {
-        node.owner = this;
-        this.nodes.push(node);
-        this.subDomObj.appendChild( node.domObj );
-        this.update(true);
-        return this;    
+    type: 'Button',
+    
+    processTemplate: function(template, classes, container) {
+        var h = this.parent(template, classes, container);
+        var b = new Jx.Button(this.options.buttonOptions);
+        var c = h.get('jxInputButton');
+        if (c) {
+            b.domObj.replaces(c);
+        }
+        return h;
     }
-});
-
+    
+});// $Id: $
+/**
+ * Class: Jx.Field.Password
+ * 
+ * Extends: <Jx.Field.Text>
+ * 
+ * This class represents a password input field.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Password = new Class({
+    
+    Extends: Jx.Field,
+    
+    options: {
+        template: '<label class="jxInputLabel" ></label><input class="jxInputPassword" type="password" name="{name}"/><span class="jxInputTag"></span>'
+    },
+    
+    type: 'Password'
+});
\ No newline at end of file



More information about the fusion-commits mailing list